From c45be5eb9fd7f5a3c3196b2a3dc9c38b686fe775 Mon Sep 17 00:00:00 2001 From: ychen Date: Wed, 23 Sep 2026 20:32:30 -0700 Subject: [PATCH 01/42] MVP: link Rinx as an in-process module (app-rinx) - rinx path dependency (default-features off, octosense-module), app-rinx feature, registered in linked_modules(). - Patches: Rinx's kevinaboos makepad URL and ruma; markdown crates added to the existing OctoSense-org/makepad table (one canonical source). - AppCard is out of this graph for the MVP: its octos-core (rusqlite 0.32 -> libsqlite3-sys 0.30) conflicts with the Matrix SDK's 0.38. Octos >= dc96b927 (rusqlite 0.40) resolves it. - Cargo.lock seeded with Rinx's locked git packages (matrix-sdk 0.18 etc.). Co-Authored-By: Claude Opus 5.5 (1M context) --- Cargo.lock | 6538 +++++++++++++++++++-------------------------------- Cargo.toml | 34 +- src/apps.rs | 8 +- 3 files changed, 2429 insertions(+), 4151 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 88ba4f23..53ee0bbf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7,12 +7,15 @@ name = "ab_glyph_rasterizer" version = "0.1.8" [[package]] -name = "addr2line" -version = "0.25.1" +name = "accessory" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +checksum = "cc86a613208ca7d144ca24f6ec49c5cdfa5c05a46f2f7ab5880ff99371f165de" dependencies = [ - "gimli", + "macroific 3.1.0", + "proc-macro2", + "quote", + "syn 3.0.5", ] [[package]] @@ -21,96 +24,25 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" -[[package]] -name = "adobe-cmap-parser" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae8abfa9a4688de8fc9f42b3f013b6fffec18ed8a554f5f113577e0b9b3212a3" -dependencies = [ - "pom", -] - [[package]] name = "aead" -version = "0.5.2" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +checksum = "1973cfbc1a2daf9cf550e74e1f088c28e7f7d8c1e1418fb6c9dc5184b7e84c99" dependencies = [ - "crypto-common 0.1.7", - "generic-array", + "crypto-common 0.2.2", + "inout", ] [[package]] name = "aes" -version = "0.8.4" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +checksum = "35f0f96ce78e38c3dc6d8948aa8163d06385be74000f3c7a95bf1eef35d3ea32" dependencies = [ - "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "cipher", - "cpufeatures 0.2.17", -] - -[[package]] -name = "agent-client-protocol" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d386d6a58f4dbe0ebfa98e915caa54005dabdefd419de3d4678b28cd5752444" -dependencies = [ - "agent-client-protocol-derive", - "agent-client-protocol-schema", - "async-io", - "async-process", - "blocking", - "futures", - "futures-concurrency", - "rustc-hash 2.1.3", - "rustix", - "schemars 1.2.2", - "serde", - "serde_json", - "shell-words", - "tracing", - "uuid", - "windows-sys 0.61.2", -] - -[[package]] -name = "agent-client-protocol-derive" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b94934d118a69e921d14e94d4af5b3076563318c52662c89a0ecb3f139e1da" -dependencies = [ - "quote", - "syn 2.0.119", -] - -[[package]] -name = "agent-client-protocol-schema" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06679e1542356341f4550ccfb16338b64f37f6af70de2105446ef6fbb078234c" -dependencies = [ - "anyhow", - "derive_more", - "schemars 1.2.2", - "serde", - "serde_json", - "serde_with", - "strum", - "tracing", -] - -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "once_cell 1.21.4", - "version_check", - "zerocopy 0.8.57", + "cpubits", + "cpufeatures 0.3.1", ] [[package]] @@ -144,20 +76,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] -name = "android_log-sys" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84521a3cf562bc62942e294181d9eef17eb38ceb8c68677bc49f144e4c3d4f8d" - -[[package]] -name = "android_logger" -version = "0.14.1" +name = "android-build" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b07e8e73d720a1f2e4b6014766e6039fd2e96a4fa44e2a78d0e1fa2ff49826" +checksum = "f9fc9904ad2ad097c3c1cfe2eacaaf0fc24710936fa9ed941cb310b7c6ed2ab7" dependencies = [ - "android_log-sys", - "env_filter 0.1.4", - "log", + "windows-sys 0.52.0", ] [[package]] @@ -169,23 +93,6 @@ dependencies = [ "libc", ] -[[package]] -name = "anndists" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8396b473aa0bceed68fb32462505387ea39fa47c7029417e0a49f10592b036" -dependencies = [ - "anyhow", - "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "cpu-time", - "env_logger", - "lazy_static", - "log", - "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus", - "rayon", -] - [[package]] name = "anstream" version = "1.0.0" @@ -243,34 +150,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] -name = "arbitrary" -version = "1.4.2" +name = "anymap2" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" -dependencies = [ - "derive_arbitrary", -] +checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c" [[package]] -name = "arc-swap" -version = "1.9.2" +name = "arboard" +version = "3.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b" +checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf" dependencies = [ - "rustversion", + "clipboard-win", + "image", + "log", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "parking_lot", + "percent-encoding", + "windows-sys 0.60.2", + "x11rb", ] [[package]] -name = "argon2" -version = "0.5.3" +name = "archery" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" -dependencies = [ - "base64ct", - "blake2", - "cpufeatures 0.2.17", - "password-hash", -] +checksum = "33ca55ee147b1926dbea904f50fe4902494e97bc742205abbbf10c709e43815f" [[package]] name = "arrayref" @@ -287,12 +196,42 @@ name = "arrayvec" version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" +dependencies = [ + "serde", +] [[package]] -name = "ascii" -version = "1.1.0" +name = "article-core" +version = "0.1.0" +dependencies = [ + "blake3", + "image", + "makepad-markdown", + "octoscript-ui-l0", + "pulldown-cmark 0.13.4", + "rand 0.8.8", + "serde", + "serde_json", + "unicode-segmentation 1.13.3", + "url", +] + +[[package]] +name = "article-makepad" +version = "0.1.0" +dependencies = [ + "article-core", + "libc", + "makepad-markdown-widgets", + "makepad-widgets", + "unicode-segmentation 1.13.3", +] + +[[package]] +name = "as_variant" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" +checksum = "9dbc3a507a82b17ba0d98f6ce8fd6954ea0c8152e98009d36a40d8dcc8ce078a" [[package]] name = "ash" @@ -301,6 +240,24 @@ dependencies = [ "libloading 0.8.9", ] +[[package]] +name = "assign" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f093eed78becd229346bf859eec0aa4dd7ddde0757287b2b4107a1f09c80002" + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + [[package]] name = "async-channel" version = "2.5.0" @@ -313,6 +270,32 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "async-compression" +version = "0.4.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb61aea1a7def73ee7c350a184f0e70b32c182344e2e75bf70c9b621b83417fd" +dependencies = [ + "compression-codecs", + "compression-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "async-executor" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + [[package]] name = "async-io" version = "2.6.0" @@ -360,6 +343,27 @@ dependencies = [ "rustix", ] +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "async-rx" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a82c1a761cd7b772912fd2500f2c0a850c0c72f5f66543038393ac6cfaa3659a" +dependencies = [ + "futures-core", + "pin-project-lite", +] + [[package]] name = "async-signal" version = "0.2.14" @@ -378,6 +382,28 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "async-task" version = "4.7.1" @@ -396,20 +422,12 @@ dependencies = [ ] [[package]] -name = "async-tungstenite" -version = "0.32.1" +name = "async_cell" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc405d38be14342132609f06f02acaf825ddccfe76c4824a69281e0458ebd4" +checksum = "447ab28afbb345f5408b120702a44e5529ebf90b1796ec76e9528df8e288e6c2" dependencies = [ - "atomic-waker", - "futures-core", - "futures-io", - "futures-task", - "futures-util", - "log", - "pin-project-lite", - "tokio", - "tungstenite 0.28.0", + "loom", ] [[package]] @@ -454,7 +472,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" dependencies = [ "axum-core", - "base64 0.22.1", "bytes", "form_urlencoded", "futures-util", @@ -467,17 +484,14 @@ dependencies = [ "matchit", "memchr 2.8.3", "mime", - "multer", "percent-encoding", "pin-project-lite", "serde_core", "serde_json", "serde_path_to_error", "serde_urlencoded", - "sha1", "sync_wrapper", "tokio", - "tokio-tungstenite 0.29.0", "tower", "tower-layer", "tower-service", @@ -504,20 +518,22 @@ dependencies = [ ] [[package]] -name = "backtrace" -version = "0.3.76" +name = "backon" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" dependencies = [ - "addr2line", - "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "libc", - "miniz_oxide 0.8.9", - "object", - "rustc-demangle", - "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "fastrand", + "gloo-timers 0.3.0", + "tokio", ] +[[package]] +name = "base16ct" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6" + [[package]] name = "base64" version = "0.22.1" @@ -549,13 +565,28 @@ dependencies = [ name = "bit-set" version = "0.8.0" dependencies = [ - "bit-vec", + "bit-vec 0.8.0", +] + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "bit-vec" version = "0.8.0" +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bitflags" version = "1.3.2" @@ -571,14 +602,8 @@ name = "bitflags" version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" - -[[package]] -name = "blake2" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "digest 0.10.7", + "serde_core", ] [[package]] @@ -590,7 +615,7 @@ dependencies = [ "arrayvec 0.7.8", "cc", "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "constant_time_eq 0.4.2", + "constant_time_eq", "cpufeatures 0.3.1", ] @@ -614,11 +639,11 @@ dependencies = [ [[package]] name = "block-padding" -version = "0.3.3" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +checksum = "710f1dd022ef4e93f8a438b4ba958de7f64308434fa6a87104481645cc30068b" dependencies = [ - "generic-array", + "hybrid-array", ] [[package]] @@ -643,6 +668,12 @@ dependencies = [ "piper", ] +[[package]] +name = "blurhash" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79769241dcd44edf79a732545e8b5cec84c247ac060f5252cd51885d093a8fc" + [[package]] name = "brotli" version = "8.0.4" @@ -673,29 +704,12 @@ dependencies = [ "tinyvec", ] -[[package]] -name = "bstr" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f" -dependencies = [ - "memchr 2.8.3", - "regex-automata", - "serde_core", -] - [[package]] name = "bumpalo" version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" -[[package]] -name = "bytecount" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" - [[package]] name = "bytemuck" version = "1.25.0" @@ -727,34 +741,27 @@ name = "bytes" version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" -dependencies = [ - "serde", -] [[package]] -name = "bzip2" -version = "0.5.2" +name = "bytesize" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47" -dependencies = [ - "bzip2-sys", -] +checksum = "7354288c522e7e980fafd2075d63d1285794c3a6a16cdd492f189ea406e5f18b" [[package]] -name = "bzip2-sys" -version = "0.1.13+1.0.8" +name = "caseless" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" +checksum = "8b6fd507454086c8edfd769ca6ada439193cdb209c7681712ef6275cccbfe5d8" dependencies = [ - "cc", - "pkg-config 0.3.34", + "unicode-normalization", ] [[package]] name = "cbc" -version = "0.1.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +checksum = "ce2dc9ee5f88d11e0beb842c88b33c8a5cf0d1329c4b19494af42b07dbfe8896" dependencies = [ "cipher", ] @@ -768,7 +775,7 @@ dependencies = [ "find-msvc-tools", "jobserver", "libc", - "shlex 2.0.1", + "shlex", ] [[package]] @@ -777,12 +784,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" -[[package]] -name = "cff-parser" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f5b6e9141c036f3ff4ce7b2f7e432b0f00dee416ddcd4f17741d189ddc2e9d" - [[package]] name = "cfg-if" version = "1.0.4" @@ -803,17 +804,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" -[[package]] -name = "chacha20" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" -dependencies = [ - "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "cipher", - "cpufeatures 0.2.17", -] - [[package]] name = "chacha20" version = "0.10.2" @@ -821,86 +811,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" dependencies = [ "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "cipher", "cpufeatures 0.3.1", "rand_core 0.10.1", ] [[package]] name = "chacha20poly1305" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +checksum = "9b89e1c441e926b9c82a8d023f6e1b7ae0adcfaa7d621814e4d60789bac751cb" dependencies = [ "aead", - "chacha20 0.9.1", + "chacha20", "cipher", "poly1305", - "zeroize", -] - -[[package]] -name = "chromiumoxide" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26ed067eb6c1f660bdb87c05efb964421d2ca262bae0296cdfe38cf0cd949a3e" -dependencies = [ - "async-tungstenite", - "base64 0.22.1", - "bytes", - "chromiumoxide_cdp", - "chromiumoxide_types", - "dunce", - "fnv", - "futures", - "futures-timer", - "pin-project-lite", - "reqwest 0.13.5", - "serde", - "serde_json", - "thiserror 2.0.20", - "tokio", - "tracing", - "url", - "which 8.0.6", - "windows-registry", -] - -[[package]] -name = "chromiumoxide_cdp" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a6a03a7ebac4ea85308f285d6959a3e6b2ce32a0c9465dc7a7b1db0144eec7" -dependencies = [ - "chromiumoxide_pdl", - "chromiumoxide_types", - "serde", - "serde_json", -] - -[[package]] -name = "chromiumoxide_pdl" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c602dea92337bc4d824668d78c5b79c3b4ddb29b40dd7218282bbe8fd3fc2091" -dependencies = [ - "chromiumoxide_types", - "either", - "heck", - "once_cell 1.21.4", - "proc-macro2", - "quote", - "regex", - "serde_json", -] - -[[package]] -name = "chromiumoxide_types" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678d5146e74f16fc4a41978b275af572cd913de1f10270d2b93b6c276bc57d80" -dependencies = [ - "serde", - "serde_json", ] [[package]] @@ -917,31 +842,15 @@ dependencies = [ "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "chrono-tz" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6139a8597ed92cf816dfb33f5dd6cf0bb93a6adc938f11039f371bc5bcd26c3" -dependencies = [ - "chrono", - "phf 0.12.1", -] - -[[package]] -name = "chunked_transfer" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901" - [[package]] name = "cipher" -version = "0.4.4" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" dependencies = [ - "crypto-common 0.1.7", + "block-buffer 0.12.1", + "crypto-common 0.2.2", "inout", - "zeroize", ] [[package]] @@ -966,15 +875,6 @@ dependencies = [ "strsim", ] -[[package]] -name = "clap_complete" -version = "4.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be2ad0423bdbbb0e25bc89add796f3559706d4a95e1bc98e4d9662a957b6a19" -dependencies = [ - "clap", -] - [[package]] name = "clap_derive" version = "4.6.4" @@ -1002,15 +902,6 @@ dependencies = [ "error-code", ] -[[package]] -name = "clru" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197fd99cb113a8d5d9b6376f3aa817f32c1078f2343b714fff7d2ca44fdf67d5" -dependencies = [ - "hashbrown 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "cmake" version = "0.1.58" @@ -1021,31 +912,10 @@ dependencies = [ ] [[package]] -name = "color-eyre" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d" -dependencies = [ - "backtrace", - "color-spantrace", - "eyre", - "indenter", - "once_cell 1.21.4", - "owo-colors", - "tracing-error", -] - -[[package]] -name = "color-spantrace" -version = "0.3.0" +name = "cmov" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8b88ea9df13354b55bc7234ebcce36e6ef896aca2e42a15de9e10edce01b427" -dependencies = [ - "once_cell 1.21.4", - "owo-colors", - "tracing-core", - "tracing-error", -] +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" [[package]] name = "color_quant" @@ -1060,25 +930,49 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] -name = "colored" -version = "2.2.0" +name = "combine" +version = "4.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" +checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" dependencies = [ - "lazy_static", - "windows-sys 0.59.0", + "bytes", + "memchr 2.8.3", ] [[package]] -name = "combine" -version = "4.6.8" +name = "compression-codecs" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" +checksum = "bef16c47ba2797aa6a909cc37d39911f3a6743811fe7408ac0b0cc0276b656e9" dependencies = [ - "bytes", + "compression-core", + "flate2", "memchr 2.8.3", ] +[[package]] +name = "compression-core" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e8ccc4ea9f6acc32d102c0f6d471d11d913ad15f20c04de743374861fa1d414" + +[[package]] +name = "comrak" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daa3d1ea6b01ce72405fa3f826768f51c7a44917527c89b37856ffdd17ee57e4" +dependencies = [ + "caseless", + "entities", + "finl_unicode", + "jetscii", + "phf", + "phf_codegen", + "rustc-hash 2.1.3", + "smallvec 1.16.1", + "typed-arena", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -1095,10 +989,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" [[package]] -name = "constant_time_eq" -version = "0.3.1" +name = "const_panic" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" +checksum = "9603f79528ece8163c496f8932121cb36cfe46259e9c907bb3d8205139d7caa3" +dependencies = [ + "typewit", +] [[package]] name = "constant_time_eq" @@ -1115,25 +1012,6 @@ dependencies = [ "unicode-segmentation 1.13.3", ] -[[package]] -name = "convert_case" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" -dependencies = [ - "unicode-segmentation 1.13.3", -] - -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "core-foundation" version = "0.10.1" @@ -1150,12 +1028,6 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" -[[package]] -name = "core_detect" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" - [[package]] name = "core_maths" version = "0.1.1" @@ -1166,14 +1038,10 @@ dependencies = [ ] [[package]] -name = "cpu-time" -version = "1.0.0" +name = "cpubits" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9e393a7668fe1fad3075085b86c781883000b4ede868f43627b34a87c8b7ded" -dependencies = [ - "libc", - "winapi", -] +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" [[package]] name = "cpufeatures" @@ -1193,21 +1061,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crc" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" -dependencies = [ - "crc-catalog", -] - -[[package]] -name = "crc-catalog" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" - [[package]] name = "crc32fast" version = "1.5.2" @@ -1217,17 +1070,6 @@ dependencies = [ "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "cron" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5877d3fbf742507b66bc2a1945106bd30dd8504019d596901ddd012a4dd01740" -dependencies = [ - "chrono", - "once_cell 1.21.4", - "winnow 0.6.26", -] - [[package]] name = "crossbeam-channel" version = "0.5.17" @@ -1238,20 +1080,10 @@ dependencies = [ ] [[package]] -name = "crossbeam-deque" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.21" +name = "crossbeam-queue" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d" +checksum = "03e8bd762f7479489c70ed6c768ddca99d7296857de437a68dcb2a94365b3fae" dependencies = [ "crossbeam-utils", ] @@ -1266,6 +1098,12 @@ checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" name = "crunchy" version = "0.2.4" +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "crypto-common" version = "0.1.7" @@ -1273,7 +1111,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", - "rand_core 0.6.4", "typenum", ] @@ -1283,7 +1120,9 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ + "getrandom 0.4.3", "hybrid-array", + "rand_core 0.10.1", ] [[package]] @@ -1297,172 +1136,164 @@ dependencies = [ ] [[package]] -name = "darling" -version = "0.23.0" +name = "ctr" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +checksum = "baaca1c4b237092596f64d571e9db6ce4109c4ef9742e27590f1709594461f21" dependencies = [ - "darling_core 0.23.0", - "darling_macro 0.23.0", + "cipher", ] [[package]] -name = "darling" -version = "0.24.1" +name = "ctutils" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" dependencies = [ - "darling_core 0.24.1", - "darling_macro 0.24.1", + "cmov", ] [[package]] -name = "darling_core" -version = "0.23.0" +name = "curve25519-dalek" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +checksum = "b5eed333089e2e1c1ac8c6c0398e5e2497b4c9926ca6d0365ed1e099afa5bc23" dependencies = [ - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.119", + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "cpufeatures 0.3.1", + "curve25519-dalek-derive", + "digest 0.11.3", + "fiat-crypto", + "rand_core 0.10.1", + "rustc_version", + "serde", + "subtle", + "zeroize", ] [[package]] -name = "darling_core" -version = "0.24.1" +name = "curve25519-dalek-derive" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6837e2cf7485aaae18f86181d2f0e9a7ed297a025e220aeabf63fdebd3a2ddff" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ - "ident_case", "proc-macro2", "quote", - "strsim", - "syn 3.0.5", + "syn 2.0.119", ] [[package]] -name = "darling_macro" -version = "0.23.0" +name = "data-url" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" -dependencies = [ - "darling_core 0.23.0", - "quote", - "syn 2.0.119", -] +checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" [[package]] -name = "darling_macro" -version = "0.24.1" +name = "date_header" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c03c416ed1a30fbb027ef484ba6ab6f80e1eada675e1a2b92fd673c045a1f1d" + +[[package]] +name = "deadpool" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785" +checksum = "3e98a7e119cd347f4201e1159b19831029e203e2d8b790547708e8157b4acf1e" dependencies = [ - "darling_core 0.24.1", - "quote", - "syn 3.0.5", + "deadpool-runtime", + "tokio", ] [[package]] -name = "dashmap" -version = "6.2.1" +name = "deadpool-runtime" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" +checksum = "2657f61fb1dd8bf37a8d51093cc7cee4e77125b22f7753f49b289f831bec2bae" dependencies = [ - "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils", - "hashbrown 0.14.5", - "lock_api", - "once_cell 1.21.4", - "parking_lot_core", + "tokio", ] [[package]] -name = "data-encoding" -version = "2.11.1" +name = "deadpool-sync" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" +checksum = "e385cc95d3d582c328b36d1ff90feac061102b001894b555e6b465a2e0eaabbf" +dependencies = [ + "deadpool-runtime", +] [[package]] -name = "data-url" -version = "0.3.2" +name = "decancer" +version = "3.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" +checksum = "a9244323129647178bf41ac861a2cdb9d9c81b9b09d3d0d1de9cd302b33b8a1d" [[package]] -name = "deflate64" -version = "0.1.12" +name = "delegate-display" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2" +checksum = "54e3a499943fd5180aeffcb708164ffb22125ceeccb05491b5297981d37d6249" +dependencies = [ + "impartial-ord", + "itoa", + "macroific 3.1.0", + "proc-macro2", + "quote", + "syn 3.0.5", +] [[package]] -name = "defmt" -version = "1.1.1" +name = "deranged" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" -dependencies = [ - "bitflags 1.3.2", - "defmt-macros", -] +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" [[package]] -name = "defmt-macros" -version = "1.1.1" +name = "derivative" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ - "defmt-parser", "proc-macro2", "quote", - "syn 2.0.119", + "syn 1.0.109", ] [[package]] -name = "defmt-parser" +name = "derive_more" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" dependencies = [ - "thiserror 2.0.20", + "derive_more-impl 1.0.0", ] [[package]] -name = "deranged" -version = "0.5.8" +name = "derive_more" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" dependencies = [ - "serde_core", + "derive_more-impl 2.1.1", ] [[package]] -name = "derive_arbitrary" -version = "1.4.2" +name = "derive_more-impl" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", "syn 2.0.119", ] -[[package]] -name = "derive_more" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" -dependencies = [ - "derive_more-impl", -] - [[package]] name = "derive_more-impl" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ - "convert_case 0.10.0", "proc-macro2", "quote", "rustc_version", @@ -1478,7 +1309,6 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", "crypto-common 0.1.7", - "subtle", ] [[package]] @@ -1490,27 +1320,19 @@ dependencies = [ "block-buffer 0.12.1", "const-oid", "crypto-common 0.2.2", -] - -[[package]] -name = "dirs" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" -dependencies = [ - "dirs-sys", + "ctutils", ] [[package]] name = "dirs-sys" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] @@ -1520,6 +1342,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ "bitflags 2.13.2", + "block2", + "libc", "objc2", ] @@ -1545,18 +1369,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] -name = "dyn-clone" -version = "1.0.20" +name = "ed25519" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a" +dependencies = [ + "serdect", + "signature", +] [[package]] -name = "ecb" -version = "0.1.2" +name = "ed25519-dalek" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a8bfa975b1aec2145850fcaa1c6fe269a16578c44705a532ae3edc92b8881c7" +checksum = "6ebaa1a2bf1290ab3bfe5a7b771d050ebffab2711c19a81691c683a5144a25de" dependencies = [ - "cipher", + "curve25519-dalek", + "ed25519", + "rand_core 0.10.1", + "serde", + "sha2 0.11.0", + "signature", + "subtle", + "zeroize", ] [[package]] @@ -1566,93 +1401,47 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" [[package]] -name = "email-encoding" -version = "0.4.2" +name = "emojis" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "420b9da095f052ea597503e39073b5b3c522f7db933fbac202d91d24492693fd" +checksum = "0b1514ced566c94991ed9563258ecf61e311fd773bf0a3f20606830386bf66e3" dependencies = [ - "base64 0.23.1", - "memchr 2.8.3", + "phf", ] [[package]] -name = "email_address" -version = "0.2.9" +name = "endi" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" [[package]] -name = "encoding_rs" -version = "0.8.41" +name = "entities" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5ef0006ac9ab233c38522f5ae99cae3625151de8f706cacee1cba4b8e2832a" -dependencies = [ - "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "core_detect", - "multiversion", - "multiversion_no_op", - "rustversion", - "scopeguard", - "simdutf8", -] +checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca" [[package]] -name = "endian-type" -version = "0.1.2" +name = "enumflags2" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] [[package]] -name = "enum-as-inner" -version = "0.6.1" +name = "enumflags2_derive" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ - "heck", "proc-macro2", "quote", "syn 2.0.119", ] -[[package]] -name = "env_filter" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" -dependencies = [ - "log", - "regex", -] - -[[package]] -name = "env_filter" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" -dependencies = [ - "log", - "regex", -] - -[[package]] -name = "env_home" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" - -[[package]] -name = "env_logger" -version = "0.11.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" -dependencies = [ - "anstream", - "anstyle", - "env_filter 2.0.0", - "jiff", - "log", -] - [[package]] name = "equivalent" version = "1.0.2" @@ -1679,15 +1468,6 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b5343afd4a8365a643ac588dab4cf234a190c7f6c88c9f6dd6ffe00837661b7" -[[package]] -name = "euclid" -version = "0.20.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bb7ef65b3777a325d1eeefefab5b6d4959da54747e33bd6258e789640f307ad" -dependencies = [ - "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "euclid" version = "0.22.14" @@ -1718,14 +1498,43 @@ dependencies = [ ] [[package]] -name = "eyre" -version = "0.6.14" +name = "eyeball" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08309dbcc659c5549a24ddb9b27027640641b282ef5768267c7e675558986a3" +checksum = "d93bd0ebf93d61d6332d3c09a96e97975968a44e19a64c947bde06e6baff383f" dependencies = [ - "autocfg", - "indenter", - "once_cell 1.21.4", + "futures-core", + "readlock", + "readlock-tokio", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "eyeball-im" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a565cc1ccb357945f66fb4c611de621c3b4cfe8ad58adcd53936f5501312f36" +dependencies = [ + "futures-core", + "imbl", + "tokio", + "tracing", +] + +[[package]] +name = "eyeball-im-util" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdcd038306bd720751aaf9881dfe952379413f463535ff6ee7c6e7e96067169d" +dependencies = [ + "arrayvec 0.7.8", + "eyeball-im", + "futures-core", + "imbl", + "pin-project-lite", + "smallvec 1.16.1", ] [[package]] @@ -1741,13 +1550,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] -name = "faster-hex" -version = "0.10.0" +name = "fancy-regex" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7223ae2d2f179b803433d9c830478527e92b8117eab39460edae7f1614d9fb73" +checksum = "998b056554fbe42e03ae0e152895cd1a7e1002aec800fdc6635d20270260c46f" dependencies = [ - "heapless", - "serde", + "bit-set 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "fancy_constructor" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a8211ab12c36b63c269e17873331dc1da196a3edb7a5c79015677ef972da34a" +dependencies = [ + "macroific 3.1.0", + "proc-macro2", + "quote", + "syn 3.0.5", ] [[package]] @@ -1757,15 +1579,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" [[package]] -name = "fd-lock" -version = "4.0.4" +name = "fax" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" -dependencies = [ - "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "rustix", - "windows-sys 0.59.0", -] +checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a" [[package]] name = "fdeflate" @@ -1777,14 +1594,10 @@ dependencies = [ ] [[package]] -name = "filetime" -version = "0.2.29" +name = "fiat-crypto" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" -dependencies = [ - "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "libc", -] +checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" [[package]] name = "find-msvc-tools" @@ -1793,10 +1606,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d" [[package]] -name = "fixedbitset" -version = "0.5.7" +name = "finl_unicode" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" +checksum = "80bb028c8b4148c9ee0cca68fcd9add6044e81d3619f48577ddf13a263d047a2" [[package]] name = "flate2" @@ -1821,12 +1634,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - [[package]] name = "foldhash" version = "0.2.0" @@ -1869,16 +1676,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fs2" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "fs_extra" version = "1.3.0" @@ -1910,19 +1707,6 @@ dependencies = [ "futures-sink", ] -[[package]] -name = "futures-concurrency" -version = "7.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175cd8cca9e1d45b87f18ffa75088f2099e3c4fe5e2f83e42de112560bea8ea6" -dependencies = [ - "fixedbitset", - "futures-core", - "futures-lite", - "pin-project", - "smallvec 1.16.1", -] - [[package]] name = "futures-core" version = "0.3.34" @@ -1982,12 +1766,6 @@ version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" -[[package]] -name = "futures-timer" -version = "3.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968" - [[package]] name = "futures-util" version = "0.3.34" @@ -2005,6 +1783,15 @@ dependencies = [ "slab", ] +[[package]] +name = "fuzzy-matcher" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" +dependencies = [ + "thread_local", +] + [[package]] name = "fxhash" version = "0.2.1" @@ -2040,6 +1827,21 @@ version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "312d2295c7302019c395cfb90dacd00a82a2eabd700429bba9c7a3f38dbbe11b" +[[package]] +name = "generator" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54ade96dc9003043bce7c035c85a9df5a858bfb2039c5a2e6fdf00f324f6c551" +dependencies = [ + "cc", + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", + "log", + "rustversion", + "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "windows-result 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -2051,29 +1853,34 @@ dependencies = [ ] [[package]] -name = "getrandom" -version = "0.2.17" +name = "gethostname" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" dependencies = [ - "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "js-sys", - "libc", - "wasi", - "wasm-bindgen", + "rustix", + "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "getopts" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df" +dependencies = [ + "unicode-width", ] [[package]] name = "getrandom" -version = "0.3.4" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "js-sys", "libc", - "r-efi 5.3.0", - "wasip2", + "wasi", "wasm-bindgen", ] @@ -2086,7 +1893,7 @@ dependencies = [ "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "js-sys", "libc", - "r-efi 6.0.0", + "r-efi", "rand_core 0.10.1", "wasm-bindgen", ] @@ -2102,1415 +1909,636 @@ dependencies = [ ] [[package]] -name = "gimli" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" - -[[package]] -name = "gix" -version = "0.79.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66adef5e4d836ad08bf424dce5e3eb18f51544eee702860419295120dd48811" -dependencies = [ - "gix-actor", - "gix-attributes", - "gix-command", - "gix-commitgraph", - "gix-config", - "gix-date", - "gix-diff", - "gix-discover", - "gix-error", - "gix-features", - "gix-filter", - "gix-fs", - "gix-glob", - "gix-hash", - "gix-hashtable", - "gix-ignore", - "gix-index", - "gix-lock", - "gix-object", - "gix-odb", - "gix-pack", - "gix-path", - "gix-pathspec", - "gix-protocol", - "gix-ref", - "gix-refspec", - "gix-revision", - "gix-revwalk", - "gix-sec", - "gix-shallow", - "gix-submodule", - "gix-tempfile", - "gix-trace", - "gix-traverse", - "gix-url", - "gix-utils", - "gix-validate", - "gix-worktree", - "smallvec 1.16.1", - "thiserror 2.0.20", +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", ] [[package]] -name = "gix-actor" -version = "0.39.0" +name = "gloo-timers" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c44f13049925e8dc3955c20ecec5391cedfb041e0952416b583ecc57bc68325" +checksum = "482ce8a491a501da4cd806bd190275363d674f2845005c6ddbd5d3e1dd54495d" dependencies = [ - "bstr", - "gix-date", - "gix-error", - "winnow 0.7.15", + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", ] [[package]] -name = "gix-attributes" -version = "0.30.1" +name = "gloo-utils" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e72da5a1c35c9a129be0c60ab9968779981ca50835dd98650ecd8b0ea4d721e" +checksum = "4202275d95a142fa209a1e35e91c250a710c5600731372cd3464a39ed01573d6" dependencies = [ - "bstr", - "gix-glob", - "gix-path", - "gix-quote", - "gix-trace", - "kstring", - "smallvec 1.16.1", - "thiserror 2.0.20", - "unicode-bom", + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", ] [[package]] -name = "gix-bitmap" -version = "0.2.16" +name = "growable-bloom-filter" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d982fc7ef0608e669851d0d2a6141dae74c60d5a27e8daa451f2a4857bbf41e2" +checksum = "d174ccb4ba660d431329e7f0797870d0a4281e36353ec4b4a3c5eab6c2cfb6f1" dependencies = [ - "thiserror 2.0.20", + "serde", + "serde_bytes", + "serde_derive", + "xxhash-rust", ] [[package]] -name = "gix-chunk" -version = "0.6.0" +name = "h2" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d14ee09ab454481a91fe969ca5afbd41c8a9b05680197b6554ebb69bdcf7d571" +checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16" dependencies = [ - "gix-error", + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.14.2", + "slab", + "tokio", + "tokio-util", + "tracing", ] [[package]] -name = "gix-command" -version = "0.7.1" +name = "half" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2962172c6f78731e2b7773bf762f7b8d1746a342a4c0a8914a612206e1295953" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ - "bstr", - "gix-path", - "gix-quote", - "gix-trace", - "shell-words", + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "crunchy 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "zerocopy 0.8.57", ] [[package]] -name = "gix-commitgraph" -version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9dc2a550978b510b4e58b0bf5a15481433c3b21981330f3898d93f25b07d9a5" +name = "hashbrown" +version = "0.16.1" dependencies = [ - "bstr", - "gix-chunk", - "gix-error", - "gix-hash", - "memmap2", + "foldhash 0.2.0", ] [[package]] -name = "gix-config" -version = "0.52.0" +name = "hashbrown" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db778c8703f3c7f687e03ce22ac8f6eac02adbdafae4cb19d541126fa012524f" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ - "bstr", - "gix-config-value", - "gix-features", - "gix-glob", - "gix-path", - "gix-ref", - "gix-sec", - "memchr 2.8.3", - "smallvec 1.16.1", - "thiserror 2.0.20", - "unicode-bom", - "winnow 0.7.15", + "allocator-api2", + "equivalent 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "foldhash 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "gix-config-value" -version = "0.17.2" +name = "hashbrown" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4378c53ec3db049919edf91ff76f56f28886a8b4b4a5a9dc633108d84afc3675" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" dependencies = [ - "bitflags 2.13.2", - "bstr", - "gix-path", - "libc", - "thiserror 2.0.20", + "foldhash 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "gix-date" -version = "0.14.0" +name = "hashlink" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e66a5117b22495fe7cb4b443777cf3f024a1b1db0009771db440fc8b38a0a6fd" +checksum = "a596f1b20ed2cc5ecac41a164aaebc7258057060f06c0cf7a2ba3991ee7990fb" dependencies = [ - "bstr", - "gix-error", - "itoa", - "jiff", - "smallvec 1.16.1", + "hashbrown 0.17.1", ] [[package]] -name = "gix-diff" -version = "0.59.0" +name = "heck" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebee256b21a7d46cec85ab84e824742142a23a21111556a5e50c15796110c6c6" -dependencies = [ - "bstr", - "gix-command", - "gix-filter", - "gix-fs", - "gix-hash", - "gix-object", - "gix-path", - "gix-tempfile", - "gix-trace", - "gix-traverse", - "gix-worktree", - "imara-diff", - "thiserror 2.0.20", -] +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] -name = "gix-discover" -version = "0.47.0" +name = "hermit-abi" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93103d88576e048a681ebee83e93162972cd4cbce1485a6137cfc98fc0ba152d" -dependencies = [ - "bstr", - "dunce", - "gix-fs", - "gix-path", - "gix-ref", - "gix-sec", - "thiserror 2.0.20", -] +checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" [[package]] -name = "gix-error" -version = "0.1.0" +name = "hex" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e37b10e97c822fc17550fc1a187283ad3ce79617e920bf179f301ee12abcbc" -dependencies = [ - "bstr", -] +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] -name = "gix-features" -version = "0.46.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "752493cd4b1d5eaaa0138a7493f65c96863fefa990fc021e0e519579e389ab20" -dependencies = [ - "crc32fast", - "crossbeam-channel", - "gix-path", - "gix-trace", - "gix-utils", - "libc", - "once_cell 1.21.4", - "parking_lot", - "prodash", - "thiserror 2.0.20", - "walkdir", - "zlib-rs", -] +name = "hexf-parse" +version = "0.2.1" [[package]] -name = "gix-filter" -version = "0.26.0" +name = "hilog-sys" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "094253121df9aa1cb46d1da0200dd63ebf16263a35f03295109978bf7ed2b483" -dependencies = [ - "bstr", - "encoding_rs", - "gix-attributes", - "gix-command", - "gix-hash", - "gix-object", - "gix-packetline", - "gix-path", - "gix-quote", - "gix-trace", - "gix-utils", - "smallvec 1.16.1", - "thiserror 2.0.20", -] +checksum = "f715f263b6e73aa2322b59057134b4daf56a765cbebc2f6b35e640ffded3361b" [[package]] -name = "gix-fs" -version = "0.19.2" +name = "hkdf" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a964b4aec683eb0bacb87533defa80805bb4768056371a47ab38b00a2d377b72" +checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018" dependencies = [ - "bstr", - "fastrand", - "gix-features", - "gix-path", - "gix-utils", - "thiserror 2.0.20", + "hmac", ] [[package]] -name = "gix-glob" -version = "0.24.0" +name = "hmac" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b03e6cd88cc0dc1eafa1fddac0fb719e4e74b6ea58dd016e71125fde4a326bee" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" dependencies = [ - "bitflags 2.13.2", - "bstr", - "gix-features", - "gix-path", + "digest 0.11.3", ] [[package]] -name = "gix-hash" -version = "0.22.1" +name = "hpke" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8ced05d2d7b13bff08b2f7eb4e47cfeaf00b974c2ddce08377c4fe1f706b3eb" +checksum = "a5324110b02044183df000f0bd7d2d7e61f1000631508627e77f63983b6fdffb" dependencies = [ - "faster-hex", - "gix-features", - "sha1-checked", - "thiserror 2.0.20", + "aead", + "chacha20poly1305", + "getrandom 0.4.3", + "hkdf", + "hybrid-array", + "rand_core 0.10.1", + "sha2 0.11.0", + "subtle", + "x25519-dalek", + "zeroize", ] [[package]] -name = "gix-hashtable" -version = "0.12.0" +name = "html5ever" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f1eecdd006390cbed81f105417dbf82a6fe40842022006550f2e32484101da" +checksum = "46a1761807faccc9a19e86944bbf40610014066306f96edcdedc2fb714bcb7b8" dependencies = [ - "gix-hash", - "hashbrown 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)", - "parking_lot", + "log", + "markup5ever", ] [[package]] -name = "gix-ignore" -version = "0.19.1" +name = "htmlize" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09f915dcf6911e3027537166d34e13f0fe101ed12225178d2ae29cd1272cff26" +checksum = "2e815d50d9e411ba2690d730e6ec139c08260dddb756df315dbd16d01a587226" dependencies = [ - "bstr", - "gix-glob", - "gix-path", - "gix-trace", - "unicode-bom", + "memchr 2.8.3", + "pastey", + "phf", + "phf_codegen", + "serde_json", ] [[package]] -name = "gix-index" -version = "0.47.0" +name = "http" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea450ed666a39676d7e9a41b899487d1645d88053bc8c8cecfca0cf96fcd4a03" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" dependencies = [ - "bitflags 2.13.2", - "bstr", - "filetime", - "fnv", - "gix-bitmap", - "gix-features", - "gix-fs", - "gix-hash", - "gix-lock", - "gix-object", - "gix-traverse", - "gix-utils", - "gix-validate", - "hashbrown 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes", "itoa", - "libc", - "memmap2", - "rustix", - "smallvec 1.16.1", - "thiserror 2.0.20", ] [[package]] -name = "gix-lock" -version = "21.0.2" +name = "http-body" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "054fbd0989700c69dc5aa80bc66944f05df1e15aa7391a9e42aca7366337905f" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" dependencies = [ - "gix-tempfile", - "gix-utils", - "thiserror 2.0.20", + "bytes", + "http", ] [[package]] -name = "gix-object" -version = "0.56.0" +name = "http-body-util" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "227e12fad42022ff08d6fbcc4bae34d6e2aa180576e9e1106d9a29a06798e750" -dependencies = [ - "bstr", - "gix-actor", - "gix-date", - "gix-features", - "gix-hash", - "gix-hashtable", - "gix-path", - "gix-utils", - "gix-validate", - "itoa", - "smallvec 1.16.1", - "thiserror 2.0.20", - "winnow 0.7.15", +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", ] [[package]] -name = "gix-odb" -version = "0.76.0" +name = "httparse" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e84f7e115b2b6615f0c5acddc269598ebc539c363fb276dbd242755a1dd7126c" -dependencies = [ - "arc-swap", - "gix-features", - "gix-fs", - "gix-hash", - "gix-hashtable", - "gix-object", - "gix-pack", - "gix-path", - "gix-quote", - "parking_lot", - "tempfile", - "thiserror 2.0.20", -] +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] -name = "gix-pack" -version = "0.66.0" +name = "httpdate" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc20b54d50f64fb02281f2a6c4a24bb9356befdf4535d5a68924575fd67cd5e" -dependencies = [ - "clru", - "gix-chunk", - "gix-error", - "gix-features", - "gix-hash", - "gix-hashtable", - "gix-object", - "gix-path", - "memmap2", - "smallvec 1.16.1", - "thiserror 2.0.20", - "uluru", -] +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] -name = "gix-packetline" -version = "0.21.5" +name = "hybrid-array" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b217dd0ee0c4021ecf169a4a519b1b4f80d15e3f3765f3dc466223dc0ac891d7" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" dependencies = [ - "bstr", - "faster-hex", - "gix-trace", - "thiserror 2.0.20", + "typenum", ] [[package]] -name = "gix-path" -version = "0.11.3" +name = "hyper" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8fd1fe596dc393b538e1d5492c5585971a9311475b3255f7b889023df208476" +checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" dependencies = [ - "bstr", - "gix-trace", - "gix-validate", - "thiserror 2.0.20", + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec 1.16.1", + "tokio", + "want", ] [[package]] -name = "gix-pathspec" -version = "0.15.1" +name = "hyper-rustls" +version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f4cc23f55ca7c190bf243f1a4e2139d4522022f724fb0dfc06c93f65a01ef6" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ - "bitflags 2.13.2", - "bstr", - "gix-attributes", - "gix-config-value", - "gix-glob", - "gix-path", - "thiserror 2.0.20", + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", ] [[package]] -name = "gix-protocol" -version = "0.57.0" +name = "hyper-util" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a13680c03e847d8f32a59cf1511dd05334d429da33f5af08891367680f15cbca" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ - "bstr", - "gix-date", - "gix-features", - "gix-hash", - "gix-ref", - "gix-shallow", - "gix-transport", - "gix-utils", - "maybe-async", - "thiserror 2.0.20", - "winnow 0.7.15", + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", ] [[package]] -name = "gix-quote" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96fc2ff2ec8cc0c92807f02eab1f00eb02619fc2810d13dc42679492fcc36757" -dependencies = [ - "bstr", - "gix-utils", - "thiserror 2.0.20", -] +name = "i_float" +version = "3.0.0" [[package]] -name = "gix-ref" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92fd2e86d65efe972a5226f303ed72cfb49a8ad03740e5cc2b27c07970a5d78" -dependencies = [ - "gix-actor", - "gix-features", - "gix-fs", - "gix-hash", - "gix-lock", - "gix-object", - "gix-path", - "gix-tempfile", - "gix-utils", - "gix-validate", - "memmap2", - "thiserror 2.0.20", - "winnow 0.7.15", -] +name = "i_key_sort" +version = "0.11.0" [[package]] -name = "gix-refspec" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40adba15f8099159d37d0a21e1cfb6602c2e49b6c05f6f8a57a47d0fb21611af" +name = "i_overlay" +version = "7.0.3" dependencies = [ - "bstr", - "gix-error", - "gix-glob", - "gix-hash", - "gix-revision", - "gix-validate", - "smallvec 1.16.1", - "thiserror 2.0.20", + "i_float", + "i_key_sort", + "i_shape", + "i_tree", ] [[package]] -name = "gix-revision" -version = "0.41.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75ef94c9d76de0765e429ae22a01c512c0d50459269195a90ea11099a5fc752" +name = "i_shape" +version = "3.0.0" dependencies = [ - "bitflags 2.13.2", - "bstr", - "gix-commitgraph", - "gix-date", - "gix-error", - "gix-hash", - "gix-hashtable", - "gix-object", - "gix-revwalk", - "gix-trace", + "i_float", ] [[package]] -name = "gix-revwalk" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07efcad1d064abdac3e79dfc6e23e05accb579559d015e944c9dcf64be95eb49" -dependencies = [ - "gix-commitgraph", - "gix-date", - "gix-error", - "gix-hash", - "gix-hashtable", - "gix-object", - "smallvec 1.16.1", - "thiserror 2.0.20", -] +name = "i_tree" +version = "0.19.0" [[package]] -name = "gix-sec" -version = "0.13.3" +name = "iana-time-zone" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "283f4a746c9bde8550be63e6f961ff4651f412ca12666e8f5615f39464960ab9" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ - "bitflags 2.13.2", - "gix-path", - "libc", - "windows-sys 0.61.2", + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.62.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "gix-shallow" -version = "0.8.1" +name = "iana-time-zone-haiku" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "189386b5da5285216cc0ede89eff5a943d5261fc794241ee6ec5360b77df15ad" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ - "bstr", - "gix-hash", - "gix-lock", - "thiserror 2.0.20", + "cc", ] [[package]] -name = "gix-submodule" -version = "0.26.0" +name = "icu_collections" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac5d38afa855046b7b9e2b85f8cdf7fbf2e449ed061dc81fa7a757abaa38bfac" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" dependencies = [ - "bstr", - "gix-config", - "gix-path", - "gix-pathspec", - "gix-refspec", - "gix-url", - "thiserror 2.0.20", + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", ] [[package]] -name = "gix-tempfile" -version = "21.0.2" +name = "icu_locale_core" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22227f6b203f511ff451c33c89899e87e4f571fc596b06f68e6e613a6508528" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" dependencies = [ - "dashmap", - "gix-fs", - "libc", - "parking_lot", - "tempfile", + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", ] [[package]] -name = "gix-trace" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be3eb81d9dc914335923e50d52829c551feefd6a72d176c4130c546b67a60814" - -[[package]] -name = "gix-transport" -version = "0.54.0" +name = "icu_normalizer" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9561a98f4f1cada03b565121c475c95d060998082bdb1277044fa6e11fe2aa17" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" dependencies = [ - "bstr", - "gix-command", - "gix-features", - "gix-packetline", - "gix-quote", - "gix-sec", - "gix-url", - "thiserror 2.0.20", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec 1.16.1", + "zerovec", ] [[package]] -name = "gix-traverse" -version = "0.53.0" +name = "icu_normalizer_data" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcdd399f8527f643f8d4fd8de6ec3b6e2c3c826b758b68e90f28275af2e7b33a" -dependencies = [ - "bitflags 2.13.2", - "gix-commitgraph", - "gix-date", - "gix-hash", - "gix-hashtable", - "gix-object", - "gix-revwalk", - "smallvec 1.16.1", - "thiserror 2.0.20", -] +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" [[package]] -name = "gix-url" -version = "0.35.3" +name = "icu_properties" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a61ead12e33fa52ae92b207ee27554f646a8e7a3dad8b78da1582ec91eda0a6" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" dependencies = [ - "bstr", - "gix-path", - "percent-encoding", - "thiserror 2.0.20", + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", ] [[package]] -name = "gix-utils" -version = "0.3.6" +name = "icu_properties_data" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0da1c46491b49458a446cc76f0085860f8164c2290742e0aa8c653ce67240a97" -dependencies = [ - "fastrand", - "getrandom 0.4.3", - "unicode-normalization", -] +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" [[package]] -name = "gix-validate" -version = "0.11.4" +name = "icu_provider" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dae8780f63ed8a803b8bdabbd7aa5f5c5d74592c8b50eed875c1bb4f6545a6a" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" dependencies = [ - "bstr", + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", ] [[package]] -name = "gix-worktree" -version = "0.48.0" +name = "idna" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93381c5cd37fa208e3f454433425985ea02725369fb33a79ff4ecf7c279f2f98" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ - "bstr", - "gix-attributes", - "gix-fs", - "gix-glob", - "gix-hash", - "gix-ignore", - "gix-index", - "gix-object", - "gix-path", - "gix-validate", + "idna_adapter", + "smallvec 1.16.1", + "utf8_iter", ] [[package]] -name = "glob" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" - -[[package]] -name = "globset" -version = "0.4.20" +name = "idna_adapter" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07c34a9410465b45bd9787443bc7370f37735bad04b0f0cd57ff1a3186c98988" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ - "aho-corasick", - "bstr", - "log", - "regex-automata", - "regex-syntax", + "icu_normalizer", + "icu_properties", ] [[package]] -name = "h2" -version = "0.4.19" +name = "image" +version = "0.25.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap 2.14.2", - "slab", - "tokio", - "tokio-util", - "tracing", + "bytemuck 1.25.2", + "byteorder-lite", + "moxcms", + "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "png 0.18.1", + "tiff", + "zune-core", + "zune-jpeg", ] [[package]] -name = "hash32" -version = "0.3.1" +name = "image-webp" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" dependencies = [ - "byteorder 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder-lite", + "quick-error", ] [[package]] -name = "hashbrown" -version = "0.12.3" +name = "imagesize" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +checksum = "09e54e57b4c48b40f7aec75635392b12b3421fa26fe8b4332e63138ed278459c" [[package]] -name = "hashbrown" -version = "0.14.5" +name = "imbl" +version = "7.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "46bad832b9b463ed9398b8506488cc2e3b897a9d44f13af115f382aac71f4fec" dependencies = [ - "ahash", + "archery", + "equivalent 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "imbl-sized-chunks", + "rand_core 0.9.5", + "rand_xoshiro", + "serde_core", + "version_check", + "wide", ] [[package]] -name = "hashbrown" -version = "0.15.5" +name = "imbl-sized-chunks" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "allocator-api2", - "equivalent 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "foldhash 0.1.5", -] +checksum = "2a0813be332553f857953298749fa19549e8b61b80589757c29b4e2a804fa9c6" [[package]] -name = "hashbrown" -version = "0.16.1" +name = "impartial-ord" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc680e03d1a08bdc1c01b60e0c39723430ced63d8cabd93cee0449b6ba916a25" dependencies = [ - "foldhash 0.2.0", + "proc-macro2", + "quote", + "syn 3.0.5", ] [[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +name = "indexmap" +version = "2.13.0" dependencies = [ - "allocator-api2", - "equivalent 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "foldhash 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "equivalent 1.0.2", + "hashbrown 0.16.1", ] [[package]] -name = "hashbrown" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" - -[[package]] -name = "hashlink" -version = "0.9.1" +name = "indexmap" +version = "2.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" dependencies = [ - "hashbrown 0.14.5", + "equivalent 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "hashbrown 0.17.1", + "serde", + "serde_core", ] [[package]] -name = "heapless" -version = "0.8.0" +name = "inout" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" dependencies = [ - "hash32", - "stable_deref_trait", + "block-padding", + "hybrid-array", ] [[package]] -name = "heck" -version = "0.5.0" +name = "ipnet" +version = "2.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" [[package]] -name = "hermit-abi" -version = "0.5.3" +name = "is_terminal_polyfill" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] -name = "hex" -version = "0.4.3" +name = "itertools" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] [[package]] -name = "hexf-parse" -version = "0.2.1" +name = "itertools" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b4baf93f58d4425749ca49a51c50ebab072c5df6994d08fed93541c331481dc" +dependencies = [ + "either", +] [[package]] -name = "hilog-sys" -version = "0.1.8" +name = "itoa" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f715f263b6e73aa2322b59057134b4daf56a765cbebc2f6b35e640ffded3361b" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] -name = "hmac" -version = "0.12.1" +name = "jetscii" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest 0.10.7", -] +checksum = "47f142fe24a9c9944451e8349de0a56af5f3e7226dc46f3ed4d4ecc0b85af75e" [[package]] -name = "hnsw_rs" -version = "0.3.4" +name = "jni" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a5258f079b97bf2e8311ff9579e903c899dcbac0d9a138d62e9a066778bd07" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" dependencies = [ - "anndists", - "anyhow", - "bincode", + "cesu8", "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "cpu-time", - "env_logger", - "hashbrown 0.15.5", - "indexmap 2.14.2", - "lazy_static", + "combine", + "jni-sys 0.3.1", "log", - "mmap-rs", - "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus", - "parking_lot", - "rand 0.9.5", - "rayon", - "serde", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", ] [[package]] -name = "home" -version = "0.5.12" +name = "jni" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "hound" -version = "3.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f" - -[[package]] -name = "htmd" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5a1c7113c831fec68cbd79cd8bf281a84e5b6943f51473dc266b0b88a6a017e" -dependencies = [ - "html5ever", - "markup5ever_rcdom", - "phf 0.13.1", -] - -[[package]] -name = "html5ever" -version = "0.38.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2" -dependencies = [ - "log", - "markup5ever", -] - -[[package]] -name = "http" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" -dependencies = [ - "bytes", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" -dependencies = [ - "bytes", - "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 = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hybrid-array" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" -dependencies = [ - "typenum", -] - -[[package]] -name = "hyper" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" -dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "smallvec 1.16.1", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" -dependencies = [ - "http", - "hyper", - "hyper-util", - "rustls", - "rustls-native-certs", - "tokio", - "tokio-rustls", - "tower-service", - "webpki-roots", -] - -[[package]] -name = "hyper-util" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-channel", - "futures-util", - "http", - "http-body", - "hyper", - "ipnet", - "libc", - "percent-encoding", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", -] - -[[package]] -name = "i_float" -version = "3.0.0" - -[[package]] -name = "i_key_sort" -version = "0.11.0" - -[[package]] -name = "i_overlay" -version = "7.0.3" -dependencies = [ - "i_float", - "i_key_sort", - "i_shape", - "i_tree", -] - -[[package]] -name = "i_shape" -version = "3.0.0" -dependencies = [ - "i_float", -] - -[[package]] -name = "i_tree" -version = "0.19.0" - -[[package]] -name = "iana-time-zone" -version = "0.1.65" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core 0.62.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "icu_collections" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" -dependencies = [ - "displaydoc", - "potential_utf", - "utf8_iter", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec 1.16.1", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" - -[[package]] -name = "icu_properties" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" - -[[package]] -name = "icu_provider" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" -dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec 1.16.1", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "ignore" -version = "0.4.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b69833ed729dc5aa7d19541d96d6cf8e9137194207a04916d658e43168402f" -dependencies = [ - "crossbeam-deque", - "globset", - "log", - "memchr 2.8.3", - "regex-automata", - "same-file", - "walkdir", - "winapi-util", -] - -[[package]] -name = "image" -version = "0.25.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" -dependencies = [ - "bytemuck 1.25.2", - "byteorder-lite", - "moxcms", - "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", - "png 0.18.1", - "zune-core", - "zune-jpeg", -] - -[[package]] -name = "image-webp" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" -dependencies = [ - "byteorder-lite", - "quick-error", -] - -[[package]] -name = "imagesize" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09e54e57b4c48b40f7aec75635392b12b3421fa26fe8b4332e63138ed278459c" - -[[package]] -name = "imara-diff" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17d34b7d42178945f775e84bc4c36dde7c1c6cdfea656d3354d009056f2bb3d2" -dependencies = [ - "hashbrown 0.15.5", -] - -[[package]] -name = "indenter" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" - -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] - -[[package]] -name = "indexmap" -version = "2.13.0" -dependencies = [ - "equivalent 1.0.2", - "hashbrown 0.16.1", -] - -[[package]] -name = "indexmap" -version = "2.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" -dependencies = [ - "equivalent 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "hashbrown 0.17.1", - "serde", - "serde_core", -] - -[[package]] -name = "inout" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" -dependencies = [ - "block-padding", - "generic-array", -] - -[[package]] -name = "ipnet" -version = "2.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" - -[[package]] -name = "is-docker" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" -dependencies = [ - "once_cell 1.21.4", -] - -[[package]] -name = "is-wsl" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" -dependencies = [ - "is-docker", - "once_cell 1.21.4", -] - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" - -[[package]] -name = "itoa" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" - -[[package]] -name = "jiff" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1baf72f08796de0260609515130699b890ac25f30e610ad894bc5856cafdb" -dependencies = [ - "defmt", - "jiff-core", - "jiff-static", - "jiff-tzdb-platform", - "log", - "portable-atomic", - "portable-atomic-util", - "serde_core", - "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "jiff-core" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e52fe76043ccecc9005d2305ebaadf7d7fc0cc89ca6baa10a94d6bc68c7128c" -dependencies = [ - "defmt", - "log", -] - -[[package]] -name = "jiff-static" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "378268a1116ad67ae6228701118ac9f491d78fda38a40a1f1a9e1348de6f7212" -dependencies = [ - "jiff-core", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "jiff-tzdb" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "142bd39932ad231f10513df9ab62661fead8719872150b7ad02a2df79f4e141e" - -[[package]] -name = "jiff-tzdb-platform" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" -dependencies = [ - "jiff-tzdb", -] - -[[package]] -name = "jni" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" -dependencies = [ - "cesu8", - "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "combine", - "jni-sys 0.3.1", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", -] - -[[package]] -name = "jni" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" dependencies = [ "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "combine", @@ -3586,42 +2614,41 @@ dependencies = [ ] [[package]] -name = "jsonwebtoken" -version = "9.3.1" +name = "js_int" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" +checksum = "d937f95470b270ce8b8950207715d71aa8e153c0d44c6684d59397ed4949160a" dependencies = [ - "base64 0.22.1", - "js-sys", - "pem", - "ring", "serde", - "serde_json", - "simple_asn1", ] [[package]] -name = "keyring" -version = "3.6.3" +name = "js_option" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c" +checksum = "c7dd3e281add16813cf673bf74a32249b0aa0d1c8117519a17b3ada5e8552b3c" dependencies = [ - "byteorder 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "linux-keyutils", - "log", - "security-framework 2.11.1", - "security-framework 3.7.0", - "windows-sys 0.60.2", - "zeroize", + "serde_core", ] [[package]] -name = "kstring" -version = "2.0.4" +name = "json5" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "733a844dbd6fef128e98cb4487b887cb55454d92cd9994b1bafe004fabbe670c" +dependencies = [ + "serde", + "ucd-trie", +] + +[[package]] +name = "konst" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b609e7ca5ea38f093c20a4a102335b247221c9643b7a6bc3510f196f99499a9e" +checksum = "f660d5f887e3562f9ab6f4a14988795b694099d66b4f5dedc02d197ba9becb1d" dependencies = [ - "static_assertions", + "const_panic", + "typewit", ] [[package]] @@ -3631,43 +2658,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b60dfc32f652b926df6192e55525b16d186c69d47876c3ead4da5cc9f8450e2" dependencies = [ "arrayvec 0.7.8", - "euclid 0.22.14", + "euclid", "polycool", "smallvec 1.16.1", ] [[package]] -name = "lazy_static" -version = "1.5.0" +name = "language-tags" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" [[package]] -name = "lettre" -version = "0.11.23" +name = "lazy_static" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2c646bd5cc763b1087b15493e29a64be6147ba8f19342004fa52048ee596eae" -dependencies = [ - "async-trait", - "base64 0.23.1", - "email-encoding", - "email_address", - "fastrand", - "futures-io", - "futures-util", - "httpdate", - "idna", - "mime", - "nom", - "percent-encoding", - "quoted_printable", - "rustls", - "socket2", - "tokio", - "tokio-rustls", - "url", - "webpki-roots", -] +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" @@ -3710,9 +2716,9 @@ dependencies = [ [[package]] name = "libsqlite3-sys" -version = "0.30.1" +version = "0.38.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +checksum = "f1d20bef17f513b9b3004532233187769cd072d790971f4e4da0e346eb6401e8" dependencies = [ "cc", "pkg-config 0.3.34", @@ -3720,23 +2726,12 @@ dependencies = [ ] [[package]] -name = "libyml" -version = "0.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3302702afa434ffa30847a83305f0a69d6abd74293b6554c18ec85c7ef30c980" -dependencies = [ - "anyhow", - "version_check", -] - -[[package]] -name = "linux-keyutils" -version = "0.2.5" +name = "linkify" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83270a18e9f90d0707c41e9f35efada77b64c0e6f3f1810e71c8368a864d5590" +checksum = "f1dfa36d52c581e9ec783a7ce2a5e0143da6237be5811a0b3153fedfdbe9f780" dependencies = [ - "bitflags 2.13.2", - "libc", + "memchr 2.8.3", ] [[package]] @@ -3749,101 +2744,145 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" name = "litemap" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "generator", + "scoped-tls 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "lru" +version = "0.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" +dependencies = [ + "hashbrown 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "lru-slab" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4050469837a6ff301cd14c1f8f24f88549e6d548f24f64e2148eb0f72cebc51f" [[package]] -name = "lock_api" -version = "0.4.14" +name = "macroific" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +checksum = "89f276537b4b8f981bf1c13d79470980f71134b7bdcc5e6e911e910e556b0285" dependencies = [ - "scopeguard", + "macroific_attr_parse 2.0.0", + "macroific_core 2.0.0", + "macroific_macro 2.0.0", ] [[package]] -name = "log" -version = "0.4.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" - -[[package]] -name = "lopdf" -version = "0.36.0" +name = "macroific" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59fa2559e99ba0f26a12458aabc754432c805bbb8cba516c427825a997af1fb7" +checksum = "cc8ab95c98294764c0f1eacebdb5b027849201b71fbc383871fe991a1de7eb83" dependencies = [ - "aes", - "bitflags 2.13.2", - "cbc", - "ecb", - "encoding_rs", - "flate2", - "indexmap 2.14.2", - "itoa", - "log", - "md-5", - "nom", - "nom_locate", - "rand 0.9.5", - "rangemap", - "sha2 0.10.9", - "stringprep", - "thiserror 2.0.20", - "weezl 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "macroific_attr_parse 3.1.0", + "macroific_core 3.1.0", + "macroific_macro 3.1.0", ] [[package]] -name = "lru" -version = "0.16.4" +name = "macroific_attr_parse" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" +checksum = "ad4023761b45fcd36abed8fb7ae6a80456b0a38102d55e89a57d9a594a236be9" dependencies = [ - "hashbrown 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "sealed 0.6.0", + "syn 2.0.119", ] [[package]] -name = "lru-slab" -version = "0.1.3" +name = "macroific_attr_parse" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4050469837a6ff301cd14c1f8f24f88549e6d548f24f64e2148eb0f72cebc51f" +checksum = "bf9b7bdb92825e6a507392b12f0570ade17480be20f4ca951b9071b40deba1d1" +dependencies = [ + "proc-macro2", + "quote", + "sealed 0.7.0", + "syn 3.0.5", +] [[package]] -name = "lzma-rs" -version = "0.3.0" +name = "macroific_core" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e" +checksum = "d0a7594d3c14916fa55bef7e9d18c5daa9ed410dd37504251e4b75bbdeec33e3" dependencies = [ - "byteorder 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "crc", + "proc-macro2", + "quote", + "sealed 0.6.0", + "syn 2.0.119", ] [[package]] -name = "lzma-sys" -version = "0.1.20" +name = "macroific_core" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" +checksum = "edc880da2bc00d6d87f748805e242d670e248d1b9b6fcef3190503210b6cf056" dependencies = [ - "cc", - "libc", - "pkg-config 0.3.34", + "proc-macro2", + "quote", + "sealed 0.7.0", + "syn 3.0.5", ] [[package]] -name = "mach2" -version = "0.4.3" +name = "macroific_macro" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +checksum = "4da6f2ed796261b0a74e2b52b42c693bb6dee1effba3a482c49592659f824b3b" dependencies = [ - "libc", + "macroific_attr_parse 2.0.0", + "macroific_core 2.0.0", + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] -name = "makepad-ai" -version = "1.0.0" +name = "macroific_macro" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201faf668c264432727fb64e44407bcfcf1f5f8153bf4ec248eb6bbe4a39b144" dependencies = [ - "makepad-live-id", - "makepad-micro-serde", - "makepad-widgets", + "macroific_attr_parse 3.1.0", + "macroific_core 3.1.0", + "proc-macro2", + "quote", + "syn 3.0.5", ] [[package]] @@ -3917,17 +2956,6 @@ dependencies = [ "makepad-micro-proc-macro", ] -[[package]] -name = "makepad-diagram-kit" -version = "0.1.0" -source = "git+https://github.com/Project-Robius-China/makepad-diagram-kit.git?branch=main#0536492116e5db6f5db88bb8b3e33bb9580abfea" -dependencies = [ - "makepad-widgets", - "serde", - "serde_json", - "thiserror 1.0.69", -] - [[package]] name = "makepad-draw" version = "2.0.0" @@ -3946,6 +2974,7 @@ dependencies = [ "makepad-zune-qoi", "rustybuzz 0.18.0", "sdfer", + "serde", "ttf-parser 0.24.1", "unicode-bidi 0.3.18", "unicode-linebreak", @@ -3997,7 +3026,7 @@ name = "makepad-half" version = "2.7.1" dependencies = [ "cfg-if 1.0.4", - "crunchy", + "crunchy 0.2.4", "num-traits 0.2.19", "zerocopy 0.8.39", ] @@ -4037,6 +3066,7 @@ name = "makepad-live-id" version = "1.0.0" dependencies = [ "makepad-live-id-macros", + "serde", ] [[package]] @@ -4053,6 +3083,38 @@ dependencies = [ "makepad-filesystem-watcher", ] +[[package]] +name = "makepad-markdown" +version = "0.1.0" +dependencies = [ + "blake3", + "comrak", + "emojis", + "html5ever", + "pulldown-cmark 0.13.4", + "unicode-segmentation 1.13.3", + "url", +] + +[[package]] +name = "makepad-markdown-widgets" +version = "0.1.0" +dependencies = [ + "blake3", + "core-foundation", + "image", + "makepad-latex-math", + "makepad-markdown", + "makepad-widgets", + "mermaid-rs-renderer", + "resvg", + "roxmltree 0.21.1", + "syntect", + "tiny-skia", + "ttf-parser 0.24.1", + "unicode-segmentation 1.12.0", +] + [[package]] name = "makepad-math" version = "1.0.0" @@ -4105,13 +3167,6 @@ dependencies = [ name = "makepad-objc-sys" version = "1.0.0" -[[package]] -name = "makepad-pdf-parse" -version = "1.0.0" -dependencies = [ - "makepad-zune-inflate", -] - [[package]] name = "makepad-photos" version = "0.1.0" @@ -4282,9 +3337,9 @@ dependencies = [ "makepad-html", "makepad-latex-math", "makepad-mbtile-reader", - "makepad-pdf-parse", "octoscript-node", - "pulldown-cmark", + "pulldown-cmark 0.12.2", + "serde", "serde_json", "ttf-parser 0.24.1", "unicode-segmentation 1.12.0", @@ -4334,80 +3389,380 @@ dependencies = [ ] [[package]] -name = "makepad-zune-png" -version = "0.5.2" +name = "makepad-zune-png" +version = "0.5.2" +dependencies = [ + "makepad-fast-inflate", + "makepad-zune-core", + "makepad-zune-inflate", +] + +[[package]] +name = "makepad-zune-qoi" +version = "0.5.2" +dependencies = [ + "makepad-zune-core", +] + +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + +[[package]] +name = "markup5ever" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7122d987ec5f704ee56f6e5b41a7d93722e9aae27ae07cafa4036c4d3f9757de" +dependencies = [ + "log", + "tendril", + "web_atoms", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "matrix-pickle" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d65d46b7379dd0afa4a42f9b2269821d31afdee0111b5e0d74e3bee03553a0" +dependencies = [ + "matrix-pickle-derive", + "thiserror 2.0.20", +] + +[[package]] +name = "matrix-pickle-derive" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "414b5e4c34009f2bc3fe35dd018f25755ca38858096574841c7332f99e2c7e77" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "matrix-sdk" +version = "0.18.0" +source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +dependencies = [ + "anymap2", + "as_variant", + "async-channel", + "async-stream", + "async-trait", + "axum", + "backon", + "bytes", + "bytesize", + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "event-listener", + "eyeball", + "eyeball-im", + "futures-core", + "futures-util", + "gloo-timers 0.4.0", + "hex", + "http", + "imbl", + "indexmap 2.14.2", + "itertools 0.15.0", + "js_int", + "language-tags", + "matrix-sdk-base", + "matrix-sdk-common", + "matrix-sdk-indexeddb", + "matrix-sdk-sqlite", + "mime", + "mime2ext", + "oauth2", + "oauth2-reqwest", + "percent-encoding", + "pin-project-lite", + "rand 0.10.2", + "reqwest", + "ruma", + "serde", + "serde_html_form", + "serde_json", + "sha2 0.11.0", + "tempfile", + "thiserror 2.0.20", + "tokio", + "tokio-stream", + "tokio-util", + "tower", + "tracing", + "url", + "urlencoding", + "vodozemac", + "zeroize", +] + +[[package]] +name = "matrix-sdk-base" +version = "0.18.0" +source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +dependencies = [ + "as_variant", + "async-trait", + "bitflags 2.13.2", + "decancer", + "eyeball", + "eyeball-im", + "futures-util", + "growable-bloom-filter", + "matrix-sdk-common", + "matrix-sdk-crypto", + "matrix-sdk-store-encryption", + "regex", + "ruma", + "serde", + "serde_bytes", + "serde_json", + "thiserror 2.0.20", + "tokio", + "tracing", + "unicode-normalization", +] + +[[package]] +name = "matrix-sdk-common" +version = "0.18.0" +source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +dependencies = [ + "eyeball-im", + "futures-core", + "futures-executor", + "futures-util", + "gloo-timers 0.4.0", + "imbl", + "ruma", + "serde", + "serde_json", + "thiserror 2.0.20", + "tokio", + "tracing", + "tracing-subscriber", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "matrix-sdk-crypto" +version = "0.18.0" +source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +dependencies = [ + "aes", + "as_variant", + "async-trait", + "bs58", + "byteorder 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "ctr", + "eyeball", + "futures-core", + "futures-util", + "hkdf", + "hmac", + "itertools 0.15.0", + "js_option", + "matrix-sdk-common", + "pbkdf2", + "rand 0.10.2", + "rmp-serde", + "ruma", + "serde", + "serde_json", + "sha2 0.11.0", + "subtle", + "thiserror 2.0.20", + "time", + "tokio", + "tokio-stream", + "tracing", + "ulid", + "url", + "vodozemac", + "zeroize", +] + +[[package]] +name = "matrix-sdk-indexeddb" +version = "0.18.0" +source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" dependencies = [ - "makepad-fast-inflate", - "makepad-zune-core", - "makepad-zune-inflate", + "async-trait", + "base64 0.23.1", + "futures-util", + "getrandom 0.4.3", + "gloo-utils", + "hkdf", + "js-sys", + "matrix-sdk-base", + "matrix-sdk-crypto", + "matrix-sdk-store-encryption", + "matrix_indexed_db_futures", + "rmp-serde", + "ruma", + "serde", + "serde-wasm-bindgen", + "serde_bytes", + "serde_json", + "sha2 0.11.0", + "thiserror 2.0.20", + "tokio", + "tracing", + "uuid", + "wasm-bindgen", + "web-sys", + "zeroize", ] [[package]] -name = "makepad-zune-qoi" -version = "0.5.2" +name = "matrix-sdk-sqlite" +version = "0.18.0" +source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" dependencies = [ - "makepad-zune-core", + "as_variant", + "async-trait", + "deadpool", + "deadpool-sync", + "itertools 0.15.0", + "matrix-sdk-base", + "matrix-sdk-crypto", + "matrix-sdk-store-encryption", + "num_cpus", + "rmp-serde", + "ruma", + "rusqlite", + "serde", + "serde_json", + "serde_path_to_error", + "thiserror 2.0.20", + "tokio", + "tracing", + "vodozemac", + "zeroize", ] [[package]] -name = "markup5ever" -version = "0.38.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862" +name = "matrix-sdk-store-encryption" +version = "0.18.0" +source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" dependencies = [ - "log", - "tendril", - "web_atoms", + "base64 0.23.1", + "blake3", + "chacha20poly1305", + "getrandom 0.4.3", + "hkdf", + "hmac", + "pbkdf2", + "rand 0.10.2", + "rmp-serde", + "serde", + "serde_bytes", + "serde_json", + "sha2 0.11.0", + "thiserror 2.0.20", + "zeroize", ] [[package]] -name = "markup5ever_rcdom" -version = "0.38.0+unofficial" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "333171ccdf66e915257740d44e38ea5b1b19ce7b45d33cc35cb6f118fbd981ff" +name = "matrix-sdk-ui" +version = "0.18.0" +source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" dependencies = [ - "html5ever", - "markup5ever", - "tendril", - "xml5ever", + "as_variant", + "async-rx", + "async-stream", + "async_cell", + "bitflags 2.13.2", + "chrono", + "emojis", + "eyeball", + "eyeball-im", + "eyeball-im-util", + "futures-core", + "futures-util", + "fuzzy-matcher", + "growable-bloom-filter", + "imbl", + "indexmap 2.14.2", + "itertools 0.15.0", + "matrix-sdk", + "matrix-sdk-base", + "matrix-sdk-common", + "mime", + "pin-project-lite", + "ruma", + "serde", + "serde_json", + "thiserror 2.0.20", + "tokio", + "tokio-stream", + "tracing", + "unicode-normalization", + "unicode-segmentation 1.13.3", ] [[package]] -name = "matchers" -version = "0.2.0" +name = "matrix_indexed_db_futures" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +checksum = "245ff6a224b4df7b0c90dda2dd5a6eb46112708d49e8bdd8b007fccb09fea8e4" dependencies = [ - "regex-automata", + "accessory", + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "delegate-display", + "derive_more 2.1.1", + "fancy_constructor", + "futures-core", + "js-sys", + "matrix_indexed_db_futures_macros_internal", + "sealed 0.6.0", + "serde", + "serde-wasm-bindgen", + "smallvec 1.16.1", + "thiserror 2.0.20", + "tokio", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm_evt_listener", + "web-sys", + "web-time", ] [[package]] -name = "matchit" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" - -[[package]] -name = "maybe-async" -version = "0.2.11" +name = "matrix_indexed_db_futures_macros_internal" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "746873a384ad60adc5db74471dfaba74bd278afbdcfd81db93fafcdfc8b5ca0c" +checksum = "b428aee5c0fe9e5babd29e99d289b7f64718c444989aac0442d1fd6d3e3f66d1" dependencies = [ + "macroific 2.0.0", "proc-macro2", "quote", "syn 2.0.119", ] -[[package]] -name = "md-5" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" -dependencies = [ - "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.10.7", -] - [[package]] name = "memchr" version = "2.7.6" @@ -4428,50 +3783,29 @@ dependencies = [ ] [[package]] -name = "metrics" -version = "0.24.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89550ee9f79e88fef3119de263694973a8adb26c21d75322164fb8c493039fe2" -dependencies = [ - "portable-atomic", - "rapidhash", -] - -[[package]] -name = "metrics-exporter-prometheus" -version = "0.16.2" +name = "memoffset" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd7399781913e5393588a8d8c6a2867bf85fb38eaf2502fdce465aad2dc6f034" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ - "base64 0.22.1", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "indexmap 2.14.2", - "ipnet", - "metrics", - "metrics-util", - "quanta", - "thiserror 1.0.69", - "tokio", - "tracing", + "autocfg", ] [[package]] -name = "metrics-util" -version = "0.19.1" +name = "mermaid-rs-renderer" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8496cc523d1f94c1385dd8f0f0c2c480b2b8aeccb5b7e4485ad6365523ae376" +checksum = "6cb5b469222ce6b9896edf0a969cf68464f5a028ef394fd6643f00bd943df117" dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", - "hashbrown 0.15.5", - "metrics", - "quanta", - "rand 0.9.5", - "rand_xoshiro", - "sketches-ddsketch", + "anyhow", + "fontdb", + "json5", + "once_cell 1.21.4", + "regex", + "serde", + "serde_json", + "thiserror 2.0.20", + "ttf-parser 0.25.1", ] [[package]] @@ -4480,6 +3814,12 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime2ext" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf6f36070878c42c5233846cd3de24cf9016828fd47bc22957a687298bb21fc" + [[package]] name = "mime_guess" version = "2.0.5" @@ -4521,23 +3861,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "mmap-rs" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ecce9d566cb9234ae3db9e249c8b55665feaaf32b0859ff1e27e310d2beb3d8" -dependencies = [ - "bitflags 2.13.2", - "combine", - "libc", - "mach2", - "nix 0.30.1", - "sysctl", - "thiserror 2.0.20", - "widestring", - "windows 0.48.0", -] - [[package]] name = "moxcms" version = "0.8.1" @@ -4548,56 +3871,12 @@ dependencies = [ "pxfm", ] -[[package]] -name = "multer" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" -dependencies = [ - "bytes", - "encoding_rs", - "futures-util", - "http", - "httparse", - "memchr 2.8.3", - "mime", - "spin", - "version_check", -] - -[[package]] -name = "multiversion" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ca4bea16ffc3f443cf7d866912118196bfef4c6a1556ca00f9f9b00bb43f7c" -dependencies = [ - "multiversion-macros", -] - -[[package]] -name = "multiversion-macros" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d416831a7317ef4b08bee00b69cbbb9c8763da7959a7026244d6266869f9c83" -dependencies = [ - "proc-macro2", - "quote", - "rustversion", - "syn 3.0.5", -] - -[[package]] -name = "multiversion_no_op" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d" - [[package]] name = "naga" version = "27.0.3" dependencies = [ "arrayvec 0.7.6", - "bit-set", + "bit-set 0.8.0", "bitflags 2.10.0", "cfg-if 1.0.4", "cfg_aliases 0.2.1", @@ -4619,135 +3898,61 @@ name = "napi-derive-backend-ohos" version = "0.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6b18d697bedddd2d4c9f8f76b49fe65bd81ed1c55a7eec21ba40c176c236ddc" -dependencies = [ - "convert_case 0.6.0", - "once_cell 1.21.4", - "proc-macro2", - "quote", - "regex", - "syn 2.0.119", -] - -[[package]] -name = "napi-derive-ohos" -version = "0.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8462d74a2d6c7a671bd610f99f9ba34c739aadd2da4d8dd9f109a7e666cc2ad2" -dependencies = [ - "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "convert_case 0.6.0", - "napi-derive-backend-ohos", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "napi-ohos" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad5a3bbb2ae61f345b8c11776f2e79fc2bb71d1901af9a5f81f03c9238a05d86" -dependencies = [ - "bitflags 2.13.2", - "ctor", - "napi-sys-ohos", - "once_cell 1.21.4", -] - -[[package]] -name = "napi-sys-ohos" -version = "0.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f101404db01422d034db5afa63eefff6d9c8f66c0894278bc456b4c30954e166" -dependencies = [ - "libloading 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "ndk-context" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" - -[[package]] -name = "new_debug_unreachable" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" - -[[package]] -name = "nibble_vec" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" -dependencies = [ - "smallvec 1.16.1", -] - -[[package]] -name = "nix" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" -dependencies = [ - "bitflags 2.13.2", - "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg_aliases 0.2.2", - "libc", +dependencies = [ + "convert_case", + "once_cell 1.21.4", + "proc-macro2", + "quote", + "regex", + "syn 2.0.119", ] [[package]] -name = "nix" -version = "0.30.1" +name = "napi-derive-ohos" +version = "0.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +checksum = "8462d74a2d6c7a671bd610f99f9ba34c739aadd2da4d8dd9f109a7e666cc2ad2" dependencies = [ - "bitflags 2.13.2", "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg_aliases 0.2.2", - "libc", + "convert_case", + "napi-derive-backend-ohos", + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] -name = "nix" -version = "0.31.3" +name = "napi-ohos" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +checksum = "ad5a3bbb2ae61f345b8c11776f2e79fc2bb71d1901af9a5f81f03c9238a05d86" dependencies = [ "bitflags 2.13.2", - "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg_aliases 0.2.2", - "libc", + "ctor", + "napi-sys-ohos", + "once_cell 1.21.4", ] [[package]] -name = "nom" -version = "8.0.0" +name = "napi-sys-ohos" +version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +checksum = "f101404db01422d034db5afa63eefff6d9c8f66c0894278bc456b4c30954e166" dependencies = [ - "memchr 2.8.3", + "libloading 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "nom_locate" -version = "5.0.0" +name = "ndk-context" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b577e2d69827c4740cba2b52efaad1c4cc7c73042860b199710b3575c68438d" -dependencies = [ - "bytecount", - "memchr 2.8.3", - "nom", -] +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" [[package]] -name = "ntapi" -version = "0.4.3" +name = "new_debug_unreachable" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" -dependencies = [ - "winapi", -] +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] name = "nu-ansi-term" @@ -4758,31 +3963,12 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "num-bigint" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" -dependencies = [ - "num-integer", - "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "num-conv" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" -[[package]] -name = "num-integer" -version = "0.1.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" -dependencies = [ - "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -4817,7 +4003,6 @@ dependencies = [ "getrandom 0.2.17", "http", "rand 0.8.8", - "reqwest 0.12.28", "serde", "serde_json", "serde_path_to_error", @@ -4826,6 +4011,16 @@ dependencies = [ "url", ] +[[package]] +name = "oauth2-reqwest" +version = "0.1.0-alpha.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234fb5c965bbce983ee5de636a7a51d6a3223da8067ea02f9ab2d2d78ac08be2" +dependencies = [ + "oauth2", + "reqwest", +] + [[package]] name = "objc2" version = "0.6.4" @@ -4840,522 +4035,123 @@ name = "objc2-app-kit" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" -dependencies = [ - "bitflags 2.13.2", - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" -dependencies = [ - "bitflags 2.13.2", - "dispatch2", - "objc2", -] - -[[package]] -name = "objc2-encode" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" - -[[package]] -name = "objc2-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" -dependencies = [ - "bitflags 2.13.2", - "objc2", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-ui-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" dependencies = [ "bitflags 2.13.2", "block2", - "objc2", - "objc2-foundation", -] - -[[package]] -name = "object" -version = "0.37.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" -dependencies = [ - "memchr 2.8.3", -] - -[[package]] -name = "octos-agent" -version = "2.0.3-rc.11" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18#a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18" -dependencies = [ - "async-trait", - "base64 0.22.1", - "chromiumoxide", - "chrono", - "dirs", - "dunce", - "eyre", - "flate2", - "futures", - "gix", - "glob", - "globset", - "hound", - "htmd", - "ignore", - "keyring", - "lettre", - "libc", - "metrics", - "oauth2", - "octos-bus", - "octos-core 2.0.3-rc.11 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18)", - "octos-llm", - "octos-memory", - "octos-plugin", - "pdf-extract", - "redb", - "regex", - "reqwest 0.12.28", - "reqwest 0.13.5", - "rmcp", - "rustix", - "serde", - "serde_json", - "sha2 0.10.9", - "shlex 1.3.0", - "similar", - "tar", - "tempfile", - "tiny_http", - "tokio", - "tokio-util", - "toml", - "tracing", - "tree-sitter", - "tree-sitter-javascript", - "tree-sitter-python", - "tree-sitter-rust", - "tree-sitter-typescript", - "url", - "webbrowser", - "which 7.0.3", -] - -[[package]] -name = "octos-app" -version = "0.1.0" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18#a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18" -dependencies = [ - "blake3", - "chrono", - "keyring", - "makepad-ai", - "makepad-code-editor", - "makepad-diagram-kit", - "makepad-widgets", - "octos-app-render", - "octos-app-store", - "octos-app-transport", - "octos-core 2.0.3-rc.11 (git+https://github.com/octos-org/octos.git?rev=deb433e9a452c03d8fe7f4f6a26b42785c47ced7)", - "octoscript-node", - "octoscript-ui-l0", - "reqwest 0.12.28", - "robius-open", - "serde", - "serde_json", - "streaming-markdown-kit", - "tokio", - "url", -] - -[[package]] -name = "octos-app-render" -version = "0.1.0" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18#a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18" -dependencies = [ - "streaming-markdown-kit", -] - -[[package]] -name = "octos-app-store" -version = "0.1.0" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18#a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18" -dependencies = [ - "chrono", - "keyring", - "log", - "octos-core 2.0.3-rc.11 (git+https://github.com/octos-org/octos.git?rev=deb433e9a452c03d8fe7f4f6a26b42785c47ced7)", - "serde", - "serde_json", - "uuid", -] - -[[package]] -name = "octos-app-transport" -version = "0.1.0" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18#a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18" -dependencies = [ - "android_logger", - "chrono", - "futures-util", - "log", - "octos-cli", - "octos-core 2.0.3-rc.11 (git+https://github.com/octos-org/octos.git?rev=deb433e9a452c03d8fe7f4f6a26b42785c47ced7)", - "reqwest 0.12.28", - "serde", - "serde_json", - "tokio", - "tokio-tungstenite 0.24.0", - "tracing-subscriber", - "url", - "uuid", -] - -[[package]] -name = "octos-bus" -version = "2.0.3-rc.11" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18#a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18" -dependencies = [ - "aes", - "async-trait", - "axum", - "base64 0.22.1", - "cbc", - "chrono", - "chrono-tz", - "cron", - "eyre", - "futures", - "lru", - "metrics", - "octos-core 2.0.3-rc.11 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18)", - "reqwest 0.12.28", - "serde", - "serde_json", - "serde_yml", - "sha2 0.10.9", - "subtle", - "tokio", - "tracing", - "uuid", -] - -[[package]] -name = "octos-cli" -version = "2.0.3-rc.11" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18#a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18" -dependencies = [ - "agent-client-protocol", - "argon2", - "async-trait", - "axum", - "base64 0.22.1", - "chacha20poly1305", - "chrono", - "clap", - "clap_complete", - "color-eyre", - "colored", - "constant_time_eq 0.3.1", - "dirs", - "eyre", - "flate2", - "fs2", - "futures", - "getrandom 0.2.17", - "iana-time-zone", - "image", - "keyring", - "lettre", - "libc", - "metrics", - "metrics-exporter-prometheus", - "octos-agent", - "octos-bus", - "octos-core 2.0.3-rc.11 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18)", - "octos-diagnostics", - "octos-fleet", - "octos-fleet-worker", - "octos-llm", - "octos-memory", - "octos-pipeline", - "octos-plugin", - "octos-services", - "octos-store", - "octos-swarm", - "octos-workflows", - "open", - "percent-encoding", - "qrcode", - "quick-xml", - "rand 0.8.8", - "redb", - "regex", - "reqwest 0.12.28", - "rpassword", - "rust-embed", - "rustix", - "rustls", - "rustls-native-certs", - "rustyline", - "serde", - "serde_json", - "sha2 0.10.9", - "subtle", - "sysinfo", - "tar", - "tempfile", - "tokio", - "tokio-tungstenite 0.26.2", - "tokio-util", - "toml", - "tower-http", - "tracing", - "tracing-appender", - "tracing-subscriber", - "url", - "uuid", - "zip", -] - -[[package]] -name = "octos-core" -version = "2.0.3-rc.11" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18#a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18" -dependencies = [ - "chrono", - "eyre", - "serde", - "serde_json", - "sha2 0.10.9", - "tracing", - "uuid", -] - -[[package]] -name = "octos-core" -version = "2.0.3-rc.11" -source = "git+https://github.com/octos-org/octos.git?rev=deb433e9a452c03d8fe7f4f6a26b42785c47ced7#deb433e9a452c03d8fe7f4f6a26b42785c47ced7" -dependencies = [ - "chrono", - "eyre", - "serde", - "serde_json", - "sha2 0.10.9", - "tracing", - "uuid", -] - -[[package]] -name = "octos-diagnostics" -version = "2.0.3-rc.11" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18#a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18" -dependencies = [ - "eyre", - "octos-core 2.0.3-rc.11 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18)", - "reqwest 0.12.28", - "serde", - "serde_json", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", ] [[package]] -name = "octos-fleet" -version = "2.0.3-rc.11" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18#a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18" +name = "objc2-authentication-services" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6d6f7dab884a28adaec1012eb3889257a49cc145724e35f93ece2d209f8b25" dependencies = [ - "eyre", - "octos-core 2.0.3-rc.11 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18)", - "redb", - "rusqlite", - "serde", - "serde_json", - "tokio", - "tracing", - "uuid", + "block2", + "objc2", + "objc2-app-kit", + "objc2-foundation", ] [[package]] -name = "octos-fleet-worker" -version = "2.0.3-rc.11" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18#a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18" +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "async-trait", - "eyre", - "octos-agent", - "octos-core 2.0.3-rc.11 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18)", - "octos-fleet", - "octos-llm", - "octos-memory", - "serde_json", - "tokio", - "tracing", + "bitflags 2.13.2", + "dispatch2", + "objc2", ] [[package]] -name = "octos-llm" -version = "2.0.3-rc.11" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18#a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18" +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ - "async-trait", - "base64 0.22.1", - "chrono", - "eyre", - "futures", - "jsonwebtoken", - "metrics", - "octos-core 2.0.3-rc.11 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18)", - "redb", - "reqwest 0.12.28", - "secrecy", - "serde", - "serde_json", - "sha2 0.10.9", - "tokio", - "tracing", + "bitflags 2.13.2", + "dispatch2", + "objc2", + "objc2-core-foundation", + "objc2-io-surface", ] [[package]] -name = "octos-memory" -version = "2.0.3-rc.11" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18#a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18" +name = "objc2-core-location" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" dependencies = [ - "bincode", - "chrono", - "eyre", - "hnsw_rs", - "octos-core 2.0.3-rc.11 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18)", - "redb", - "regex", - "serde", - "serde_json", - "tokio", - "tracing", - "uuid", + "objc2", + "objc2-foundation", ] [[package]] -name = "octos-pipeline" -version = "2.0.3-rc.11" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18#a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18" -dependencies = [ - "async-trait", - "chrono", - "eyre", - "futures", - "glob", - "octos-agent", - "octos-core 2.0.3-rc.11 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18)", - "octos-llm", - "octos-memory", - "octos-plugin", - "regex", - "serde", - "serde_json", - "tokio", - "tracing", -] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" [[package]] -name = "octos-plugin" -version = "2.0.3-rc.11" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18#a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18" +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "eyre", - "metrics", - "serde", - "serde_json", - "tokio", - "tracing", - "which 7.0.3", + "bitflags 2.13.2", + "block2", + "objc2", + "objc2-core-foundation", ] [[package]] -name = "octos-services" -version = "2.0.3-rc.11" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18#a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18" +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" dependencies = [ - "chrono", - "dirs", - "eyre", - "flate2", - "futures", - "octos-bus", - "octos-core 2.0.3-rc.11 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18)", - "octos-llm", - "reqwest 0.12.28", - "serde", - "serde_json", - "tar", - "tokio", - "tracing", + "bitflags 2.13.2", + "objc2", + "objc2-core-foundation", ] [[package]] -name = "octos-store" -version = "2.0.3-rc.11" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18#a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18" +name = "objc2-photos-ui" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdddde9dfb986546746e3115021a85c4a83e1c5e9037b3c865a688fe9bf94ebb" dependencies = [ - "base64 0.22.1", - "chrono", - "constant_time_eq 0.3.1", - "eyre", - "getrandom 0.2.17", - "octos-core 2.0.3-rc.11 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18)", - "redb", - "serde", - "serde_json", - "sha2 0.10.9", - "tokio", - "tracing", - "uuid", + "bitflags 2.13.2", + "objc2", + "objc2-foundation", ] [[package]] -name = "octos-swarm" -version = "2.0.3-rc.11" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18#a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18" +name = "objc2-ui-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" dependencies = [ - "async-trait", - "chrono", - "eyre", - "metrics", - "octos-agent", - "redb", - "serde", - "serde_json", - "sha2 0.10.9", - "tokio", - "tracing", - "uuid", + "bitflags 2.13.2", + "block2", + "objc2", + "objc2-core-foundation", + "objc2-foundation", + "objc2-uniform-type-identifiers", ] [[package]] -name = "octos-workflows" -version = "2.0.3-rc.11" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18#a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18" +name = "objc2-uniform-type-identifiers" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7902ac02859fc1f7045f8b598c63f1ae0cc7efeaa06a9bc9f3d9a3c955974fa4" dependencies = [ - "chrono", - "eyre", - "octos-agent", - "octos-core 2.0.3-rc.11 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18)", - "octos-pipeline", - "serde", - "serde_json", - "tokio", - "tracing", + "objc2", + "objc2-foundation", ] [[package]] @@ -5385,18 +4181,8 @@ dependencies = [ "makepad-widgets", "makepad-wm-api", "makepad-wm-theme", - "octosense-appcard", "octosense-reference", -] - -[[package]] -name = "octosense-appcard" -version = "0.1.0" -dependencies = [ - "makepad-app-module", - "makepad-widgets", - "octos-app", - "serde_json", + "rinx", ] [[package]] @@ -5429,22 +4215,6 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" -[[package]] -name = "opaque-debug" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" - -[[package]] -name = "open" -version = "5.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa576c76302b7b808eecc68061e67336c47833ef9d22caa74dda10fa9675eebc" -dependencies = [ - "is-wsl", - "libc", -] - [[package]] name = "openssl-probe" version = "0.2.1" @@ -5458,10 +4228,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] -name = "owo-colors" -version = "4.4.0" +name = "ordered-stream" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c45bb4a6ae1280ec0803b1ef9d3455eb50f01efbbe1447ab020f1d54fba9d8" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] [[package]] name = "parking" @@ -5492,58 +4266,19 @@ dependencies = [ "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "password-hash" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" -dependencies = [ - "base64ct", - "rand_core 0.6.4", - "subtle", -] - [[package]] name = "pastey" -version = "0.2.3" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" [[package]] name = "pbkdf2" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" -dependencies = [ - "digest 0.10.7", - "hmac", -] - -[[package]] -name = "pdf-extract" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c2f44c6c642e359e2fe7f662bf5438db3811b6b4be60afc6de04b619ce51e1a" -dependencies = [ - "adobe-cmap-parser", - "cff-parser", - "encoding_rs", - "euclid 0.20.14", - "log", - "lopdf", - "postscript", - "type1-encoding-parser", - "unicode-normalization", -] - -[[package]] -name = "pem" -version = "3.0.6" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +checksum = "112d82ceb8c5bf524d9af484d4e4970c9fd5a0cc15ba14ad93dccd28873b0629" dependencies = [ - "base64 0.22.1", - "serde_core", + "digest 0.11.3", ] [[package]] @@ -5552,23 +4287,13 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" -[[package]] -name = "phf" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7" -dependencies = [ - "phf_shared 0.12.1", -] - [[package]] name = "phf" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" dependencies = [ - "phf_macros", - "phf_shared 0.13.1", + "phf_shared", "serde", ] @@ -5579,7 +4304,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" dependencies = [ "phf_generator", - "phf_shared 0.13.1", + "phf_shared", ] [[package]] @@ -5589,29 +4314,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" dependencies = [ "fastrand", - "phf_shared 0.13.1", -] - -[[package]] -name = "phf_macros" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" -dependencies = [ - "phf_generator", - "phf_shared 0.13.1", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "phf_shared" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981" -dependencies = [ - "siphasher", + "phf_shared", ] [[package]] @@ -5629,26 +4332,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" -[[package]] -name = "pin-project" -version = "1.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "pin-project-lite" version = "0.2.17" @@ -5716,14 +4399,19 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "pollster" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" + [[package]] name = "poly1305" -version = "0.8.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +checksum = "6e2d0073b297041425c7c3df6eb4792d598a15323fe63346852b092eca02904c" dependencies = [ - "cpufeatures 0.2.17", - "opaque-debug", + "cpufeatures 0.3.1", "universal-hash", ] @@ -5736,33 +4424,6 @@ dependencies = [ "arrayvec 0.7.8", ] -[[package]] -name = "pom" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60f6ce597ecdcc9a098e7fddacb1065093a3d66446fa16c675e7e71d1b5c28e6" - -[[package]] -name = "portable-atomic" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" - -[[package]] -name = "portable-atomic-util" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" -dependencies = [ - "portable-atomic", -] - -[[package]] -name = "postscript" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78451badbdaebaf17f053fd9152b3ffb33b516104eacb45e7864aaa9c712f306" - [[package]] name = "potential_utf" version = "0.1.6" @@ -5793,6 +4454,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.107" @@ -5803,26 +4473,26 @@ dependencies = [ ] [[package]] -name = "process-wrap" -version = "9.1.0" +name = "prost" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e842efad9119158434d193c6682e2ebee4b44d6ad801d7b349623b3f57cdf55" +checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1" dependencies = [ - "futures", - "indexmap 2.14.2", - "nix 0.31.3", - "tokio", - "tracing", - "windows 0.62.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes", + "prost-derive", ] [[package]] -name = "prodash" -version = "31.0.0" +name = "prost-derive" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "962200e2d7d551451297d9fdce85138374019ada198e30ea9ede38034e27604c" +checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" dependencies = [ - "parking_lot", + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] @@ -5835,31 +4505,29 @@ dependencies = [ ] [[package]] -name = "pxfm" -version = "0.1.30" +name = "pulldown-cmark" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea" +checksum = "e9f068eba8e7071c5f9511831b44f32c740d5adf574e990f946ddb53db2f314e" +dependencies = [ + "bitflags 2.13.2", + "getopts", + "memchr 2.8.3", + "pulldown-cmark-escape", + "unicase 2.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] -name = "qrcode" -version = "0.14.1" +name = "pulldown-cmark-escape" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d68782463e408eb1e668cf6152704bd856c78c5b6417adaee3203d8f4c1fc9ec" +checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae" [[package]] -name = "quanta" -version = "0.12.6" +name = "pxfm" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7" -dependencies = [ - "crossbeam-utils", - "libc", - "once_cell 1.21.4", - "raw-cpuid", - "wasi", - "web-sys", - "winapi", -] +checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea" [[package]] name = "quick-error" @@ -5867,15 +4535,6 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" -[[package]] -name = "quick-xml" -version = "0.37.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" -dependencies = [ - "memchr 2.8.3", -] - [[package]] name = "quinn" version = "0.11.11" @@ -5942,34 +4601,12 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "quoted_printable" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478e0585659a122aa407eb7e3c0e1fa51b1d8a870038bd29f0cf4a8551eea972" - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - [[package]] name = "r-efi" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" -[[package]] -name = "radix_trie" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" -dependencies = [ - "endian-type", - "nibble_vec", -] - [[package]] name = "rand" version = "0.8.8" @@ -5977,27 +4614,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" dependencies = [ "libc", - "rand_chacha 0.3.1", + "rand_chacha", "rand_core 0.6.4", ] -[[package]] -name = "rand" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.5", -] - [[package]] name = "rand" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" dependencies = [ - "chacha20 0.10.2", + "chacha20", "getrandom 0.4.3", "rand_core 0.10.1", ] @@ -6012,16 +4639,6 @@ dependencies = [ "rand_core 0.6.4", ] -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.5", -] - [[package]] name = "rand_core" version = "0.6.4" @@ -6036,9 +4653,6 @@ name = "rand_core" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom 0.3.4", -] [[package]] name = "rand_core" @@ -6071,50 +4685,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a611d15b50743feb4c76b7d03edcb0e64f399c26961e4efe6975bc398be6aa3d" [[package]] -name = "rapidhash" -version = "4.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5da7e78a036ce858e8d55b7e7dc8ba3a88b78350fd2155d3591bbd966b58589e" -dependencies = [ - "rustversion", -] - -[[package]] -name = "raw-cpuid" -version = "11.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" -dependencies = [ - "bitflags 2.13.2", -] - -[[package]] -name = "rayon" -version = "1.12.0" +name = "raw-window-handle" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" -dependencies = [ - "either", - "rayon-core", -] +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" [[package]] -name = "rayon-core" -version = "1.13.0" +name = "readlock" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] +checksum = "6da6f291b23556edd9edaf655a0be2ad8ef8002ff5f1bca62b264f3f58b53f34" [[package]] -name = "redb" -version = "2.6.3" +name = "readlock-tokio" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eca1e9d98d5a7e9002d0013e18d5a9b000aee942eb134883a82f06ebffb6c01" +checksum = "fc7e264f9ec4f3d112e8e2f214e8e7cb5cf3b83278f3570b7e00bfe13d3bd8ff" dependencies = [ - "libc", + "tokio", ] [[package]] @@ -6128,33 +4716,12 @@ dependencies = [ [[package]] name = "redox_users" -version = "0.4.6" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +checksum = "60dc65c0ff1a7ae1294b0c67b9f14baf70b644404010370171787bfac1038fc0" dependencies = [ - "getrandom 0.2.17", "libredox", - "thiserror 1.0.69", -] - -[[package]] -name = "ref-cast" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.5", + "thiserror 2.0.20", ] [[package]] @@ -6186,49 +4753,6 @@ version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" -[[package]] -name = "reqwest" -version = "0.12.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "js-sys", - "log", - "mime_guess", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-rustls", - "tokio-util", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams 0.4.2", - "web-sys", - "webpki-roots", -] - [[package]] name = "reqwest" version = "0.13.5" @@ -6239,6 +4763,7 @@ dependencies = [ "bytes", "futures-core", "futures-util", + "h2", "http", "http-body", "http-body-util", @@ -6253,8 +4778,6 @@ dependencies = [ "rustls", "rustls-pki-types", "rustls-platform-verifier", - "serde", - "serde_json", "sync_wrapper", "tokio", "tokio-rustls", @@ -6265,7 +4788,7 @@ dependencies = [ "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams 0.5.0", + "wasm-streams", "web-sys", ] @@ -6286,6 +4809,30 @@ dependencies = [ "zune-jpeg", ] +[[package]] +name = "rfd" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20dafead71c16a34e1ff357ddefc8afc11e7d51d6d2b9fbd07eaa48e3e540220" +dependencies = [ + "block2", + "dispatch2", + "js-sys", + "libc", + "log", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "percent-encoding", + "pollster", + "raw-window-handle", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-sys 0.61.2", +] + [[package]] name = "rgb" version = "0.8.53" @@ -6310,67 +4857,156 @@ dependencies = [ ] [[package]] -name = "rmcp" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1f571c72940a19d9532fe52dbea8bc9912bf1d766c2970bb824056b86f3f59" +name = "rinx" +version = "1.0.0-beta.1" dependencies = [ - "async-trait", - "base64 0.22.1", - "bytes", + "anyhow", + "arboard", + "article-core", + "article-makepad", + "bitflags 2.13.2", + "blake3", + "blurhash", + "bytesize", "chrono", + "clap", + "crossbeam-channel", + "crossbeam-queue", + "dispatch2", + "eyeball", + "eyeball-im", "futures", - "http", - "http-body", - "http-body-util", - "oauth2", - "pastey", - "pin-project-lite", - "process-wrap", - "rand 0.10.2", - "reqwest 0.13.5", - "rmcp-macros", - "schemars 1.2.2", + "futures-util", + "hashbrown 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)", + "htmlize", + "image", + "imbl", + "indexmap 2.14.2", + "libc", + "linkify", + "makepad-app-module", + "makepad-code-editor", + "makepad-widgets", + "matrix-sdk", + "matrix-sdk-base", + "matrix-sdk-crypto", + "matrix-sdk-ui", + "mime", + "mime_guess", + "num_cpus", + "pulldown-cmark 0.13.4", + "rand 0.8.8", + "rangemap", + "rfd", + "robius-directories", + "robius-file-picker", + "robius-location", + "robius-open", + "robius-share", + "robius-speech", + "robius-use-makepad", + "robius-web-auth-session", + "ruma", + "sanitize-filename", "serde", "serde_json", - "sse-stream", "thiserror 2.0.20", "tokio", - "tokio-stream", - "tokio-util", - "tower-service", - "tracing", + "toml", + "tracing-subscriber", + "unicode-segmentation 1.13.3", "url", - "uuid", + "webpki-root-certs", + "winresource", ] [[package]] -name = "rmcp-macros" -version = "1.8.0" +name = "rmp" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aad0035b69380782d78ea95b508327e6deaa2235909053e596eea8f27b5e1d5" +checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c" dependencies = [ - "darling 0.23.0", - "proc-macro2", - "quote", - "serde_json", - "syn 2.0.119", + "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rmp-serde" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155" +dependencies = [ + "rmp", + "serde", +] + +[[package]] +name = "robius-android-env" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "087fcb3061ccc432658a605cb868edd44e0efb08e7a159b486f02804a7616bef" +dependencies = [ + "jni 0.21.1", + "makepad-android-state", + "ndk-context", +] + +[[package]] +name = "robius-common" +version = "0.3.1" +source = "git+https://github.com/project-robius/robius#cca3cc3dc99d8fcb6c69a127cd28d47ba8f3e3eb" + +[[package]] +name = "robius-directories" +version = "6.0.1" +source = "git+https://github.com/project-robius/robius#cca3cc3dc99d8fcb6c69a127cd28d47ba8f3e3eb" +dependencies = [ + "dirs-sys", + "jni 0.21.1", + "robius-android-env", +] + +[[package]] +name = "robius-file-picker" +version = "0.3.1" +source = "git+https://github.com/project-robius/robius#cca3cc3dc99d8fcb6c69a127cd28d47ba8f3e3eb" +dependencies = [ + "android-build", + "block2", + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "dispatch2", + "jni 0.21.1", + "mime_guess", + "objc2", + "objc2-foundation", + "objc2-photos-ui", + "objc2-ui-kit", + "objc2-uniform-type-identifiers", + "rfd", + "robius-android-env", + "robius-common", + "robius-directories", ] [[package]] -name = "robius-android-env" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "087fcb3061ccc432658a605cb868edd44e0efb08e7a159b486f02804a7616bef" +name = "robius-location" +version = "0.3.1" +source = "git+https://github.com/project-robius/robius#cca3cc3dc99d8fcb6c69a127cd28d47ba8f3e3eb" dependencies = [ + "android-build", + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "jni 0.21.1", - "ndk-context", + "objc2", + "objc2-core-location", + "objc2-foundation", + "robius-android-env", + "windows 0.56.0", + "zbus", ] [[package]] name = "robius-open" version = "0.3.1" -source = "git+https://github.com/project-robius/robius#71966dc2194307e27d3f9eedc9138614624b4265" +source = "git+https://github.com/project-robius/robius#cca3cc3dc99d8fcb6c69a127cd28d47ba8f3e3eb" dependencies = [ "block2", "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -6384,6 +5020,58 @@ dependencies = [ "windows 0.56.0", ] +[[package]] +name = "robius-share" +version = "0.3.1" +source = "git+https://github.com/project-robius/robius#cca3cc3dc99d8fcb6c69a127cd28d47ba8f3e3eb" +dependencies = [ + "android-build", + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "dispatch2", + "jni 0.21.1", + "libc", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "robius-android-env", + "robius-common", + "windows 0.56.0", +] + +[[package]] +name = "robius-speech" +version = "0.3.1" +source = "git+https://github.com/project-robius/robius?rev=166991b1b18188ee2d8441f7118721cee2a0c6eb#166991b1b18188ee2d8441f7118721cee2a0c6eb" +dependencies = [ + "jni 0.21.1", + "robius-android-env", + "windows 0.61.3", +] + +[[package]] +name = "robius-use-makepad" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "947db6944188cbab806d657f70ec5c55ec9f169d3cab83ac2d04801bd25a2826" +dependencies = [ + "robius-android-env", +] + +[[package]] +name = "robius-web-auth-session" +version = "0.3.1" +source = "git+https://github.com/project-robius/robius#cca3cc3dc99d8fcb6c69a127cd28d47ba8f3e3eb" +dependencies = [ + "block2", + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "dispatch2", + "objc2", + "objc2-authentication-services", + "objc2-foundation", + "objc2-ui-kit", +] + [[package]] name = "roxmltree" version = "0.20.0" @@ -6399,17 +5087,6 @@ dependencies = [ "memchr 2.8.3", ] -[[package]] -name = "rpassword" -version = "7.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da316a15f47e3d053de9cb2c439650bd8fa4aaeb9365f2e5f27f492ff73c196" -dependencies = [ - "libc", - "rtoolbox", - "windows-sys 0.61.2", -] - [[package]] name = "rqrr" version = "0.10.1" @@ -6422,69 +5099,147 @@ dependencies = [ ] [[package]] -name = "rtoolbox" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a1efe12a1469752d0e6ff5ebec0b6ef4924cc5c4c71046b0ec730040535819d" +name = "ruma" +version = "0.16.0" +source = "git+https://github.com/project-robius/ruma.git?branch=tsp#7a912c8579871e72468ee5a4deecc16615e73099" dependencies = [ - "libc", - "windows-sys 0.61.2", + "assign", + "js_int", + "js_option", + "ruma-client-api", + "ruma-common", + "ruma-events", + "ruma-html", + "web-time", ] [[package]] -name = "rusqlite" -version = "0.32.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e" +name = "ruma-client-api" +version = "0.24.0" +source = "git+https://github.com/project-robius/ruma.git?branch=tsp#7a912c8579871e72468ee5a4deecc16615e73099" dependencies = [ - "bitflags 2.13.2", - "fallible-iterator", - "fallible-streaming-iterator", - "hashlink", - "libsqlite3-sys", - "smallvec 1.16.1", + "as_variant", + "assign", + "bytes", + "http", + "js_int", + "js_option", + "maplit", + "ruma-common", + "ruma-events", + "serde", + "serde_html_form", + "serde_json", + "thiserror 2.0.20", + "url", + "web-time", ] [[package]] -name = "rust-embed" -version = "8.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9e7760e252aaba7b09f4be00e36476cf585bdb68a53552ac954cdf504ab4bc9" +name = "ruma-common" +version = "0.19.0" +source = "git+https://github.com/project-robius/ruma.git?branch=tsp#7a912c8579871e72468ee5a4deecc16615e73099" dependencies = [ - "rust-embed-impl", - "rust-embed-utils", - "walkdir", + "as_variant", + "base64 0.22.1", + "bytes", + "date_header", + "form_urlencoded", + "getrandom 0.4.3", + "http", + "indexmap 2.14.2", + "js_int", + "konst", + "percent-encoding", + "rand 0.10.2", + "regex", + "ruma-identifiers-validation", + "ruma-macros", + "serde", + "serde_html_form", + "serde_json", + "thiserror 2.0.20", + "time", + "tracing", + "url", + "uuid", + "web-time", + "wildmatch", + "zeroize", ] [[package]] -name = "rust-embed-impl" -version = "8.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bcfc4d6f53af43755f7a723e4b6b8794fcce052a178dd8c6c1dadc5f5343097" +name = "ruma-events" +version = "0.34.0" +source = "git+https://github.com/project-robius/ruma.git?branch=tsp#7a912c8579871e72468ee5a4deecc16615e73099" dependencies = [ - "mime_guess", - "proc-macro2", - "quote", - "rust-embed-utils", - "syn 2.0.119", - "walkdir", + "as_variant", + "indexmap 2.14.2", + "js_int", + "js_option", + "pulldown-cmark 0.13.4", + "ruma-common", + "ruma-html", + "ruma-macros", + "serde", + "serde_json", + "thiserror 2.0.20", + "tracing", + "web-time", + "wildmatch", + "zeroize", ] [[package]] -name = "rust-embed-utils" -version = "8.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ffa149f6aa81b58a5b3011d01a857c4ed12c7a732d2c51947a4c7c692185f0" +name = "ruma-html" +version = "0.8.0" +source = "git+https://github.com/project-robius/ruma.git?branch=tsp#7a912c8579871e72468ee5a4deecc16615e73099" dependencies = [ - "sha2 0.11.0", - "walkdir", + "as_variant", + "html5ever", + "ruma-common", + "tracing", + "wildmatch", +] + +[[package]] +name = "ruma-identifiers-validation" +version = "0.12.1" +source = "git+https://github.com/project-robius/ruma.git?branch=tsp#7a912c8579871e72468ee5a4deecc16615e73099" +dependencies = [ + "js_int", + "thiserror 2.0.20", +] + +[[package]] +name = "ruma-macros" +version = "0.19.0" +source = "git+https://github.com/project-robius/ruma.git?branch=tsp#7a912c8579871e72468ee5a4deecc16615e73099" +dependencies = [ + "as_variant", + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-crate", + "proc-macro2", + "quote", + "ruma-identifiers-validation", + "serde", + "syn 3.0.5", + "toml", ] [[package]] -name = "rustc-demangle" -version = "0.1.28" +name = "rusqlite" +version = "0.40.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb" +checksum = "23f2a97da3e3873c73cb2a2e71b35c40ff95e0b1eefa8d72d8499a6928c3b5b3" +dependencies = [ + "bitflags 2.13.2", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec 1.16.1", +] [[package]] name = "rustc-hash" @@ -6525,9 +5280,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6725596c3f2c3a0aef021139e145d4eafe314a6623e4680ca83852b2c67ab2ba" dependencies = [ "aws-lc-rs", - "log", "once_cell 1.21.4", - "ring", "rustls-pki-types", "rustls-webpki", "subtle", @@ -6543,7 +5296,7 @@ dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework 3.7.0", + "security-framework", ] [[package]] @@ -6562,7 +5315,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" dependencies = [ - "core-foundation 0.10.1", + "core-foundation", "core-foundation-sys", "jni 0.22.4", "log", @@ -6571,7 +5324,7 @@ dependencies = [ "rustls-native-certs", "rustls-platform-verifier-android", "rustls-webpki", - "security-framework 3.7.0", + "security-framework", "security-framework-sys", "webpki-root-certs", "windows-sys 0.61.2", @@ -6601,58 +5354,6 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" -[[package]] -name = "rusty-mermaid" -version = "0.2.0" -source = "git+https://github.com/ZhangHanDong/rusty-mermaid.git#68a429b0472d57bce992c7bdddb59aa0c967e2f8" -dependencies = [ - "rusty-mermaid-core", - "rusty-mermaid-diagrams", - "rusty-mermaid-svg", -] - -[[package]] -name = "rusty-mermaid-core" -version = "0.2.0" -source = "git+https://github.com/ZhangHanDong/rusty-mermaid.git#68a429b0472d57bce992c7bdddb59aa0c967e2f8" - -[[package]] -name = "rusty-mermaid-dagre" -version = "0.2.0" -source = "git+https://github.com/ZhangHanDong/rusty-mermaid.git#68a429b0472d57bce992c7bdddb59aa0c967e2f8" -dependencies = [ - "rusty-mermaid-core", - "rusty-mermaid-graph", -] - -[[package]] -name = "rusty-mermaid-diagrams" -version = "0.2.0" -source = "git+https://github.com/ZhangHanDong/rusty-mermaid.git#68a429b0472d57bce992c7bdddb59aa0c967e2f8" -dependencies = [ - "rusty-mermaid-core", - "rusty-mermaid-dagre", - "rusty-mermaid-graph", - "thiserror 2.0.20", - "winnow 0.7.15", -] - -[[package]] -name = "rusty-mermaid-graph" -version = "0.2.0" -source = "git+https://github.com/ZhangHanDong/rusty-mermaid.git#68a429b0472d57bce992c7bdddb59aa0c967e2f8" -dependencies = [ - "rusty-mermaid-core", -] - -[[package]] -name = "rusty-mermaid-svg" -version = "0.2.0" -source = "git+https://github.com/ZhangHanDong/rusty-mermaid.git#68a429b0472d57bce992c7bdddb59aa0c967e2f8" -dependencies = [ - "rusty-mermaid-core", -] - [[package]] name = "rustybuzz" version = "0.18.0" @@ -6686,28 +5387,6 @@ dependencies = [ "unicode-script 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "rustyline" -version = "15.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee1e066dc922e513bda599c6ccb5f3bb2b0ea5870a579448f2622993f0a9a2f" -dependencies = [ - "bitflags 2.13.2", - "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "clipboard-win", - "fd-lock", - "home", - "libc", - "log", - "memchr 2.8.3", - "nix 0.29.0", - "radix_trie", - "unicode-segmentation 1.13.3", - "unicode-width", - "utf8parse", - "windows-sys 0.59.0", -] - [[package]] name = "ryu" version = "1.0.23" @@ -6715,64 +5394,50 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] -name = "same-file" -version = "1.0.6" +name = "safe_arch" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" dependencies = [ - "winapi-util", + "bytemuck 1.25.2", ] [[package]] -name = "schannel" -version = "0.1.29" +name = "same-file" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ - "windows-sys 0.61.2", + "winapi-util", ] [[package]] -name = "schemars" -version = "0.9.0" +name = "sanitize-filename" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +checksum = "bc984f4f9ceb736a7bb755c3e3bd17dc56370af2600c9780dcc48c66453da34d" dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", + "regex", ] [[package]] -name = "schemars" -version = "1.2.2" +name = "schannel" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ - "chrono", - "dyn-clone", - "ref-cast", - "schemars_derive", - "serde", - "serde_json", + "windows-sys 0.61.2", ] [[package]] -name = "schemars_derive" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98c67716b46af2f0b8cf752abc930f6f9aecfbf671ecfb531db8a31dbe4e2ba" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn 3.0.5", -] +name = "scoped-tls" +version = "1.0.1" [[package]] name = "scoped-tls" version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "scopeguard" @@ -6785,26 +5450,25 @@ name = "sdfer" version = "0.2.1" [[package]] -name = "secrecy" -version = "0.10.3" +name = "sealed" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" +checksum = "22f968c5ea23d555e670b449c1c5e7b2fc399fdaec1d304a17cd48e288abc107" dependencies = [ - "serde", - "zeroize", + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] -name = "security-framework" -version = "2.11.1" +name = "sealed" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +checksum = "9b68e2ea526d9fb32f23ca8894fb5da9e743f34c2f41701f0501dc8a25c4b343" dependencies = [ - "bitflags 2.13.2", - "core-foundation 0.9.4", - "core-foundation-sys", - "libc", - "security-framework-sys", + "proc-macro2", + "quote", + "syn 3.0.5", ] [[package]] @@ -6814,7 +5478,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ "bitflags 2.13.2", - "core-foundation 0.10.1", + "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", @@ -6834,16 +5498,37 @@ dependencies = [ name = "semver" version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] [[package]] -name = "serde" -version = "1.0.229" +name = "serde_bytes" +version = "0.11.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" dependencies = [ + "serde", "serde_core", - "serde_derive", ] [[package]] @@ -6867,14 +5552,16 @@ dependencies = [ ] [[package]] -name = "serde_derive_internals" -version = "0.30.0" +name = "serde_html_form" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f852137cce035d6a4df67ccce505ff6b3e9fd3a10e3e52b24dc71e650bb1a9bd" +checksum = "8f0346d7a342ab90f405cfc08f25d15075f944f42fcabbc5eac923829fa6d228" dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.5", + "form_urlencoded", + "indexmap 2.14.2", + "itoa", + "serde_core", + "zmij", ] [[package]] @@ -6883,7 +5570,6 @@ version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ - "indexmap 2.14.2", "itoa", "memchr 2.8.3", "serde", @@ -6903,93 +5589,45 @@ dependencies = [ ] [[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" +name = "serde_repr" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", + "proc-macro2", + "quote", + "syn 3.0.5", ] [[package]] -name = "serde_with" -version = "3.23.0" +name = "serde_spanned" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "935177bb8c0cd8ca1a4e6d1a2ac8988bea69cab4f9d3a31311e012ad27868ea4" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ - "base64 0.23.1", - "bs58", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.14.2", - "jiff", - "schemars 0.9.0", - "schemars 1.2.2", "serde_core", - "serde_json", - "serde_with_macros", - "time", -] - -[[package]] -name = "serde_with_macros" -version = "3.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d607aa01a3cb0ad757d6fd216136910db3c97b102fe686585689615a02dbcdc" -dependencies = [ - "darling 0.24.1", - "proc-macro2", - "quote", - "syn 3.0.5", ] [[package]] -name = "serde_yml" -version = "0.0.12" +name = "serde_urlencoded" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59e2dd588bf1597a252c3b920e0143eb99b0f76e4e082f4c92ce34fbc9e71ddd" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ - "indexmap 2.14.2", + "form_urlencoded", "itoa", - "libyml", - "memchr 2.8.3", "ryu", "serde", - "version_check", ] [[package]] -name = "sha1" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" -dependencies = [ - "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "cpufeatures 0.2.17", - "digest 0.10.7", -] - -[[package]] -name = "sha1-checked" -version = "0.10.0" +name = "serdect" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89f599ac0c323ebb1c6082821a54962b839832b03984598375bff3975b804423" +checksum = "66cf8fedced2fcf12406bcb34223dffb92eaf34908ede12fed414c82b7f00b3e" dependencies = [ - "digest 0.10.7", - "sha1", + "base16ct", + "serde", ] [[package]] @@ -7023,18 +5661,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "shell-words" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - [[package]] name = "shlex" version = "2.0.1" @@ -7051,6 +5677,15 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" +dependencies = [ + "rand_core 0.10.1", +] + [[package]] name = "simd-adler32" version = "0.3.9" @@ -7077,24 +5712,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" -[[package]] -name = "similar" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" - -[[package]] -name = "simple_asn1" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" -dependencies = [ - "num-bigint", - "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", - "thiserror 2.0.20", - "time", -] - [[package]] name = "simplecss" version = "0.2.2" @@ -7110,12 +5727,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" -[[package]] -name = "sketches-ddsketch" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c6f73aeb92d671e0cc4dca167e59b2deb6387c375391bc99ee743f326994a2b" - [[package]] name = "slab" version = "0.4.12" @@ -7140,6 +5751,9 @@ name = "smallvec" version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba467056f1b547ed52077911161fc86985becbc60e8e1857c8a144dab0def891" +dependencies = [ + "serde", +] [[package]] name = "socket2" @@ -7151,12 +5765,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "spin" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" - [[package]] name = "spirv" version = "0.3.0+sdk-1.3.268.0" @@ -7164,48 +5772,12 @@ dependencies = [ "bitflags 2.10.0", ] -[[package]] -name = "sse-stream" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c25ac7aff0abd1dbc474536e40416e1102c7dd9bfba0b9861c6d357f835dcfb4" -dependencies = [ - "bytes", - "futures-util", - "http-body", - "http-body-util", - "pin-project-lite", -] - [[package]] name = "stable_deref_trait" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "streaming-iterator" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520" - -[[package]] -name = "streaming-markdown-kit" -version = "0.1.0" -source = "git+https://github.com/Project-Robius-China/streaming-markdown-kit.git?branch=main#68b8cdf91ea7579f4bd607b09f005cecfa8aca6f" -dependencies = [ - "anyhow", - "resvg", - "rusty-mermaid", - "usvg", -] - [[package]] name = "strict-num" version = "0.1.1" @@ -7223,9 +5795,8 @@ checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" dependencies = [ "new_debug_unreachable", "parking_lot", - "phf_shared 0.13.1", + "phf_shared", "precomputed-hash", - "serde", ] [[package]] @@ -7235,49 +5806,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" dependencies = [ "phf_generator", - "phf_shared 0.13.1", + "phf_shared", "proc-macro2", "quote", ] -[[package]] -name = "stringprep" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" -dependencies = [ - "unicode-bidi 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-normalization", - "unicode-properties 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "strum" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "subtle" version = "2.6.1" @@ -7295,10 +5834,15 @@ dependencies = [ ] [[package]] -name = "symlink" -version = "0.1.0" +name = "syn" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident 1.0.24", +] [[package]] name = "syn" @@ -7343,43 +5887,23 @@ dependencies = [ ] [[package]] -name = "sysctl" -version = "0.6.0" +name = "syntect" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01198a2debb237c62b6826ec7081082d951f46dbb64b0e8c7649a452230d1dfc" +checksum = "656b45c05d95a5704399aeef6bd0ddec7b2b3531b7c9e900abbf7c4d2190c925" dependencies = [ - "bitflags 2.13.2", - "byteorder 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "enum-as-inner", - "libc", - "thiserror 1.0.69", + "bincode", + "fancy-regex", + "flate2", + "fnv", + "once_cell 1.21.4", + "regex-syntax", + "serde", + "serde_derive", + "thiserror 2.0.20", "walkdir", ] -[[package]] -name = "sysinfo" -version = "0.34.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4b93974b3d3aeaa036504b8eefd4c039dced109171c1ae973f1dc63b2c7e4b2" -dependencies = [ - "libc", - "memchr 2.8.3", - "ntapi", - "objc2-core-foundation", - "windows 0.57.0", -] - -[[package]] -name = "tar" -version = "0.4.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" -dependencies = [ - "filetime", - "libc", - "xattr", -] - [[package]] name = "tempfile" version = "3.27.0" @@ -7465,6 +5989,20 @@ dependencies = [ "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[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 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "zune-jpeg", +] + [[package]] name = "time" version = "0.3.55" @@ -7521,18 +6059,6 @@ dependencies = [ "strict-num", ] -[[package]] -name = "tiny_http" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82" -dependencies = [ - "ascii", - "chunked_transfer", - "httpdate", - "log", -] - [[package]] name = "tinystr" version = "0.8.4" @@ -7558,9 +6084,7 @@ dependencies = [ "bytes", "libc", "mio", - "parking_lot", "pin-project-lite", - "signal-hook-registry", "socket2", "tokio-macros", "windows-sys 0.61.2", @@ -7588,58 +6112,15 @@ dependencies = [ ] [[package]] -name = "tokio-stream" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" -dependencies = [ - "futures-util", - "log", - "rustls", - "rustls-native-certs", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tungstenite 0.24.0", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" -dependencies = [ - "futures-util", - "log", - "rustls", - "rustls-native-certs", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tungstenite 0.26.2", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.29.0" +name = "tokio-stream" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f72a05e828585856dacd553fba484c242c46e391fb0e58917c942ee9202915c" +checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" dependencies = [ - "futures-util", - "log", + "futures-core", + "pin-project-lite", "tokio", - "tungstenite 0.29.0", + "tokio-util", ] [[package]] @@ -7658,44 +6139,54 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.23" +version = "1.1.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +checksum = "920602543f0911ab71da12c50d59701da54c196d1a2bf5cb4b75667f137a406a" dependencies = [ - "serde", + "indexmap 2.14.2", + "serde_core", "serde_spanned", "toml_datetime", - "toml_edit", + "toml_parser", + "toml_writer", + "winnow", ] [[package]] name = "toml_datetime" -version = "0.6.11" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ - "serde", + "serde_core", ] [[package]] name = "toml_edit" -version = "0.22.27" +version = "0.25.15+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +checksum = "1340ea94a5856333492c9064b02c778b191dd2c853778d9609debdcdfea3a614" dependencies = [ "indexmap 2.14.2", - "serde", - "serde_spanned", "toml_datetime", - "toml_write", - "winnow 0.7.15", + "toml_parser", + "winnow", ] [[package]] -name = "toml_write" -version = "0.1.2" +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 = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" [[package]] name = "tower" @@ -7719,16 +6210,20 @@ version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ + "async-compression", "bitflags 2.13.2", "bytes", + "futures-core", "futures-util", "http", "http-body", + "http-body-util", "pin-project-lite", + "tokio", + "tokio-util", "tower", "tower-layer", "tower-service", - "tracing", "url", ] @@ -7756,19 +6251,6 @@ dependencies = [ "tracing-core", ] -[[package]] -name = "tracing-appender" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c" -dependencies = [ - "crossbeam-channel", - "symlink", - "thiserror 2.0.20", - "time", - "tracing-subscriber", -] - [[package]] name = "tracing-attributes" version = "0.1.31" @@ -7790,16 +6272,6 @@ dependencies = [ "valuable", ] -[[package]] -name = "tracing-error" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" -dependencies = [ - "tracing", - "tracing-subscriber", -] - [[package]] name = "tracing-log" version = "0.2.0" @@ -7811,16 +6283,6 @@ dependencies = [ "tracing-core", ] -[[package]] -name = "tracing-serde" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" -dependencies = [ - "serde", - "tracing-core", -] - [[package]] name = "tracing-subscriber" version = "0.3.23" @@ -7831,74 +6293,12 @@ dependencies = [ "nu-ansi-term", "once_cell 1.21.4", "regex-automata", - "serde", - "serde_json", "sharded-slab", "smallvec 1.16.1", "thread_local", "tracing", "tracing-core", "tracing-log", - "tracing-serde", -] - -[[package]] -name = "tree-sitter" -version = "0.24.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5387dffa7ffc7d2dae12b50c6f7aab8ff79d6210147c6613561fc3d474c6f75" -dependencies = [ - "cc", - "regex", - "regex-syntax", - "streaming-iterator", - "tree-sitter-language", -] - -[[package]] -name = "tree-sitter-javascript" -version = "0.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf40bf599e0416c16c125c3cec10ee5ddc7d1bb8b0c60fa5c4de249ad34dc1b1" -dependencies = [ - "cc", - "tree-sitter-language", -] - -[[package]] -name = "tree-sitter-language" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca0d1bf6fdd806e43ae5198f82f527056d359def39e54e67a0f478ac09dac081" - -[[package]] -name = "tree-sitter-python" -version = "0.23.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d065aaa27f3aaceaf60c1f0e0ac09e1cb9eb8ed28e7bcdaa52129cffc7f4b04" -dependencies = [ - "cc", - "tree-sitter-language", -] - -[[package]] -name = "tree-sitter-rust" -version = "0.23.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca8ccb3e3a3495c8a943f6c3fd24c3804c471fd7f4f16087623c7fa4c0068e8a" -dependencies = [ - "cc", - "tree-sitter-language", -] - -[[package]] -name = "tree-sitter-typescript" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5f76ed8d947a75cc446d5fccd8b602ebf0cde64ccf2ffa434d873d7a575eff" -dependencies = [ - "cc", - "tree-sitter-language", ] [[package]] @@ -7921,99 +6321,48 @@ dependencies = [ ] [[package]] -name = "tungstenite" -version = "0.24.0" +name = "typed-arena" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" -dependencies = [ - "byteorder 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "bytes", - "data-encoding", - "http", - "httparse", - "log", - "rand 0.8.8", - "rustls", - "rustls-pki-types", - "sha1", - "thiserror 1.0.69", - "utf-8", -] +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" [[package]] -name = "tungstenite" -version = "0.26.2" +name = "typenum" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" -dependencies = [ - "bytes", - "data-encoding", - "http", - "httparse", - "log", - "rand 0.9.5", - "rustls", - "rustls-pki-types", - "sha1", - "thiserror 2.0.20", - "utf-8", -] +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] -name = "tungstenite" -version = "0.28.0" +name = "typewit" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" -dependencies = [ - "bytes", - "data-encoding", - "http", - "httparse", - "log", - "rand 0.9.5", - "sha1", - "thiserror 2.0.20", - "utf-8", -] +checksum = "214ca0b2191785cbc06209b9ca1861e048e39b5ba33574b3cedd58363d5bb5f6" [[package]] -name = "tungstenite" -version = "0.29.0" +name = "ucd-trie" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c01152af293afb9c7c2a57e4b559c5620b421f6d133261c60dd2d0cdb38e6b8" -dependencies = [ - "bytes", - "data-encoding", - "http", - "httparse", - "log", - "rand 0.9.5", - "sha1", - "thiserror 2.0.20", -] +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] -name = "type1-encoding-parser" -version = "0.1.1" +name = "uds_windows" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa10c302f5a53b7ad27fd42a3996e23d096ba39b5b8dd6d9e683a05b01bee749" +checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" dependencies = [ - "pom", + "memoffset", + "tempfile", + "windows-sys 0.61.2", ] [[package]] -name = "typenum" -version = "1.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" - -[[package]] -name = "uluru" -version = "3.1.0" +name = "ulid" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c8a2469e56e6e5095c82ccd3afb98dad95f7af7929aab6d8ba8d6e0f73657da" +checksum = "947dde63b6d514cc5e044edad4e0ca7261afd1099d16c83d942cb2b2f348689c" dependencies = [ - "arrayvec 0.7.8", + "rand 0.10.2", + "web-time", ] [[package]] @@ -8046,12 +6395,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfa6e8c60bb66d49db113e0125ee8711b7647b5579dc7f5f19c42357ed039fe" -[[package]] -name = "unicode-bom" -version = "2.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217" - [[package]] name = "unicode-ccc" version = "0.3.0" @@ -8135,12 +6478,12 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "universal-hash" -version = "0.5.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +checksum = "f4987bdc12753382e0bec4a65c50738ffaabc998b9cdd1f952fb5f39b0048a96" dependencies = [ - "crypto-common 0.1.7", - "subtle", + "crypto-common 0.2.2", + "ctutils", ] [[package]] @@ -8162,6 +6505,12 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "usvg" version = "0.46.0" @@ -8189,12 +6538,6 @@ dependencies = [ "xmlwriter", ] -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -8237,6 +6580,37 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vodozemac" +version = "0.10.0" +source = "git+https://github.com/matrix-org/vodozemac/?rev=cfc4eee#cfc4eeebdf0953314144a6b709449b0434019ca2" +dependencies = [ + "aes", + "arrayvec 0.7.8", + "base64 0.22.1", + "base64ct", + "cbc", + "chacha20poly1305", + "cipher", + "curve25519-dalek", + "ed25519-dalek", + "getrandom 0.4.3", + "hkdf", + "hmac", + "hpke", + "matrix-pickle", + "prost", + "rand 0.10.2", + "serde", + "serde_bytes", + "serde_json", + "sha2 0.11.0", + "subtle", + "thiserror 2.0.20", + "x25519-dalek", + "zeroize", +] + [[package]] name = "walkdir" version = "2.5.0" @@ -8262,15 +6636,6 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasip2" -version = "1.0.4+wasi-0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" -dependencies = [ - "wit-bindgen", -] - [[package]] name = "wasm-bindgen" version = "0.2.128" @@ -8328,9 +6693,9 @@ dependencies = [ [[package]] name = "wasm-streams" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" dependencies = [ "futures-util", "js-sys", @@ -8340,15 +6705,20 @@ dependencies = [ ] [[package]] -name = "wasm-streams" -version = "0.5.0" +name = "wasm_evt_listener" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +checksum = "dc92d6378b411ed94839112a36d9dbc77143451d85b05dfb0cce93a78dab1963" dependencies = [ - "futures-util", + "accessory", + "derivative", + "derive_more 1.0.0", + "fancy_constructor", + "futures-core", "js-sys", + "smallvec 1.16.1", + "tokio", "wasm-bindgen", - "wasm-bindgen-futures", "web-sys", ] @@ -8357,7 +6727,7 @@ name = "wayland-backend" version = "0.3.12" dependencies = [ "downcast-rs", - "scoped-tls", + "scoped-tls 1.0.1", "smallvec 1.16.1", "wayland-sys", ] @@ -8413,6 +6783,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ "js-sys", + "serde", "wasm-bindgen", ] @@ -8422,28 +6793,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba8b815c1b593dc0baf78dd0f4fc8fdb2de53198fb1163738093e9a311c33fb3" dependencies = [ - "phf 0.13.1", + "phf", "phf_codegen", "string_cache", "string_cache_codegen", ] -[[package]] -name = "webbrowser" -version = "1.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62c35be770821a214dbc362fc26908c853e776c0004294d0b10b8a6bad582f94" -dependencies = [ - "jni 0.22.4", - "log", - "ndk-context", - "objc2", - "objc2-app-kit", - "objc2-foundation", - "url", - "web-sys", -] - [[package]] name = "webpki-root-certs" version = "1.0.9" @@ -8453,15 +6808,6 @@ dependencies = [ "rustls-pki-types", ] -[[package]] -name = "webpki-roots" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "weezl" version = "0.1.12" @@ -8473,70 +6819,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" [[package]] -name = "which" -version = "7.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d643ce3fd3e5b54854602a080f34fb10ab75e0b813ee32d00ca2b44fa74762" -dependencies = [ - "either", - "env_home", - "rustix", - "winsafe", -] - -[[package]] -name = "which" -version = "8.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bae2f2b2b816647a1cab1acc91f5bd20812d53cb344382635ec2181940c8034f" -dependencies = [ - "libc", -] - -[[package]] -name = "widestring" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.11" +name = "wide" +version = "0.7.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03" dependencies = [ - "windows-sys 0.61.2", + "bytemuck 1.25.2", + "safe_arch", ] [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "wildmatch" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "29333c3ea1ba8b17211763463ff24ee84e41c78224c16b001cd907e663a38c68" [[package]] -name = "windows" -version = "0.48.0" +name = "winapi-util" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-targets 0.48.5", + "windows-sys 0.61.2", ] [[package]] @@ -8551,12 +6855,15 @@ dependencies = [ [[package]] name = "windows" -version = "0.57.0" +version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ - "windows-core 0.57.0", - "windows-targets 0.52.6", + "windows-collections 0.2.0", + "windows-core 0.61.2", + "windows-future 0.2.1", + "windows-link 0.1.3", + "windows-numerics", ] [[package]] @@ -8569,15 +6876,12 @@ dependencies = [ ] [[package]] -name = "windows" -version = "0.62.2" +name = "windows-collections" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" dependencies = [ - "windows-collections 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "windows-core 0.62.2 (registry+https://github.com/rust-lang/crates.io-index)", - "windows-future 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "windows-numerics", + "windows-core 0.61.2", ] [[package]] @@ -8587,15 +6891,6 @@ dependencies = [ "windows-core 0.62.2", ] -[[package]] -name = "windows-collections" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" -dependencies = [ - "windows-core 0.62.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "windows-core" version = "0.56.0" @@ -8610,14 +6905,15 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.57.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ - "windows-implement 0.57.0", - "windows-interface 0.57.0", - "windows-result 0.1.2", - "windows-targets 0.52.6", + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", ] [[package]] @@ -8644,20 +6940,20 @@ dependencies = [ [[package]] name = "windows-future" -version = "0.3.2" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ - "windows-core 0.62.2", + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading", ] [[package]] name = "windows-future" version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" dependencies = [ - "windows-core 0.62.2 (registry+https://github.com/rust-lang/crates.io-index)", - "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "windows-threading", + "windows-core 0.62.2", ] [[package]] @@ -8671,17 +6967,6 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "windows-implement" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "windows-implement" version = "0.60.2" @@ -8706,9 +6991,9 @@ dependencies = [ [[package]] name = "windows-interface" -version = "0.57.0" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", @@ -8716,15 +7001,10 @@ dependencies = [ ] [[package]] -name = "windows-interface" -version = "0.59.3" +name = "windows-link" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-link" @@ -8738,32 +7018,30 @@ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-numerics" -version = "0.3.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ - "windows-core 0.62.2 (registry+https://github.com/rust-lang/crates.io-index)", - "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "windows-core 0.61.2", + "windows-link 0.1.3", ] [[package]] -name = "windows-registry" -version = "0.6.1" +name = "windows-result" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" dependencies = [ - "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "windows-result 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "windows-strings 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "windows-targets 0.52.6", ] [[package]] name = "windows-result" -version = "0.1.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ - "windows-targets 0.52.6", + "windows-link 0.1.3", ] [[package]] @@ -8782,6 +7060,15 @@ dependencies = [ "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + [[package]] name = "windows-strings" version = "0.5.1" @@ -8807,15 +7094,6 @@ dependencies = [ "windows-targets 0.42.2", ] -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" @@ -8825,15 +7103,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-sys" -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" @@ -8867,21 +7136,6 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -8917,11 +7171,11 @@ dependencies = [ [[package]] name = "windows-threading" -version = "0.2.1" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" dependencies = [ - "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "windows-link 0.1.3", ] [[package]] @@ -8930,12 +7184,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -8954,12 +7202,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -8978,12 +7220,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -9014,12 +7250,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -9038,12 +7268,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -9062,12 +7286,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -9086,12 +7304,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -9106,34 +7318,23 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.6.26" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e90edd2ac1aa278a5c4599b1d89cf03074b610800f866d4026dc199d7929a28" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" dependencies = [ "memchr 2.8.3", ] [[package]] -name = "winnow" -version = "0.7.15" +name = "winresource" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +checksum = "0986a8b1d586b7d3e4fe3d9ea39fb451ae22869dcea4aa109d287a374d866087" dependencies = [ - "memchr 2.8.3", + "toml", + "version_check", ] -[[package]] -name = "winsafe" -version = "0.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" - -[[package]] -name = "wit-bindgen" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" - [[package]] name = "writeable" version = "0.6.4" @@ -9141,23 +7342,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" [[package]] -name = "xattr" -version = "1.6.1" +name = "x11rb" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" dependencies = [ - "libc", + "gethostname", "rustix", + "x11rb-protocol", ] [[package]] -name = "xml5ever" -version = "0.38.0" +name = "x11rb-protocol" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" + +[[package]] +name = "x25519-dalek" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dc9559429edf0cd3f327cc0afd9d6b36fa8cec6d93107b7fbe64f806b5f2d9" +checksum = "e7e8131a03190127fb2263afc72b322ecadae46b6ff8c6f399ff5d02f5559af6" dependencies = [ - "log", - "markup5ever", + "curve25519-dalek", + "rand_core 0.10.1", + "serde", + "zeroize", ] [[package]] @@ -9167,13 +7377,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" [[package]] -name = "xz2" -version = "0.1.7" +name = "xxhash-rust" +version = "0.8.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" -dependencies = [ - "lzma-sys", -] +checksum = "aee1b19627c7c60102ab80d3a9cbe18de90bfe03bfa6c3715447681f0e8c8af6" [[package]] name = "yoke" @@ -9198,6 +7405,76 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zbus" +version = "5.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5db4be7c075cb421e4b7ee645541604239bd243ba7c357511f4ff3a74b555907" +dependencies = [ + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "libc", + "ordered-stream", + "rustix", + "serde", + "serde_repr", + "tracing", + "uds_windows", + "uuid", + "windows-sys 0.61.2", + "winnow", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2990635d09ade6df1868f72f8cac69a876a90981e8bd3c40b1be413f8dc88f40" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 3.0.5", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e" +dependencies = [ + "serde", + "winnow", + "zvariant", +] + +[[package]] +name = "zcheapstr" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1afec51604565183aeb5c54c20aeab286120d4e4460f7f76e3e8bb8c0d99473" +dependencies = [ + "serde", +] + [[package]] name = "zerocopy" version = "0.8.39" @@ -9296,36 +7573,6 @@ dependencies = [ "syn 3.0.5", ] -[[package]] -name = "zip" -version = "2.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" -dependencies = [ - "aes", - "arbitrary", - "bzip2", - "constant_time_eq 0.3.1", - "crc32fast", - "crossbeam-utils", - "deflate64", - "displaydoc", - "flate2", - "getrandom 0.3.4", - "hmac", - "indexmap 2.14.2", - "lzma-rs", - "memchr 2.8.3", - "pbkdf2", - "sha1", - "thiserror 2.0.20", - "time", - "xz2", - "zeroize", - "zopfli", - "zstd", -] - [[package]] name = "zlib-rs" version = "0.6.7" @@ -9339,56 +7586,61 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" [[package]] -name = "zopfli" -version = "0.8.3" +name = "zune-core" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" -dependencies = [ - "bumpalo", - "crc32fast", - "log", - "simd-adler32 0.3.10", -] +checksum = "d56377fd46368984a170bc5aac5567e52ca5da874caa60bea39fcbca78fb658b" [[package]] -name = "zstd" -version = "0.13.3" +name = "zune-jpeg" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" dependencies = [ - "zstd-safe", + "zune-core", ] [[package]] -name = "zstd-safe" -version = "7.3.0" +name = "zvariant" +version = "5.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64d80649ab6db9d9f6f9c80a40becd948eda4714a0a5ac8c4d157a32231c7882" +checksum = "c1d34c27cc6cdd1f458427519dd6b8612f7b7e3f7b9a0b2355d041dda9869147" dependencies = [ - "zstd-sys", + "endi", + "enumflags2", + "serde", + "winnow", + "zcheapstr", + "zvariant_derive", + "zvariant_utils", ] [[package]] -name = "zstd-sys" -version = "2.1.0+zstd.1.5.7" +name = "zvariant_derive" +version = "5.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ef0a8027ec3ee71300ab3bcbcd0393f434aa72b91ca6d635a39941deae8eea0" +checksum = "864155e69b4352db0c7f374917bf45d1e0c8d17659c8b3dbf9795f3673f8c497" dependencies = [ - "cc", - "pkg-config 0.3.34", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 3.0.5", + "zvariant_utils", ] [[package]] -name = "zune-core" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56377fd46368984a170bc5aac5567e52ca5da874caa60bea39fcbca78fb658b" - -[[package]] -name = "zune-jpeg" -version = "0.5.15" +name = "zvariant_utils" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" +checksum = "bad0294361a320b694a328460dc73add56c306150f5cb6bfafc44446120008a3" dependencies = [ - "zune-core", + "proc-macro2", + "quote", + "serde", + "syn 3.0.5", + "winnow", ] + +[[patch.unused]] +name = "makepad-ai" +version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index 2c5a7824..6e067d33 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,7 +56,13 @@ makepad-photos = { git = "https://github.com/OctoSense-org/makepad.git", rev = " octosense-reference = { path = "apps/reference", optional = true } # The whole Octoscript-AppCard app hosted in a tile, in-process (Phase B of # the AppCard port): octos-app's AppShell — routing brain, cards, kernel agent. -octosense-appcard = { path = "apps/appcard", optional = true } +# MVP (Rinx module): AppCard is out of this graph. Its Octos (octos-core +# deb433e9, rusqlite 0.32) links libsqlite3-sys 0.30, which conflicts with the +# Matrix SDK's 0.38 in Rinx. Octos >= dc96b927 (rusqlite 0.40) resolves this. +# octosense-appcard = { path = "apps/appcard", optional = true } +# Rinx (the Matrix client with Moments and articles) as an in-process module. +# Built without its standalone window entry point. +rinx = { path = "../rinx", default-features = false, features = ["octosense-module"], optional = true } # The assistant as a module, seated in the pane in-process: the web build's # only chat, a desktop option. Without the engine feature: no model runtime, # NoModel and the tool console (the child process keeps the real models). @@ -66,27 +72,30 @@ makepad-aichat = { git = "https://github.com/OctoSense-org/makepad.git", rev = " # available modules into native mobile builds without requiring CLI flags. [target.'cfg(any(target_os = "android", target_os = "ios"))'.dependencies] octosense-reference = { path = "apps/reference" } -octosense-appcard = { path = "apps/appcard" } +# octosense-appcard = { path = "apps/appcard" } # see the MVP note above makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d4502ef1e4d196d2829a07aa137740cb0581e9b4", default-features = false } makepad-photos = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d4502ef1e4d196d2829a07aa137740cb0581e9b4", default-features = false } [features] default = [] # Also usable on desktop for testing the same embedded apps with --module. -mobile-apps = ["app-reference", "app-sheets", "app-photos", "app-appcard"] +mobile-apps = ["app-reference", "app-sheets", "app-photos"] app-reference = ["dep:octosense-reference"] # Sheets linked in as a module (hosting is still Process unless switched). app-sheets = ["dep:makepad-sheets"] # Photos (the picture wall) linked in as a module, the same way. app-photos = ["dep:makepad-photos"] # The AppCard module (one L0 weather card in a tile) linked in. -app-appcard = ["dep:octosense-appcard"] +app-appcard = [] # The aichat module linked in (the pane seats it in-process where there are # no processes, or when `aichat` is switched to module hosting). app-aichat = ["dep:makepad-aichat"] +# Rinx linked in as a module (`--module rinx`). +app-rinx = ["dep:rinx"] [workspace] -members = [".", "apps/reference", "apps/appcard"] +members = [".", "apps/reference"] +exclude = ["apps/appcard"] default-members = ["."] resolver = "2" @@ -123,6 +132,9 @@ makepad-wm-theme = { path = "../makepad/libs/wm_theme" } makepad-sheets = { path = "../makepad/apps/sheets" } makepad-photos = { path = "../makepad/apps/photos" } makepad-aichat = { path = "../makepad/apps/aichat" } +# Rinx's article editor: the Markdown and math components. +makepad-markdown = { path = "../makepad/libs/markdown" } +makepad-markdown-widgets = { path = "../makepad/libs/markdown_widgets" } [patch."https://github.com/OctoSense-org/Octoscript.git"] octoscript-ui-l0 = { path = "../octoscript/crates/octoscript-ui-l0" } @@ -132,3 +144,15 @@ octoscript-node = { path = "../octoscript-makepad/crates/octoscript-node" } [patch."https://github.com/ZhangHanDong/makepad.git"] makepad-widgets = { path = "../makepad/widgets" } + +# Rinx names makepad by the kevinaboos URL; it resolves to the one makepad here. +# (Its OctoSense-org URL without `.git` is the same source as the table above.) +[patch."https://github.com/kevinaboos/makepad"] +makepad-widgets = { path = "../makepad/widgets" } +makepad-code-editor = { path = "../makepad/code_editor" } + + +# Rinx's Matrix patches (see Rinx's Cargo.toml). +[patch."https://github.com/ruma/ruma"] +ruma = { git = "https://github.com/project-robius/ruma.git", branch = "tsp" } + diff --git a/src/apps.rs b/src/apps.rs index 5b87b387..44e0f558 100644 --- a/src/apps.rs +++ b/src/apps.rs @@ -48,8 +48,9 @@ fn linked_modules() -> Vec<&'static dyn AppModule> { out.push(&makepad_sheets::SHEETS_MODULE); #[cfg(any(feature = "app-photos", target_os = "android", target_os = "ios"))] out.push(&makepad_photos::PHOTOS_MODULE); - #[cfg(any(feature = "app-appcard", target_os = "android", target_os = "ios"))] - out.push(&octosense_appcard::APPCARD_MODULE); + // MVP (Rinx module): AppCard is out of the graph; see Cargo.toml. + #[cfg(feature = "app-rinx")] + out.push(&rinx::module::RINX_MODULE); out } @@ -220,7 +221,8 @@ mod tests { } } -#[cfg(all(test, feature = "mobile-apps"))] +// MVP (Rinx module): AppCard is out of the graph; see Cargo.toml. +#[cfg(any())] mod appcard_isolate_tests { /// The app's cards are Splash widgets, each in an ISOLATE that is minted /// without the framework's `sys`/`agent` engine; the AppCard module must From e5a18c424496a04408332899f36d55193bd842cc Mon Sep 17 00:00:00 2001 From: ychen Date: Wed, 23 Sep 2026 21:51:03 -0700 Subject: [PATCH 02/42] Module windows as tiles; modals kept inside module tiles - ModuleHost: each instance's ModuleWindows (supported only outside the phone shell; updated on style switches). The WM turns Open requests into module tiles drawn in the owner's isolate (focusing an open one), closes them on request without reporting back, reports person-closed windows to the owner, and closes an instance's windows with it or on switching to a phone style. - MpModuleView sets ModalBounds to its rect while drawing the root. Co-Authored-By: Claude Opus 5.5 (1M context) --- src/desktop_app.rs | 10 +++++++ src/main.rs | 72 ++++++++++++++++++++++++++++++++++++++++++++-- src/module_host.rs | 35 +++++++++++++++++++++- src/module_view.rs | 3 ++ 4 files changed, 117 insertions(+), 3 deletions(-) diff --git a/src/desktop_app.rs b/src/desktop_app.rs index 6ed4ccf0..b392c3bc 100644 --- a/src/desktop_app.rs +++ b/src/desktop_app.rs @@ -97,6 +97,16 @@ impl App { state.roles = roles; state.dragging.clear(); state.style.select(style); + // Extra module windows are a desktop thing: the phone shell's apps are + // full-screen, so their windows close (the apps hear it, as from a person). + self.module_host.set_extra_windows(!style.mobile()); + if style.mobile() { + let windows: Vec<_> = self.module_windows.keys().copied().collect(); + for window in windows { + self.remove_client(cx, window); + } + } + let state = self.state_mut(); if changes_size { state.style.step(1.0); } state.layout.desktop.enabled = style.floating(); for c in state.layout.all_clients() { diff --git a/src/main.rs b/src/main.rs index 66cfdf7f..62295c88 100644 --- a/src/main.rs +++ b/src/main.rs @@ -61,7 +61,7 @@ use shell::panels::ShellPanelAction; use ai_bus::{AiBus, Route}; use apps::{AppRegistry, Hosting}; use makepad_ai_services::wire::{ServiceCall, ServiceDown, ToolResult}; -use makepad_app_module::{AppModule, ExecOutcome, ModuleUpstream}; +use makepad_app_module::{AppModule, ExecOutcome, ModuleUpstream, WindowRequest}; use module_host::ModuleHost; use pane_links::{PaneCall, PaneLinks}; use makepad_widgets::ai_slot::AiSlotRequests; @@ -386,6 +386,10 @@ pub struct App { /// isolate each, seated in a `ModuleTile`. #[rust] module_host: ModuleHost, + /// Extra windows module instances opened (`ModuleWindows`): window + /// client -> (owner instance's client, the instance's key for it). + #[rust] + module_windows: HashMap, /// The in-process transport to an assistant seated in the pane as a /// module: the WM's services as links its registry adopts (pane_links.rs). #[rust] @@ -1370,7 +1374,8 @@ impl App { fn request_close(&mut self, cx: &mut Cx, client: ClientId) { // A module instance has no process to ask politely and nothing to // reap later: it ends now, through the same removal as a death. - if self.module_host.is_module(client) { + // So does one of its extra windows. + if self.module_host.is_module(client) || self.module_windows.contains_key(&client) { self.remove_client(cx, client); self.update_bar(cx); return; @@ -1425,6 +1430,22 @@ impl App { fn remove_client(&mut self, cx: &mut Cx, client: ClientId) { log!("wm: removing client {}", client); + // An instance's extra window: the tile lets go of the root (the + // instance keeps the widget), and the instance hears it was closed. + if let Some((owner, key)) = self.module_windows.remove(&client) { + self.desk(cx) + .borrow_mut::() + .map(|mut d| d.with_module_view(cx, client, |cx, v| v.clear_root(cx))); + self.module_host.notify_window_closed(owner, key); + } + // An instance going away takes its extra windows with it, first. + if self.module_host.is_module(client) { + let windows: Vec = self.module_windows.iter() + .filter(|(_, (owner, _))| *owner == client).map(|(w, _)| *w).collect(); + for window in windows { + self.remove_client(cx, window); + } + } // A module instance: the tile lets go of the root FIRST, then the // instance and its isolate go (module_host.rs). The bus hears an // Unregister for it below, like for any client. @@ -1931,6 +1952,8 @@ impl App { let id = self.next_id; self.next_id += 1; let area = self.desk_area(cx); + // Extra windows are a desktop thing; the phone shell's apps are full-screen. + self.module_host.extra_windows = !self.state_mut().style.target.mobile(); if let Err(e) = self.module_host.create(cx, id, module, open, dvec2(area.w, area.h)) { log!("wm: module {} failed to start: {}", module.id(), e); return; @@ -1989,6 +2012,50 @@ impl App { } } + /// Instances' extra-window requests (`ModuleWindows`): each root becomes + /// a module tile of its own, drawn and fed events in the owner's isolate. + fn drain_module_windows(&mut self, cx: &mut Cx) { + for (owner, vm_id, app, request) in self.module_host.take_window_requests() { + let existing = |this: &Self, key: LiveId| this.module_windows.iter() + .find(|(_, (o, k))| *o == owner && *k == key).map(|(w, _)| *w); + match request { + WindowRequest::Open { key, title, root, size: _ } => { + if let Some(window) = existing(self, key) { + self.activate_client(cx, window); + continue; + } + let id = self.next_id; + self.next_id += 1; + let area = self.desk_area(cx); + self.state_mut().clients.insert(id, clients::ClientSlot::module(id, app, &title)); + let gap = self.state_mut().gap; + self.state_mut().layout.insert(id, area, gap); + self.desk(cx).borrow_mut::().map(|mut d| { + d.mark_module(id); + d.with_module_view(cx, id, |cx, v| v.set_root(cx, id, vm_id, root)); + }); + self.module_windows.insert(id, (owner, key)); + log!("wm: {} (client {}) opened window {:?} as client {}", app, owner, title, id); + self.activate_client(cx, id); + self.update_bar(cx); + self.redraw_all(cx); + } + WindowRequest::Close { key } => { + // The instance closed it itself: no report back, or a + // stale "closed" could end a window it reopens later. + if let Some(window) = existing(self, key) { + self.module_windows.remove(&window); + self.desk(cx) + .borrow_mut::() + .map(|mut d| d.with_module_view(cx, window, |cx, v| v.clear_root(cx))); + self.remove_client(cx, window); + self.update_bar(cx); + } + } + } + } + } + /// Results in-process executors answered later, up to the pane: down /// the instance's link when the assistant is in-process, as a frame to /// the aichat child otherwise. @@ -4190,6 +4257,7 @@ impl MatchEvent for App { self.drain_hub(cx); self.drain_client_lines(cx); self.drain_module_upstream(); + self.drain_module_windows(cx); } } } diff --git a/src/module_host.rs b/src/module_host.rs index 0cf19d38..a9bed7a5 100644 --- a/src/module_host.rs +++ b/src/module_host.rs @@ -38,6 +38,8 @@ pub struct AppInstance { shutdown: Option>, /// Results and publications the executor sent later. upstream: Receiver, + /// The instance's requests for extra windows, and our reports of closes. + pub windows: ModuleWindows, } impl AppInstance { @@ -48,6 +50,9 @@ impl AppInstance { #[derive(Default)] pub struct ModuleHost { + /// Whether new instances may open extra windows: the desktop shell, + /// not the phone shell (whose apps are full-screen). + pub extra_windows: bool, instances: HashMap, next_scope: u64, per_app: HashMap, @@ -92,7 +97,8 @@ impl ModuleHost { // instance (§3b's mount and the web's IndexedDB sit under it). let storage = cx.storage(&format!("{}.{}", module.id(), instance_no)); let (replies, upstream) = ReplySink::pair(); - let handles = InstanceHandles { scope, storage, viewport: Viewport { size: viewport }, replies }; + let windows = ModuleWindows::new(self.extra_windows); + let handles = InstanceHandles { scope, storage, viewport: Viewport { size: viewport }, replies, windows: windows.clone() }; let vm_id = cx.alloc_splash_vm_with_network(false); let parts = cx.with_script_vm_id_trusted(vm_id, |vm| { // The isolate came up with the stock theme; the WM's palette @@ -124,6 +130,7 @@ impl ModuleHost { executor: parts.executor, shutdown: Some(parts.shutdown), upstream, + windows, }, ); Ok(()) @@ -213,6 +220,32 @@ impl ModuleHost { } /// Every result or publication an executor sent later, with its client. + /// Instances' pending window requests: (owner, its isolate, its app id, request). + pub fn take_window_requests(&mut self) -> Vec<(ClientId, SplashVmId, &'static str, WindowRequest)> { + let mut out = Vec::new(); + for (client, instance) in &self.instances { + for request in instance.windows.take_requests() { + out.push((*client, instance.vm_id, instance.module.id(), request)); + } + } + out + } + + /// The host starts or stops showing extra windows (desktop vs phone shell). + pub fn set_extra_windows(&mut self, on: bool) { + self.extra_windows = on; + for instance in self.instances.values() { + instance.windows.set_supported(on); + } + } + + /// The person closed `owner`'s window `key`. + pub fn notify_window_closed(&self, owner: ClientId, key: LiveId) { + if let Some(instance) = self.instances.get(&owner) { + instance.windows.notify_closed(key); + } + } + pub fn drain_upstream(&mut self) -> Vec<(ClientId, ModuleUpstream)> { let mut out = Vec::new(); for (client, instance) in &self.instances { diff --git a/src/module_view.rs b/src/module_view.rs index 34de8c12..fc0443d4 100644 --- a/src/module_view.rs +++ b/src/module_view.rs @@ -201,7 +201,10 @@ impl Widget for MpModuleView { self.draw_bg.draw_abs(cx, rect); if let Some(root) = self.root.clone() { let entry = enter_isolate(cx, self.vm_id); + // The instance's modals dim and centre within this tile. + let outer = std::mem::replace(&mut cx.global::().0, Some(rect)); root.draw_walk_all(cx, scope, Walk::fill()); + cx.global::().0 = outer; leave_isolate(cx, entry); self.drawn = true; } From 84346fb92713f409c090d67cb226456e645f77d8 Mon Sep 17 00:00:00 2001 From: ychen Date: Wed, 23 Sep 2026 23:37:36 -0700 Subject: [PATCH 03/42] Rinx from upstreamlabs/Rinx#19; makepad pinned to OctoSense-org/makepad#24 - rinx: git dependency at 65aa09c2 (octosense-module, no standalone entry). - makepad-* pins: d9372c61 (rinx/octosense-module-windows = d4502ef1 + Rinx's Markdown/math commits + module windows and pane-bounded modals). - native-runtime.lock.json is NOT updated: it needs an Octoscript-Makepad release selecting this makepad. Co-Authored-By: Claude Opus 5.5 (1M context) --- Cargo.lock | 3 +++ Cargo.toml | 28 ++++++++++++++-------------- apps/reference/Cargo.toml | 4 ++-- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 53ee0bbf..3c81211e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -203,6 +203,7 @@ dependencies = [ [[package]] name = "article-core" version = "0.1.0" +source = "git+https://github.com/upstreamlabs/Rinx?rev=65aa09c2f6022e173b5a875a007806de22974f53#65aa09c2f6022e173b5a875a007806de22974f53" dependencies = [ "blake3", "image", @@ -219,6 +220,7 @@ dependencies = [ [[package]] name = "article-makepad" version = "0.1.0" +source = "git+https://github.com/upstreamlabs/Rinx?rev=65aa09c2f6022e173b5a875a007806de22974f53#65aa09c2f6022e173b5a875a007806de22974f53" dependencies = [ "article-core", "libc", @@ -4859,6 +4861,7 @@ dependencies = [ [[package]] name = "rinx" version = "1.0.0-beta.1" +source = "git+https://github.com/upstreamlabs/Rinx?rev=65aa09c2f6022e173b5a875a007806de22974f53#65aa09c2f6022e173b5a875a007806de22974f53" dependencies = [ "anyhow", "arboard", diff --git a/Cargo.toml b/Cargo.toml index 6e067d33..defe73e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,19 +40,19 @@ path = "src/main.rs" [dependencies] # `appcard,maps`: the Octoscript-AppCard widget set (kit, text roles, weather, # finance, matplot) and the map navigation layer the appcard module renders. -makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d4502ef1e4d196d2829a07aa137740cb0581e9b4", features = ["appcard", "maps"] } -makepad-studio-protocol = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d4502ef1e4d196d2829a07aa137740cb0581e9b4" } -makepad-network = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d4502ef1e4d196d2829a07aa137740cb0581e9b4" } -makepad-wm-api = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d4502ef1e4d196d2829a07aa137740cb0581e9b4" } +makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d9372c6142b359b98b94b57914686ee35321c32b", features = ["appcard", "maps"] } +makepad-studio-protocol = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d9372c6142b359b98b94b57914686ee35321c32b" } +makepad-network = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d9372c6142b359b98b94b57914686ee35321c32b" } +makepad-wm-api = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d9372c6142b359b98b94b57914686ee35321c32b" } # The AI services wire only: the WM relays frames between the aichat child and the apps. -makepad-ai-services = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d4502ef1e4d196d2829a07aa137740cb0581e9b4" } -makepad-strict-json = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d4502ef1e4d196d2829a07aa137740cb0581e9b4" } +makepad-ai-services = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d9372c6142b359b98b94b57914686ee35321c32b" } +makepad-strict-json = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d9372c6142b359b98b94b57914686ee35321c32b" } # In-process hosting (aicontrol §3): the module contract, the palette applied # into each instance's isolate, and the modules this build links. -makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d4502ef1e4d196d2829a07aa137740cb0581e9b4" } -makepad-wm-theme = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d4502ef1e4d196d2829a07aa137740cb0581e9b4" } -makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d4502ef1e4d196d2829a07aa137740cb0581e9b4", default-features = false, optional = true } -makepad-photos = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d4502ef1e4d196d2829a07aa137740cb0581e9b4", default-features = false, optional = true } +makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d9372c6142b359b98b94b57914686ee35321c32b" } +makepad-wm-theme = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d9372c6142b359b98b94b57914686ee35321c32b" } +makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d9372c6142b359b98b94b57914686ee35321c32b", default-features = false, optional = true } +makepad-photos = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d9372c6142b359b98b94b57914686ee35321c32b", default-features = false, optional = true } octosense-reference = { path = "apps/reference", optional = true } # The whole Octoscript-AppCard app hosted in a tile, in-process (Phase B of # the AppCard port): octos-app's AppShell — routing brain, cards, kernel agent. @@ -62,19 +62,19 @@ octosense-reference = { path = "apps/reference", optional = true } # octosense-appcard = { path = "apps/appcard", optional = true } # Rinx (the Matrix client with Moments and articles) as an in-process module. # Built without its standalone window entry point. -rinx = { path = "../rinx", default-features = false, features = ["octosense-module"], optional = true } +rinx = { git = "https://github.com/upstreamlabs/Rinx", rev = "65aa09c2f6022e173b5a875a007806de22974f53", default-features = false, features = ["octosense-module"], optional = true } # The assistant as a module, seated in the pane in-process: the web build's # only chat, a desktop option. Without the engine feature: no model runtime, # NoModel and the tool console (the child process keeps the real models). -makepad-aichat = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d4502ef1e4d196d2829a07aa137740cb0581e9b4", default-features = false, optional = true } +makepad-aichat = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d9372c6142b359b98b94b57914686ee35321c32b", default-features = false, optional = true } # A phone cannot spawn the desktop catalog's Cargo binaries. Link the # available modules into native mobile builds without requiring CLI flags. [target.'cfg(any(target_os = "android", target_os = "ios"))'.dependencies] octosense-reference = { path = "apps/reference" } # octosense-appcard = { path = "apps/appcard" } # see the MVP note above -makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d4502ef1e4d196d2829a07aa137740cb0581e9b4", default-features = false } -makepad-photos = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d4502ef1e4d196d2829a07aa137740cb0581e9b4", default-features = false } +makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d9372c6142b359b98b94b57914686ee35321c32b", default-features = false } +makepad-photos = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d9372c6142b359b98b94b57914686ee35321c32b", default-features = false } [features] default = [] diff --git a/apps/reference/Cargo.toml b/apps/reference/Cargo.toml index 3c596649..17afed3a 100644 --- a/apps/reference/Cargo.toml +++ b/apps/reference/Cargo.toml @@ -6,5 +6,5 @@ license = "Apache-2.0" publish = false [dependencies] -makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d4502ef1e4d196d2829a07aa137740cb0581e9b4" } -makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d4502ef1e4d196d2829a07aa137740cb0581e9b4" } +makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d9372c6142b359b98b94b57914686ee35321c32b" } +makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d9372c6142b359b98b94b57914686ee35321c32b" } From c018e9b5b27d4571d045839b77bbc846de6a9ff4 Mon Sep 17 00:00:00 2001 From: ychen Date: Wed, 23 Sep 2026 23:55:25 -0700 Subject: [PATCH 04/42] Module tiles: a press claimed by a window in front does not raise the one behind MpModuleView raised its tile on any press inside its rect, ignoring the desk's topmost-first claim. With an app's extra window over its main window, every click in the extra window also raised the main one, which (handled later) won. Now a tile raises only on a press no window in front took, and claims an unclaimed press inside it, so windows behind do not react to it. Co-Authored-By: Claude Opus 5.5 (1M context) --- src/module_view.rs | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/module_view.rs b/src/module_view.rs index fc0443d4..280a063f 100644 --- a/src/module_view.rs +++ b/src/module_view.rs @@ -165,15 +165,20 @@ impl Widget for MpModuleView { if matches!(event, Event::KeyDown(_) | Event::KeyUp(_) | Event::TextInput(_)) && !self.focused { return; } + // A press, with the cell that records who claimed it. Tiles see input + // topmost first (desk.rs), so a claimed press belongs to a tile in front. let press = match event { - Event::MouseDown(e) => Some(e.abs), + Event::MouseDown(e) => Some((e.abs, &e.handled)), Event::TouchUpdate(update) => update.touches.iter() .find(|point| point.state == makepad_platform::event::TouchState::Start) - .map(|point| point.abs), + .map(|point| (point.abs, &point.handled)), _ => None, }; - if let Some(abs) = press { - if self.area.is_valid(cx) && self.area.rect(cx).contains(abs) { + let inside = press.is_some_and(|(abs, _)| self.area.is_valid(cx) && self.area.rect(cx).contains(abs)); + if let Some((_, handled)) = press { + // Only a press no window in front took: overlapping windows of one + // app (its extra windows) must not raise the one behind. + if inside && handled.get().is_empty() { if let Some(client) = self.client { // The WM moves focus here (and back to us through // `focus_keyboard`), exactly as for a process tile. @@ -184,6 +189,13 @@ impl Widget for MpModuleView { let entry = enter_isolate(cx, self.vm_id); root.handle_event(cx, event, scope); leave_isolate(cx, entry); + // A press inside this tile is this tile's, even where none of the + // app's widgets took it, so no window behind reacts to it. + if let Some((_, handled)) = press { + if inside && handled.get().is_empty() { + handled.set(self.area); + } + } } fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep { From 94694a1642da6e280f40cefcb06adfc0ebdf1d45 Mon Sep 17 00:00:00 2001 From: ychen Date: Thu, 24 Sep 2026 00:38:11 -0700 Subject: [PATCH 05/42] Link AppCard beside Rinx: AppCard on Octos main, one SQLite AppCard 9a1a8a70 moves to octos-core 18fcd3f1 (rusqlite 0.40 / libsqlite3-sys 0.38), the same SQLite as Rinx's matrix-sdk, so both modules link into one binary. AppCard's makepad pin follows d9372c61. Co-Authored-By: Claude Opus 5.5 (1M context) --- Cargo.lock | 7710 +++++++++++++++++++++++++++++++-------- Cargo.toml | 15 +- apps/appcard/Cargo.toml | 6 +- src/apps.rs | 6 +- 4 files changed, 6172 insertions(+), 1565 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3c81211e..17623389 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,12 +18,40 @@ dependencies = [ "syn 3.0.5", ] +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + [[package]] name = "adler2" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "adobe-cmap-parser" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae8abfa9a4688de8fc9f42b3f013b6fffec18ed8a554f5f113577e0b9b3212a3" +dependencies = [ + "pom", +] + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common 0.1.7", + "generic-array", +] + [[package]] name = "aead" version = "0.6.1" @@ -31,7 +59,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1973cfbc1a2daf9cf550e74e1f088c28e7f7d8c1e1418fb6c9dc5184b7e84c99" dependencies = [ "crypto-common 0.2.2", - "inout", + "inout 0.2.2", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "cipher 0.4.4", + "cpufeatures 0.2.17", ] [[package]] @@ -40,11 +79,61 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35f0f96ce78e38c3dc6d8948aa8163d06385be74000f3c7a95bf1eef35d3ea32" dependencies = [ - "cipher", + "cipher 0.5.2", "cpubits", "cpufeatures 0.3.1", ] +[[package]] +name = "agent-client-protocol" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d386d6a58f4dbe0ebfa98e915caa54005dabdefd419de3d4678b28cd5752444" +dependencies = [ + "agent-client-protocol-derive", + "agent-client-protocol-schema", + "async-io", + "async-process", + "blocking", + "futures", + "futures-concurrency", + "rustc-hash 2.1.3", + "rustix", + "schemars 1.2.2", + "serde", + "serde_json", + "shell-words", + "tracing", + "uuid", + "windows-sys 0.61.2", +] + +[[package]] +name = "agent-client-protocol-derive" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b94934d118a69e921d14e94d4af5b3076563318c52662c89a0ecb3f139e1da" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "agent-client-protocol-schema" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06679e1542356341f4550ccfb16338b64f37f6af70de2105446ef6fbb078234c" +dependencies = [ + "anyhow", + "derive_more 2.1.1", + "schemars 1.2.2", + "serde", + "serde_json", + "serde_with", + "strum", + "tracing", +] + [[package]] name = "aho-corasick" version = "1.1.5" @@ -84,6 +173,23 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "android_log-sys" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84521a3cf562bc62942e294181d9eef17eb38ceb8c68677bc49f144e4c3d4f8d" + +[[package]] +name = "android_logger" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b07e8e73d720a1f2e4b6014766e6039fd2e96a4fa44e2a78d0e1fa2ff49826" +dependencies = [ + "android_log-sys", + "env_filter 0.1.4", + "log", +] + [[package]] name = "android_system_properties" version = "0.1.6" @@ -93,6 +199,23 @@ dependencies = [ "libc", ] +[[package]] +name = "anndists" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8396b473aa0bceed68fb32462505387ea39fa47c7029417e0a49f10592b036" +dependencies = [ + "anyhow", + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "cpu-time", + "env_logger", + "lazy_static", + "log", + "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus", + "rayon", +] + [[package]] name = "anstream" version = "1.0.0" @@ -155,6 +278,15 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c" +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + [[package]] name = "arboard" version = "3.6.1" @@ -175,12 +307,33 @@ dependencies = [ "x11rb", ] +[[package]] +name = "arc-swap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b" +dependencies = [ + "rustversion", +] + [[package]] name = "archery" version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ca55ee147b1926dbea904f50fe4902494e97bc742205abbbf10c709e43815f" +[[package]] +name = "argon2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures 0.2.17", + "password-hash", +] + [[package]] name = "arrayref" version = "0.3.9" @@ -235,6 +388,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9dbc3a507a82b17ba0d98f6ce8fd6954ea0c8152e98009d36a40d8dcc8ce078a" +[[package]] +name = "ascii" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" + [[package]] name = "ash" version = "0.38.0+1.3.281" @@ -423,6 +582,23 @@ dependencies = [ "syn 3.0.5", ] +[[package]] +name = "async-tungstenite" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc405d38be14342132609f06f02acaf825ddccfe76c4824a69281e0458ebd4" +dependencies = [ + "atomic-waker", + "futures-core", + "futures-io", + "futures-task", + "futures-util", + "log", + "pin-project-lite", + "tokio", + "tungstenite 0.28.0", +] + [[package]] name = "async_cell" version = "0.2.3" @@ -474,6 +650,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" dependencies = [ "axum-core", + "base64 0.22.1", "bytes", "form_urlencoded", "futures-util", @@ -486,14 +663,17 @@ dependencies = [ "matchit", "memchr 2.8.3", "mime", + "multer", "percent-encoding", "pin-project-lite", "serde_core", "serde_json", "serde_path_to_error", "serde_urlencoded", + "sha1", "sync_wrapper", "tokio", + "tokio-tungstenite 0.29.0", "tower", "tower-layer", "tower-service", @@ -530,6 +710,21 @@ dependencies = [ "tokio", ] +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", + "miniz_oxide 0.8.9", + "object", + "rustc-demangle", + "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "base16ct" version = "1.0.0" @@ -608,6 +803,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "blake3" version = "1.8.7" @@ -617,7 +821,7 @@ dependencies = [ "arrayvec 0.7.8", "cc", "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "constant_time_eq", + "constant_time_eq 0.4.2", "cpufeatures 0.3.1", ] @@ -639,6 +843,15 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + [[package]] name = "block-padding" version = "0.4.2" @@ -706,12 +919,29 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "bstr" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f" +dependencies = [ + "memchr 2.8.3", + "regex-automata", + "serde_core", +] + [[package]] name = "bumpalo" version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" +[[package]] +name = "bytecount" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" + [[package]] name = "bytemuck" version = "1.25.0" @@ -743,6 +973,9 @@ name = "bytes" version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" +dependencies = [ + "serde", +] [[package]] name = "bytesize" @@ -750,6 +983,25 @@ version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7354288c522e7e980fafd2075d63d1285794c3a6a16cdd492f189ea406e5f18b" +[[package]] +name = "bzip2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47" +dependencies = [ + "bzip2-sys", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.13+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" +dependencies = [ + "cc", + "pkg-config 0.3.34", +] + [[package]] name = "caseless" version = "0.2.2" @@ -759,13 +1011,22 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher 0.4.4", +] + [[package]] name = "cbc" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce2dc9ee5f88d11e0beb842c88b33c8a5cf0d1329c4b19494af42b07dbfe8896" dependencies = [ - "cipher", + "cipher 0.5.2", ] [[package]] @@ -777,7 +1038,7 @@ dependencies = [ "find-msvc-tools", "jobserver", "libc", - "shlex", + "shlex 2.0.1", ] [[package]] @@ -786,6 +1047,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" +[[package]] +name = "cff-parser" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f5b6e9141c036f3ff4ce7b2f7e432b0f00dee416ddcd4f17741d189ddc2e9d" + [[package]] name = "cfg-if" version = "1.0.4" @@ -806,6 +1073,17 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "cipher 0.4.4", + "cpufeatures 0.2.17", +] + [[package]] name = "chacha20" version = "0.10.2" @@ -813,72 +1091,186 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" dependencies = [ "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "cipher", + "cipher 0.5.2", "cpufeatures 0.3.1", "rand_core 0.10.1", ] +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead 0.5.2", + "chacha20 0.9.1", + "cipher 0.4.4", + "poly1305 0.8.0", + "zeroize", +] + [[package]] name = "chacha20poly1305" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b89e1c441e926b9c82a8d023f6e1b7ae0adcfaa7d621814e4d60789bac751cb" dependencies = [ - "aead", - "chacha20", - "cipher", - "poly1305", + "aead 0.6.1", + "chacha20 0.10.2", + "cipher 0.5.2", + "poly1305 0.9.1", ] [[package]] -name = "chrono" -version = "0.4.45" +name = "chromiumoxide" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +checksum = "26ed067eb6c1f660bdb87c05efb964421d2ca262bae0296cdfe38cf0cd949a3e" dependencies = [ - "iana-time-zone", - "js-sys", - "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "async-tungstenite", + "base64 0.22.1", + "bytes", + "chromiumoxide_cdp", + "chromiumoxide_types", + "dunce", + "fnv", + "futures", + "futures-timer", + "pin-project-lite", + "reqwest 0.13.5", "serde", - "wasm-bindgen", - "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json", + "thiserror 2.0.20", + "tokio", + "tracing", + "url", + "which 8.0.6", + "windows-registry", ] [[package]] -name = "cipher" -version = "0.5.2" +name = "chromiumoxide_cdp" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" +checksum = "68a6a03a7ebac4ea85308f285d6959a3e6b2ce32a0c9465dc7a7b1db0144eec7" dependencies = [ - "block-buffer 0.12.1", - "crypto-common 0.2.2", - "inout", + "chromiumoxide_pdl", + "chromiumoxide_types", + "serde", + "serde_json", ] [[package]] -name = "clap" -version = "4.6.6" +name = "chromiumoxide_pdl" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" +checksum = "c602dea92337bc4d824668d78c5b79c3b4ddb29b40dd7218282bbe8fd3fc2091" dependencies = [ - "clap_builder", - "clap_derive", + "chromiumoxide_types", + "either", + "heck", + "once_cell 1.21.4", + "proc-macro2", + "quote", + "regex", + "serde_json", ] [[package]] -name = "clap_builder" -version = "4.6.6" +name = "chromiumoxide_types" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" +checksum = "678d5146e74f16fc4a41978b275af572cd913de1f10270d2b93b6c276bc57d80" dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", + "serde", + "serde_json", ] [[package]] -name = "clap_derive" +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "serde", + "wasm-bindgen", + "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "chrono-tz" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6139a8597ed92cf816dfb33f5dd6cf0bb93a6adc938f11039f371bc5bcd26c3" +dependencies = [ + "chrono", + "phf 0.12.1", +] + +[[package]] +name = "chunked_transfer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901" + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common 0.1.7", + "inout 0.1.4", + "zeroize", +] + +[[package]] +name = "cipher" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" +dependencies = [ + "block-buffer 0.12.1", + "crypto-common 0.2.2", + "inout 0.2.2", +] + +[[package]] +name = "clap" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_complete" +version = "4.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037e2a1a92236d0aff7e845093f64661d6df4c02c9fcc61a60e9e1d736fa392f" +dependencies = [ + "clap", +] + +[[package]] +name = "clap_derive" version = "4.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" @@ -904,6 +1296,15 @@ dependencies = [ "error-code", ] +[[package]] +name = "clru" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "197fd99cb113a8d5d9b6376f3aa817f32c1078f2343b714fff7d2ca44fdf67d5" +dependencies = [ + "hashbrown 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "cmake" version = "0.1.58" @@ -919,6 +1320,33 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" +[[package]] +name = "color-eyre" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell 1.21.4", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8b88ea9df13354b55bc7234ebcce36e6ef896aca2e42a15de9e10edce01b427" +dependencies = [ + "once_cell 1.21.4", + "owo-colors", + "tracing-core", + "tracing-error", +] + [[package]] name = "color_quant" version = "1.1.0" @@ -931,6 +1359,16 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "colored" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" +dependencies = [ + "lazy_static", + "windows-sys 0.52.0", +] + [[package]] name = "combine" version = "4.6.8" @@ -968,8 +1406,8 @@ dependencies = [ "entities", "finl_unicode", "jetscii", - "phf", - "phf_codegen", + "phf 0.13.1", + "phf_codegen 0.13.1", "rustc-hash 2.1.3", "smallvec 1.16.1", "typed-arena", @@ -999,6 +1437,12 @@ dependencies = [ "typewit", ] +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + [[package]] name = "constant_time_eq" version = "0.4.2" @@ -1014,6 +1458,25 @@ dependencies = [ "unicode-segmentation 1.13.3", ] +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation 1.13.3", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation" version = "0.10.1" @@ -1030,6 +1493,12 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core_detect" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" + [[package]] name = "core_maths" version = "0.1.1" @@ -1039,6 +1508,16 @@ dependencies = [ "libm", ] +[[package]] +name = "cpu-time" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e393a7668fe1fad3075085b86c781883000b4ede868f43627b34a87c8b7ded" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "cpubits" version = "0.1.1" @@ -1063,6 +1542,21 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" + [[package]] name = "crc32fast" version = "1.5.2" @@ -1072,6 +1566,17 @@ dependencies = [ "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "cron" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5877d3fbf742507b66bc2a1945106bd30dd8504019d596901ddd012a4dd01740" +dependencies = [ + "chrono", + "once_cell 1.21.4", + "winnow 0.6.26", +] + [[package]] name = "crossbeam-channel" version = "0.5.17" @@ -1081,6 +1586,25 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-queue" version = "0.3.14" @@ -1113,6 +1637,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", + "rand_core 0.6.4", "typenum", ] @@ -1143,7 +1668,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baaca1c4b237092596f64d571e9db6ce4109c4ef9742e27590f1709594461f21" dependencies = [ - "cipher", + "cipher 0.5.2", ] [[package]] @@ -1184,6 +1709,94 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core 0.23.0", + "darling_macro 0.23.0", +] + +[[package]] +name = "darling" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec" +dependencies = [ + "darling_core 0.24.1", + "darling_macro 0.24.1", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.119", +] + +[[package]] +name = "darling_core" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6837e2cf7485aaae18f86181d2f0e9a7ed297a025e220aeabf63fdebd3a2ddff" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 3.0.5", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core 0.23.0", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "darling_macro" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785" +dependencies = [ + "darling_core 0.24.1", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "dashmap" +version = "6.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" +dependencies = [ + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell 1.21.4", + "parking_lot_core", +] + +[[package]] +name = "data-encoding" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" + [[package]] name = "data-url" version = "0.3.2" @@ -1231,43 +1844,103 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9244323129647178bf41ac861a2cdb9d9c81b9b09d3d0d1de9cd302b33b8a1d" [[package]] -name = "delegate-display" -version = "3.0.1" +name = "deflate64" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54e3a499943fd5180aeffcb708164ffb22125ceeccb05491b5297981d37d6249" +checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2" + +[[package]] +name = "defmt" +version = "0.3.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad" dependencies = [ - "impartial-ord", - "itoa", - "macroific 3.1.0", - "proc-macro2", - "quote", - "syn 3.0.5", + "defmt 1.1.1", ] [[package]] -name = "deranged" -version = "0.5.8" +name = "defmt" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] [[package]] -name = "derivative" -version = "2.2.0" +name = "defmt-macros" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" dependencies = [ + "defmt-parser", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.119", ] [[package]] -name = "derive_more" +name = "defmt-parser" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" dependencies = [ - "derive_more-impl 1.0.0", + "thiserror 2.0.20", +] + +[[package]] +name = "delegate-display" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54e3a499943fd5180aeffcb708164ffb22125ceeccb05491b5297981d37d6249" +dependencies = [ + "impartial-ord", + "itoa", + "macroific 3.1.0", + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "serde_core", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl 1.0.0", ] [[package]] @@ -1296,6 +1969,7 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ + "convert_case 0.10.0", "proc-macro2", "quote", "rustc_version", @@ -1311,6 +1985,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", "crypto-common 0.1.7", + "subtle", ] [[package]] @@ -1325,6 +2000,27 @@ dependencies = [ "ctutils", ] +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys 0.4.1", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.4.6", + "windows-sys 0.48.0", +] + [[package]] name = "dirs-sys" version = "0.5.0" @@ -1333,7 +2029,7 @@ checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", - "redox_users", + "redox_users 0.5.3", "windows-sys 0.61.2", ] @@ -1370,6 +2066,21 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "ecb" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a8bfa975b1aec2145850fcaa1c6fe269a16578c44705a532ae3edc92b8881c7" +dependencies = [ + "cipher 0.4.4", +] + [[package]] name = "ed25519" version = "3.0.0" @@ -1402,13 +2113,44 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" +[[package]] +name = "email-encoding" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "420b9da095f052ea597503e39073b5b3c522f7db933fbac202d91d24492693fd" +dependencies = [ + "base64 0.23.1", + "memchr 2.8.3", +] + +[[package]] +name = "email_address" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" + [[package]] name = "emojis" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b1514ced566c94991ed9563258ecf61e311fd773bf0a3f20606830386bf66e3" dependencies = [ - "phf", + "phf 0.13.1", +] + +[[package]] +name = "encoding_rs" +version = "0.8.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5ef0006ac9ab233c38522f5ae99cae3625151de8f706cacee1cba4b8e2832a" +dependencies = [ + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "core_detect", + "multiversion", + "multiversion_no_op", + "rustversion", + "scopeguard", + "simdutf8", ] [[package]] @@ -1417,12 +2159,30 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + [[package]] name = "entities" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca" +[[package]] +name = "enum-as-inner" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "enumflags2" version = "0.7.12" @@ -1444,6 +2204,45 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "env_filter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_filter" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_home" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" + +[[package]] +name = "env_logger" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" +dependencies = [ + "anstream", + "anstyle", + "env_filter 2.0.0", + "jiff", + "log", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -1470,6 +2269,15 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b5343afd4a8365a643ac588dab4cf234a190c7f6c88c9f6dd6ffe00837661b7" +[[package]] +name = "euclid" +version = "0.20.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bb7ef65b3777a325d1eeefefab5b6d4959da54747e33bd6258e789640f307ad" +dependencies = [ + "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "euclid" version = "0.22.14" @@ -1539,6 +2347,17 @@ dependencies = [ "smallvec 1.16.1", ] +[[package]] +name = "eyre" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08309dbcc659c5549a24ddb9b27027640641b282ef5768267c7e675558986a3" +dependencies = [ + "autocfg", + "indenter", + "once_cell 1.21.4", +] + [[package]] name = "fallible-iterator" version = "0.3.0" @@ -1574,6 +2393,18 @@ dependencies = [ "syn 3.0.5", ] +[[package]] +name = "faster-hex" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04839bdf9d8c10f66806fad16b852fc72aab80873aebc3cb69d85b4fa41543ed" +dependencies = [ + "autocfg", + "defmt 0.3.100", + "heapless", + "serde", +] + [[package]] name = "fastrand" version = "2.5.0" @@ -1586,6 +2417,17 @@ version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a" +[[package]] +name = "fd-lock" +version = "4.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" +dependencies = [ + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "rustix", + "windows-sys 0.52.0", +] + [[package]] name = "fdeflate" version = "0.3.7" @@ -1601,6 +2443,16 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" +[[package]] +name = "filetime" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" +dependencies = [ + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", +] + [[package]] name = "find-msvc-tools" version = "0.1.12" @@ -1613,6 +2465,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80bb028c8b4148c9ee0cca68fcd9add6044e81d3619f48577ddf13a263d047a2" +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + [[package]] name = "flate2" version = "1.1.10" @@ -1636,6 +2494,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "foldhash" version = "0.2.0" @@ -1678,12 +2542,41 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs-err" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91aa448ca50d7e79433bdf3ee8d99215430d2ec02ade5aefab2a073a1822e8a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "fs_extra" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + [[package]] name = "futures" version = "0.3.34" @@ -1709,6 +2602,19 @@ dependencies = [ "futures-sink", ] +[[package]] +name = "futures-concurrency" +version = "7.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175cd8cca9e1d45b87f18ffa75088f2099e3c4fe5e2f83e42de112560bea8ea6" +dependencies = [ + "fixedbitset", + "futures-core", + "futures-lite", + "pin-project", + "smallvec 1.16.1", +] + [[package]] name = "futures-core" version = "0.3.34" @@ -1768,6 +2674,12 @@ version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" +[[package]] +name = "futures-timer" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968" + [[package]] name = "futures-util" version = "0.3.34" @@ -1886,6 +2798,20 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", +] + [[package]] name = "getrandom" version = "0.4.3" @@ -1895,7 +2821,7 @@ dependencies = [ "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "js-sys", "libc", - "r-efi", + "r-efi 6.0.0", "rand_core 0.10.1", "wasm-bindgen", ] @@ -1911,2249 +2837,4207 @@ dependencies = [ ] [[package]] -name = "gloo-timers" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "gix" +version = "0.79.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66adef5e4d836ad08bf424dce5e3eb18f51544eee702860419295120dd48811" +dependencies = [ + "gix-actor", + "gix-attributes", + "gix-command", + "gix-commitgraph", + "gix-config", + "gix-date", + "gix-diff", + "gix-discover", + "gix-error", + "gix-features", + "gix-filter", + "gix-fs", + "gix-glob", + "gix-hash", + "gix-hashtable", + "gix-ignore", + "gix-index", + "gix-lock", + "gix-object", + "gix-odb", + "gix-pack", + "gix-path", + "gix-pathspec", + "gix-protocol", + "gix-ref", + "gix-refspec", + "gix-revision", + "gix-revwalk", + "gix-sec", + "gix-shallow", + "gix-submodule", + "gix-tempfile", + "gix-trace", + "gix-traverse", + "gix-url", + "gix-utils", + "gix-validate", + "gix-worktree", + "smallvec 1.16.1", + "thiserror 2.0.20", ] [[package]] -name = "gloo-timers" -version = "0.4.0" +name = "gix-actor" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "482ce8a491a501da4cd806bd190275363d674f2845005c6ddbd5d3e1dd54495d" +checksum = "0c44f13049925e8dc3955c20ecec5391cedfb041e0952416b583ecc57bc68325" dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", + "bstr", + "gix-date", + "gix-error", + "winnow 0.7.15", ] [[package]] -name = "gloo-utils" -version = "0.3.0" +name = "gix-attributes" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4202275d95a142fa209a1e35e91c250a710c5600731372cd3464a39ed01573d6" +checksum = "9e72da5a1c35c9a129be0c60ab9968779981ca50835dd98650ecd8b0ea4d721e" dependencies = [ - "js-sys", - "serde", - "serde_json", - "wasm-bindgen", - "web-sys", + "bstr", + "gix-glob", + "gix-path", + "gix-quote", + "gix-trace", + "kstring", + "smallvec 1.16.1", + "thiserror 2.0.20", + "unicode-bom", ] [[package]] -name = "growable-bloom-filter" -version = "2.1.1" +name = "gix-bitmap" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d174ccb4ba660d431329e7f0797870d0a4281e36353ec4b4a3c5eab6c2cfb6f1" +checksum = "d982fc7ef0608e669851d0d2a6141dae74c60d5a27e8daa451f2a4857bbf41e2" dependencies = [ - "serde", - "serde_bytes", - "serde_derive", - "xxhash-rust", + "thiserror 2.0.20", ] [[package]] -name = "h2" -version = "0.4.19" +name = "gix-chunk" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16" +checksum = "d14ee09ab454481a91fe969ca5afbd41c8a9b05680197b6554ebb69bdcf7d571" dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap 2.14.2", - "slab", - "tokio", - "tokio-util", - "tracing", + "gix-error", ] [[package]] -name = "half" -version = "2.7.1" +name = "gix-command" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +checksum = "2962172c6f78731e2b7773bf762f7b8d1746a342a4c0a8914a612206e1295953" dependencies = [ - "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "crunchy 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", - "zerocopy 0.8.57", + "bstr", + "gix-path", + "gix-quote", + "gix-trace", + "shell-words", ] [[package]] -name = "hashbrown" -version = "0.16.1" +name = "gix-commitgraph" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9dc2a550978b510b4e58b0bf5a15481433c3b21981330f3898d93f25b07d9a5" dependencies = [ - "foldhash 0.2.0", + "bstr", + "gix-chunk", + "gix-error", + "gix-hash", + "memmap2", ] [[package]] -name = "hashbrown" -version = "0.16.1" +name = "gix-config" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +checksum = "db778c8703f3c7f687e03ce22ac8f6eac02adbdafae4cb19d541126fa012524f" dependencies = [ - "allocator-api2", - "equivalent 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "foldhash 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bstr", + "gix-config-value", + "gix-features", + "gix-glob", + "gix-path", + "gix-ref", + "gix-sec", + "memchr 2.8.3", + "smallvec 1.16.1", + "thiserror 2.0.20", + "unicode-bom", + "winnow 0.7.15", ] [[package]] -name = "hashbrown" -version = "0.17.1" +name = "gix-config-value" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +checksum = "4378c53ec3db049919edf91ff76f56f28886a8b4b4a5a9dc633108d84afc3675" dependencies = [ - "foldhash 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 2.13.2", + "bstr", + "gix-path", + "libc", + "thiserror 2.0.20", ] [[package]] -name = "hashlink" -version = "0.12.2" +name = "gix-date" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a596f1b20ed2cc5ecac41a164aaebc7258057060f06c0cf7a2ba3991ee7990fb" +checksum = "e66a5117b22495fe7cb4b443777cf3f024a1b1db0009771db440fc8b38a0a6fd" dependencies = [ - "hashbrown 0.17.1", + "bstr", + "gix-error", + "itoa", + "jiff", + "smallvec 1.16.1", ] [[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hermit-abi" -version = "0.5.3" +name = "gix-diff" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" +checksum = "ebee256b21a7d46cec85ab84e824742142a23a21111556a5e50c15796110c6c6" +dependencies = [ + "bstr", + "gix-command", + "gix-filter", + "gix-fs", + "gix-hash", + "gix-object", + "gix-path", + "gix-tempfile", + "gix-trace", + "gix-traverse", + "gix-worktree", + "imara-diff", + "thiserror 2.0.20", +] [[package]] -name = "hex" -version = "0.4.3" +name = "gix-discover" +version = "0.47.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hexf-parse" -version = "0.2.1" +checksum = "93103d88576e048a681ebee83e93162972cd4cbce1485a6137cfc98fc0ba152d" +dependencies = [ + "bstr", + "dunce", + "gix-fs", + "gix-path", + "gix-ref", + "gix-sec", + "thiserror 2.0.20", +] [[package]] -name = "hilog-sys" -version = "0.1.8" +name = "gix-error" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f715f263b6e73aa2322b59057134b4daf56a765cbebc2f6b35e640ffded3361b" +checksum = "92e37b10e97c822fc17550fc1a187283ad3ce79617e920bf179f301ee12abcbc" +dependencies = [ + "bstr", +] [[package]] -name = "hkdf" -version = "0.13.0" +name = "gix-features" +version = "0.46.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018" +checksum = "752493cd4b1d5eaaa0138a7493f65c96863fefa990fc021e0e519579e389ab20" dependencies = [ - "hmac", + "crc32fast", + "crossbeam-channel", + "gix-path", + "gix-trace", + "gix-utils", + "libc", + "once_cell 1.21.4", + "parking_lot", + "prodash", + "thiserror 2.0.20", + "walkdir", + "zlib-rs", ] [[package]] -name = "hmac" -version = "0.13.0" +name = "gix-filter" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +checksum = "094253121df9aa1cb46d1da0200dd63ebf16263a35f03295109978bf7ed2b483" dependencies = [ - "digest 0.11.3", + "bstr", + "encoding_rs", + "gix-attributes", + "gix-command", + "gix-hash", + "gix-object", + "gix-packetline", + "gix-path", + "gix-quote", + "gix-trace", + "gix-utils", + "smallvec 1.16.1", + "thiserror 2.0.20", ] [[package]] -name = "hpke" -version = "0.14.1" +name = "gix-fs" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5324110b02044183df000f0bd7d2d7e61f1000631508627e77f63983b6fdffb" +checksum = "a964b4aec683eb0bacb87533defa80805bb4768056371a47ab38b00a2d377b72" dependencies = [ - "aead", - "chacha20poly1305", - "getrandom 0.4.3", - "hkdf", - "hybrid-array", - "rand_core 0.10.1", - "sha2 0.11.0", - "subtle", - "x25519-dalek", - "zeroize", + "bstr", + "fastrand", + "gix-features", + "gix-path", + "gix-utils", + "thiserror 2.0.20", ] [[package]] -name = "html5ever" -version = "0.39.0" +name = "gix-glob" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a1761807faccc9a19e86944bbf40610014066306f96edcdedc2fb714bcb7b8" +checksum = "b03e6cd88cc0dc1eafa1fddac0fb719e4e74b6ea58dd016e71125fde4a326bee" dependencies = [ - "log", - "markup5ever", + "bitflags 2.13.2", + "bstr", + "gix-features", + "gix-path", ] [[package]] -name = "htmlize" -version = "1.1.0" +name = "gix-hash" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e815d50d9e411ba2690d730e6ec139c08260dddb756df315dbd16d01a587226" +checksum = "d8ced05d2d7b13bff08b2f7eb4e47cfeaf00b974c2ddce08377c4fe1f706b3eb" dependencies = [ - "memchr 2.8.3", - "pastey", - "phf", - "phf_codegen", - "serde_json", + "faster-hex", + "gix-features", + "sha1-checked", + "thiserror 2.0.20", ] [[package]] -name = "http" -version = "1.5.0" +name = "gix-hashtable" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +checksum = "52f1eecdd006390cbed81f105417dbf82a6fe40842022006550f2e32484101da" dependencies = [ - "bytes", - "itoa", + "gix-hash", + "hashbrown 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot", ] [[package]] -name = "http-body" -version = "1.1.0" +name = "gix-ignore" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +checksum = "09f915dcf6911e3027537166d34e13f0fe101ed12225178d2ae29cd1272cff26" dependencies = [ - "bytes", - "http", + "bstr", + "gix-glob", + "gix-path", + "gix-trace", + "unicode-bom", ] [[package]] -name = "http-body-util" -version = "0.1.5" +name = "gix-index" +version = "0.47.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" +checksum = "ea450ed666a39676d7e9a41b899487d1645d88053bc8c8cecfca0cf96fcd4a03" dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", + "bitflags 2.13.2", + "bstr", + "filetime", + "fnv", + "gix-bitmap", + "gix-features", + "gix-fs", + "gix-hash", + "gix-lock", + "gix-object", + "gix-traverse", + "gix-utils", + "gix-validate", + "hashbrown 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa", + "libc", + "memmap2", + "rustix", + "smallvec 1.16.1", + "thiserror 2.0.20", ] [[package]] -name = "httparse" -version = "1.10.1" +name = "gix-lock" +version = "21.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +checksum = "054fbd0989700c69dc5aa80bc66944f05df1e15aa7391a9e42aca7366337905f" +dependencies = [ + "gix-tempfile", + "gix-utils", + "thiserror 2.0.20", +] [[package]] -name = "httpdate" -version = "1.0.3" +name = "gix-object" +version = "0.56.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +checksum = "227e12fad42022ff08d6fbcc4bae34d6e2aa180576e9e1106d9a29a06798e750" +dependencies = [ + "bstr", + "gix-actor", + "gix-date", + "gix-features", + "gix-hash", + "gix-hashtable", + "gix-path", + "gix-utils", + "gix-validate", + "itoa", + "smallvec 1.16.1", + "thiserror 2.0.20", + "winnow 0.7.15", +] [[package]] -name = "hybrid-array" -version = "0.4.15" +name = "gix-odb" +version = "0.76.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" +checksum = "e84f7e115b2b6615f0c5acddc269598ebc539c363fb276dbd242755a1dd7126c" dependencies = [ - "typenum", + "arc-swap", + "gix-features", + "gix-fs", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-pack", + "gix-path", + "gix-quote", + "parking_lot", + "tempfile", + "thiserror 2.0.20", ] [[package]] -name = "hyper" -version = "1.11.1" +name = "gix-pack" +version = "0.66.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" +checksum = "dfc20b54d50f64fb02281f2a6c4a24bb9356befdf4535d5a68924575fd67cd5e" dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", + "clru", + "gix-chunk", + "gix-error", + "gix-features", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-path", + "memmap2", "smallvec 1.16.1", - "tokio", - "want", + "thiserror 2.0.20", + "uluru", ] [[package]] -name = "hyper-rustls" -version = "0.27.9" +name = "gix-packetline" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +checksum = "b217dd0ee0c4021ecf169a4a519b1b4f80d15e3f3765f3dc466223dc0ac891d7" dependencies = [ - "http", - "hyper", - "hyper-util", - "rustls", - "tokio", - "tokio-rustls", - "tower-service", + "bstr", + "faster-hex", + "gix-trace", + "thiserror 2.0.20", ] [[package]] -name = "hyper-util" -version = "0.1.20" +name = "gix-path" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +checksum = "c8fd1fe596dc393b538e1d5492c5585971a9311475b3255f7b889023df208476" dependencies = [ - "base64 0.22.1", - "bytes", - "futures-channel", - "futures-util", - "http", - "http-body", - "hyper", - "ipnet", - "libc", - "percent-encoding", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", + "bstr", + "gix-trace", + "gix-validate", + "thiserror 2.0.20", ] [[package]] -name = "i_float" -version = "3.0.0" - -[[package]] -name = "i_key_sort" -version = "0.11.0" +name = "gix-pathspec" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f4cc23f55ca7c190bf243f1a4e2139d4522022f724fb0dfc06c93f65a01ef6" +dependencies = [ + "bitflags 2.13.2", + "bstr", + "gix-attributes", + "gix-config-value", + "gix-glob", + "gix-path", + "thiserror 2.0.20", +] [[package]] -name = "i_overlay" -version = "7.0.3" +name = "gix-protocol" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13680c03e847d8f32a59cf1511dd05334d429da33f5af08891367680f15cbca" dependencies = [ - "i_float", - "i_key_sort", - "i_shape", - "i_tree", + "bstr", + "gix-date", + "gix-features", + "gix-hash", + "gix-ref", + "gix-shallow", + "gix-transport", + "gix-utils", + "maybe-async", + "thiserror 2.0.20", + "winnow 0.7.15", ] [[package]] -name = "i_shape" -version = "3.0.0" +name = "gix-quote" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96fc2ff2ec8cc0c92807f02eab1f00eb02619fc2810d13dc42679492fcc36757" dependencies = [ - "i_float", + "bstr", + "gix-utils", + "thiserror 2.0.20", ] [[package]] -name = "i_tree" -version = "0.19.0" +name = "gix-ref" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92fd2e86d65efe972a5226f303ed72cfb49a8ad03740e5cc2b27c07970a5d78" +dependencies = [ + "gix-actor", + "gix-features", + "gix-fs", + "gix-hash", + "gix-lock", + "gix-object", + "gix-path", + "gix-tempfile", + "gix-utils", + "gix-validate", + "memmap2", + "thiserror 2.0.20", + "winnow 0.7.15", +] [[package]] -name = "iana-time-zone" -version = "0.1.65" +name = "gix-refspec" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +checksum = "40adba15f8099159d37d0a21e1cfb6602c2e49b6c05f6f8a57a47d0fb21611af" dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core 0.62.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bstr", + "gix-error", + "gix-glob", + "gix-hash", + "gix-revision", + "gix-validate", + "smallvec 1.16.1", + "thiserror 2.0.20", ] [[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" +name = "gix-revision" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +checksum = "a75ef94c9d76de0765e429ae22a01c512c0d50459269195a90ea11099a5fc752" dependencies = [ - "cc", + "bitflags 2.13.2", + "bstr", + "gix-commitgraph", + "gix-date", + "gix-error", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-revwalk", + "gix-trace", ] [[package]] -name = "icu_collections" -version = "2.3.0" +name = "gix-revwalk" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +checksum = "07efcad1d064abdac3e79dfc6e23e05accb579559d015e944c9dcf64be95eb49" dependencies = [ - "displaydoc", - "potential_utf", - "utf8_iter", - "yoke", - "zerofrom", - "zerovec", + "gix-commitgraph", + "gix-date", + "gix-error", + "gix-hash", + "gix-hashtable", + "gix-object", + "smallvec 1.16.1", + "thiserror 2.0.20", ] [[package]] -name = "icu_locale_core" -version = "2.3.0" +name = "gix-sec" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +checksum = "283f4a746c9bde8550be63e6f961ff4651f412ca12666e8f5615f39464960ab9" dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", + "bitflags 2.13.2", + "gix-path", + "libc", + "windows-sys 0.61.2", ] [[package]] -name = "icu_normalizer" -version = "2.3.0" +name = "gix-shallow" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +checksum = "189386b5da5285216cc0ede89eff5a943d5261fc794241ee6ec5360b77df15ad" dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec 1.16.1", - "zerovec", + "bstr", + "gix-hash", + "gix-lock", + "thiserror 2.0.20", ] [[package]] -name = "icu_normalizer_data" -version = "2.3.0" +name = "gix-submodule" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" +checksum = "ac5d38afa855046b7b9e2b85f8cdf7fbf2e449ed061dc81fa7a757abaa38bfac" +dependencies = [ + "bstr", + "gix-config", + "gix-path", + "gix-pathspec", + "gix-refspec", + "gix-url", + "thiserror 2.0.20", +] [[package]] -name = "icu_properties" -version = "2.3.0" +name = "gix-tempfile" +version = "21.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +checksum = "d22227f6b203f511ff451c33c89899e87e4f571fc596b06f68e6e613a6508528" dependencies = [ - "displaydoc", - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", + "dashmap", + "gix-fs", + "libc", + "parking_lot", + "tempfile", ] [[package]] -name = "icu_properties_data" -version = "2.3.0" +name = "gix-trace" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" +checksum = "be3eb81d9dc914335923e50d52829c551feefd6a72d176c4130c546b67a60814" [[package]] -name = "icu_provider" -version = "2.3.1" +name = "gix-transport" +version = "0.54.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" +checksum = "9561a98f4f1cada03b565121c475c95d060998082bdb1277044fa6e11fe2aa17" dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", + "bstr", + "gix-command", + "gix-features", + "gix-packetline", + "gix-quote", + "gix-sec", + "gix-url", + "thiserror 2.0.20", ] [[package]] -name = "idna" -version = "1.1.0" +name = "gix-traverse" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +checksum = "bcdd399f8527f643f8d4fd8de6ec3b6e2c3c826b758b68e90f28275af2e7b33a" dependencies = [ - "idna_adapter", + "bitflags 2.13.2", + "gix-commitgraph", + "gix-date", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-revwalk", "smallvec 1.16.1", - "utf8_iter", + "thiserror 2.0.20", ] [[package]] -name = "idna_adapter" -version = "1.2.2" +name = "gix-url" +version = "0.35.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +checksum = "1a61ead12e33fa52ae92b207ee27554f646a8e7a3dad8b78da1582ec91eda0a6" dependencies = [ - "icu_normalizer", - "icu_properties", + "bstr", + "gix-path", + "percent-encoding", + "thiserror 2.0.20", ] [[package]] -name = "image" -version = "0.25.10" +name = "gix-utils" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +checksum = "0da1c46491b49458a446cc76f0085860f8164c2290742e0aa8c653ce67240a97" dependencies = [ - "bytemuck 1.25.2", - "byteorder-lite", - "moxcms", - "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", - "png 0.18.1", - "tiff", - "zune-core", - "zune-jpeg", + "fastrand", + "getrandom 0.4.3", + "unicode-normalization", ] [[package]] -name = "image-webp" -version = "0.2.4" +name = "gix-validate" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" +checksum = "4dae8780f63ed8a803b8bdabbd7aa5f5c5d74592c8b50eed875c1bb4f6545a6a" dependencies = [ - "byteorder-lite", - "quick-error", + "bstr", ] [[package]] -name = "imagesize" -version = "0.14.0" +name = "gix-worktree" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09e54e57b4c48b40f7aec75635392b12b3421fa26fe8b4332e63138ed278459c" +checksum = "93381c5cd37fa208e3f454433425985ea02725369fb33a79ff4ecf7c279f2f98" +dependencies = [ + "bstr", + "gix-attributes", + "gix-fs", + "gix-glob", + "gix-hash", + "gix-ignore", + "gix-index", + "gix-object", + "gix-path", + "gix-validate", +] [[package]] -name = "imbl" -version = "7.0.2" +name = "glob" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46bad832b9b463ed9398b8506488cc2e3b897a9d44f13af115f382aac71f4fec" -dependencies = [ - "archery", - "equivalent 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "imbl-sized-chunks", - "rand_core 0.9.5", - "rand_xoshiro", - "serde_core", - "version_check", - "wide", -] +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" [[package]] -name = "imbl-sized-chunks" -version = "0.2.0" +name = "globset" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0813be332553f857953298749fa19549e8b61b80589757c29b4e2a804fa9c6" +checksum = "07c34a9410465b45bd9787443bc7370f37735bad04b0f0cd57ff1a3186c98988" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] [[package]] -name = "impartial-ord" -version = "2.0.0" +name = "gloo-timers" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc680e03d1a08bdc1c01b60e0c39723430ced63d8cabd93cee0449b6ba916a25" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.5", + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", ] [[package]] -name = "indexmap" -version = "2.13.0" +name = "gloo-timers" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "482ce8a491a501da4cd806bd190275363d674f2845005c6ddbd5d3e1dd54495d" dependencies = [ - "equivalent 1.0.2", - "hashbrown 0.16.1", + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", ] [[package]] -name = "indexmap" -version = "2.14.2" +name = "gloo-utils" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +checksum = "4202275d95a142fa209a1e35e91c250a710c5600731372cd3464a39ed01573d6" dependencies = [ - "equivalent 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "hashbrown 0.17.1", + "js-sys", "serde", - "serde_core", + "serde_json", + "wasm-bindgen", + "web-sys", ] [[package]] -name = "inout" -version = "0.2.2" +name = "goblin" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" +checksum = "17582616a7718cca54cec18e534a76c7c4aec11a8b9a85695712f262fd15a4c8" dependencies = [ - "block-padding", - "hybrid-array", + "log", + "plain", + "scroll", ] [[package]] -name = "ipnet" -version = "2.12.2" +name = "growable-bloom-filter" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" +checksum = "d174ccb4ba660d431329e7f0797870d0a4281e36353ec4b4a3c5eab6c2cfb6f1" +dependencies = [ + "serde", + "serde_bytes", + "serde_derive", + "xxhash-rust", +] [[package]] -name = "is_terminal_polyfill" -version = "1.70.2" +name = "h2" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.14.2", + "slab", + "tokio", + "tokio-util", + "tracing", +] [[package]] -name = "itertools" -version = "0.14.0" +name = "half" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ - "either", + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "crunchy 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "zerocopy 0.8.57", ] [[package]] -name = "itertools" -version = "0.15.0" +name = "hash32" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b4baf93f58d4425749ca49a51c50ebab072c5df6994d08fed93541c331481dc" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" dependencies = [ - "either", + "byteorder 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "itoa" -version = "1.0.18" +name = "hashbrown" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] -name = "jetscii" -version = "0.5.3" +name = "hashbrown" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47f142fe24a9c9944451e8349de0a56af5f3e7226dc46f3ed4d4ecc0b85af75e" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] -name = "jni" -version = "0.21.1" +name = "hashbrown" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "cesu8", - "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "combine", - "jni-sys 0.3.1", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", + "allocator-api2", + "equivalent 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "foldhash 0.1.5", ] [[package]] -name = "jni" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +name = "hashbrown" +version = "0.16.1" dependencies = [ - "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "combine", - "jni-macros", - "jni-sys 0.4.1", - "log", - "simd_cesu8", - "thiserror 2.0.20", - "walkdir", - "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "foldhash 0.2.0", ] [[package]] -name = "jni-macros" -version = "0.22.4" +name = "hashbrown" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ - "proc-macro2", - "quote", - "rustc_version", - "simd_cesu8", - "syn 2.0.119", + "allocator-api2", + "equivalent 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "foldhash 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "jni-sys" -version = "0.3.1" +name = "hashbrown" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" dependencies = [ - "jni-sys 0.4.1", + "foldhash 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "jni-sys" -version = "0.4.1" +name = "hashlink" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +checksum = "a596f1b20ed2cc5ecac41a164aaebc7258057060f06c0cf7a2ba3991ee7990fb" dependencies = [ - "jni-sys-macros", + "hashbrown 0.17.1", ] [[package]] -name = "jni-sys-macros" -version = "0.4.1" +name = "heapless" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" dependencies = [ - "quote", - "syn 2.0.119", + "hash32", + "stable_deref_trait", ] [[package]] -name = "jobserver" -version = "0.1.35" +name = "heck" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" -dependencies = [ - "getrandom 0.4.3", - "libc", -] +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] -name = "js-sys" -version = "0.3.105" +name = "hermit-abi" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e" -dependencies = [ - "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-util", - "wasm-bindgen", -] +checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" [[package]] -name = "js_int" -version = "0.2.2" +name = "hex" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d937f95470b270ce8b8950207715d71aa8e153c0d44c6684d59397ed4949160a" -dependencies = [ - "serde", -] +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] -name = "js_option" -version = "0.2.0" +name = "hexf-parse" +version = "0.2.1" + +[[package]] +name = "hilog-sys" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7dd3e281add16813cf673bf74a32249b0aa0d1c8117519a17b3ada5e8552b3c" -dependencies = [ - "serde_core", -] +checksum = "f715f263b6e73aa2322b59057134b4daf56a765cbebc2f6b35e640ffded3361b" [[package]] -name = "json5" -version = "1.3.1" +name = "hkdf" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "733a844dbd6fef128e98cb4487b887cb55454d92cd9994b1bafe004fabbe670c" +checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018" dependencies = [ - "serde", - "ucd-trie", + "hmac 0.13.0", ] [[package]] -name = "konst" -version = "0.4.3" +name = "hmac" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f660d5f887e3562f9ab6f4a14988795b694099d66b4f5dedc02d197ba9becb1d" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "const_panic", - "typewit", + "digest 0.10.7", ] [[package]] -name = "kurbo" -version = "0.13.1" +name = "hmac" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b60dfc32f652b926df6192e55525b16d186c69d47876c3ead4da5cc9f8450e2" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" dependencies = [ - "arrayvec 0.7.8", - "euclid", - "polycool", - "smallvec 1.16.1", + "digest 0.11.3", ] [[package]] -name = "language-tags" -version = "0.3.2" +name = "hnsw_rs" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" +checksum = "43a5258f079b97bf2e8311ff9579e903c899dcbac0d9a138d62e9a066778bd07" +dependencies = [ + "anndists", + "anyhow", + "bincode", + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "cpu-time", + "env_logger", + "hashbrown 0.15.5", + "indexmap 2.14.2", + "lazy_static", + "log", + "mmap-rs", + "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus", + "parking_lot", + "rand 0.9.5", + "rayon", + "serde", +] [[package]] -name = "lazy_static" -version = "1.5.0" +name = "home" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] [[package]] -name = "libc" -version = "0.2.189" +name = "hound" +version = "3.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" +checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f" [[package]] -name = "libloading" -version = "0.8.9" +name = "hpke" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5324110b02044183df000f0bd7d2d7e61f1000631508627e77f63983b6fdffb" dependencies = [ - "cfg-if 1.0.4", - "windows-link 0.2.1", + "aead 0.6.1", + "chacha20poly1305 0.11.0", + "getrandom 0.4.3", + "hkdf", + "hybrid-array", + "rand_core 0.10.1", + "sha2 0.11.0", + "subtle", + "x25519-dalek", + "zeroize", ] [[package]] -name = "libloading" -version = "0.8.9" +name = "htmd" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +checksum = "a5a1c7113c831fec68cbd79cd8bf281a84e5b6943f51473dc266b0b88a6a017e" dependencies = [ - "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "html5ever 0.38.0", + "markup5ever_rcdom 0.38.0+unofficial", + "phf 0.13.1", ] [[package]] -name = "libm" -version = "0.2.16" +name = "html5ever" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" +checksum = "55d958c2f74b664487a2035fe1dadb032c48718a03b63f3ab0b8537db8549ed4" +dependencies = [ + "log", + "markup5ever 0.35.0", + "match_token", +] [[package]] -name = "libredox" -version = "0.1.24" +name = "html5ever" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6480ccc157a1389bb2e4891b24751b0f798ba640d22386f23143fbcc89da195a" +checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2" dependencies = [ - "libc", + "log", + "markup5ever 0.38.0", ] [[package]] -name = "libsqlite3-sys" -version = "0.38.2" +name = "html5ever" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1d20bef17f513b9b3004532233187769cd072d790971f4e4da0e346eb6401e8" +checksum = "46a1761807faccc9a19e86944bbf40610014066306f96edcdedc2fb714bcb7b8" dependencies = [ - "cc", - "pkg-config 0.3.34", - "vcpkg", + "log", + "markup5ever 0.39.0", ] [[package]] -name = "linkify" -version = "0.10.0" +name = "htmlize" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1dfa36d52c581e9ec783a7ce2a5e0143da6237be5811a0b3153fedfdbe9f780" +checksum = "2e815d50d9e411ba2690d730e6ec139c08260dddb756df315dbd16d01a587226" dependencies = [ "memchr 2.8.3", + "pastey 0.1.1", + "phf 0.13.1", + "phf_codegen 0.13.1", + "serde_json", ] [[package]] -name = "linux-raw-sys" -version = "0.12.1" +name = "http" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] [[package]] -name = "litemap" -version = "0.8.3" +name = "http-body" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] [[package]] -name = "lock_api" -version = "0.4.14" +name = "http-body-util" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" dependencies = [ - "scopeguard", + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", ] [[package]] -name = "log" -version = "0.4.34" +name = "httparse" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] -name = "loom" -version = "0.7.2" +name = "httpdate" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" -dependencies = [ - "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "generator", - "scoped-tls 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "tracing", - "tracing-subscriber", -] +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] -name = "lru" -version = "0.16.4" +name = "hybrid-array" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" dependencies = [ - "hashbrown 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)", + "typenum", ] [[package]] -name = "lru-slab" -version = "0.1.3" +name = "hyper" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4050469837a6ff301cd14c1f8f24f88549e6d548f24f64e2148eb0f72cebc51f" +checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec 1.16.1", + "tokio", + "want", +] [[package]] -name = "macroific" -version = "2.0.0" +name = "hyper-rustls" +version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89f276537b4b8f981bf1c13d79470980f71134b7bdcc5e6e911e910e556b0285" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ - "macroific_attr_parse 2.0.0", - "macroific_core 2.0.0", - "macroific_macro 2.0.0", + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-native-certs", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", ] [[package]] -name = "macroific" -version = "3.1.0" +name = "hyper-util" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc8ab95c98294764c0f1eacebdb5b027849201b71fbc383871fe991a1de7eb83" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ - "macroific_attr_parse 3.1.0", - "macroific_core 3.1.0", - "macroific_macro 3.1.0", + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", ] [[package]] -name = "macroific_attr_parse" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad4023761b45fcd36abed8fb7ae6a80456b0a38102d55e89a57d9a594a236be9" +name = "i_float" +version = "3.0.0" + +[[package]] +name = "i_key_sort" +version = "0.11.0" + +[[package]] +name = "i_overlay" +version = "7.0.3" dependencies = [ - "proc-macro2", - "quote", - "sealed 0.6.0", - "syn 2.0.119", + "i_float", + "i_key_sort", + "i_shape", + "i_tree", ] [[package]] -name = "macroific_attr_parse" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf9b7bdb92825e6a507392b12f0570ade17480be20f4ca951b9071b40deba1d1" +name = "i_shape" +version = "3.0.0" dependencies = [ - "proc-macro2", - "quote", - "sealed 0.7.0", - "syn 3.0.5", + "i_float", ] [[package]] -name = "macroific_core" -version = "2.0.0" +name = "i_tree" +version = "0.19.0" + +[[package]] +name = "iana-time-zone" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a7594d3c14916fa55bef7e9d18c5daa9ed410dd37504251e4b75bbdeec33e3" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ - "proc-macro2", - "quote", - "sealed 0.6.0", - "syn 2.0.119", + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.62.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "macroific_core" -version = "3.1.0" +name = "iana-time-zone-haiku" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc880da2bc00d6d87f748805e242d670e248d1b9b6fcef3190503210b6cf056" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ - "proc-macro2", - "quote", - "sealed 0.7.0", - "syn 3.0.5", + "cc", ] [[package]] -name = "macroific_macro" -version = "2.0.0" +name = "icu_collections" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4da6f2ed796261b0a74e2b52b42c693bb6dee1effba3a482c49592659f824b3b" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" dependencies = [ - "macroific_attr_parse 2.0.0", - "macroific_core 2.0.0", - "proc-macro2", - "quote", - "syn 2.0.119", + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", ] [[package]] -name = "macroific_macro" -version = "3.1.0" +name = "icu_locale_core" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201faf668c264432727fb64e44407bcfcf1f5f8153bf4ec248eb6bbe4a39b144" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" dependencies = [ - "macroific_attr_parse 3.1.0", - "macroific_core 3.1.0", - "proc-macro2", - "quote", - "syn 3.0.5", + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", ] [[package]] -name = "makepad-ai-services" -version = "0.1.0" +name = "icu_normalizer" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" dependencies = [ - "makepad-micro-serde", - "makepad-platform", - "makepad-strict-json", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec 1.16.1", + "zerovec", ] [[package]] -name = "makepad-aichat" -version = "0.1.0" -dependencies = [ - "makepad-ai-services", - "makepad-strict-json", - "makepad-widgets", - "makepad-wm-api", - "makepad-wm-theme", -] +name = "icu_normalizer_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" [[package]] -name = "makepad-android-state" -version = "0.1.0" +name = "icu_properties" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd004cda8be459fd76954218b76a1249a079fb9360bbca4e724cb7ddb2962857" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" dependencies = [ - "makepad-jni-sys", + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", ] [[package]] -name = "makepad-app-module" -version = "0.1.0" -dependencies = [ - "makepad-ai-services", - "makepad-strict-json", - "makepad-widgets", -] +name = "icu_properties_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" [[package]] -name = "makepad-apple-sys" -version = "1.0.0" +name = "icu_provider" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" dependencies = [ - "makepad-objc-sys", + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", ] [[package]] -name = "makepad-byteorder-lite" -version = "0.1.0" +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] -name = "makepad-code-editor" -version = "2.0.0" +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ - "makepad-widgets", + "idna_adapter", + "smallvec 1.16.1", + "utf8_iter", ] [[package]] -name = "makepad-derive-wasm-bridge" -version = "1.0.0" +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ - "makepad-micro-proc-macro", + "icu_normalizer", + "icu_properties", ] [[package]] -name = "makepad-derive-widget" -version = "2.0.0" +name = "ignore" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b69833ed729dc5aa7d19541d96d6cf8e9137194207a04916d658e43168402f" dependencies = [ - "makepad-live-id", - "makepad-micro-proc-macro", + "crossbeam-deque", + "globset", + "log", + "memchr 2.8.3", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", ] [[package]] -name = "makepad-draw" -version = "2.0.0" +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" dependencies = [ - "ab_glyph_rasterizer", - "fxhash", - "makepad-gif", - "makepad-live-id", - "makepad-math", - "makepad-platform", - "makepad-svg", - "makepad-webp", - "makepad-zune-bmp", - "makepad-zune-jpeg", - "makepad-zune-png", - "makepad-zune-qoi", - "rustybuzz 0.18.0", - "sdfer", - "serde", - "ttf-parser 0.24.1", - "unicode-bidi 0.3.18", - "unicode-linebreak", - "unicode-segmentation 1.12.0", + "bytemuck 1.25.2", + "byteorder-lite", + "moxcms", + "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "png 0.18.1", + "tiff", + "zune-core", + "zune-jpeg", ] [[package]] -name = "makepad-error-log" -version = "1.0.0" +name = "image-webp" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" dependencies = [ - "makepad-micro-serde", + "byteorder-lite", + "quick-error", ] [[package]] -name = "makepad-fast-inflate" -version = "0.1.0" +name = "imagesize" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e54e57b4c48b40f7aec75635392b12b3421fa26fe8b4332e63138ed278459c" [[package]] -name = "makepad-filesystem-watcher" -version = "0.1.0" +name = "imara-diff" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17d34b7d42178945f775e84bc4c36dde7c1c6cdfea656d3354d009056f2bb3d2" dependencies = [ - "makepad-git", + "hashbrown 0.15.5", ] [[package]] -name = "makepad-futures" -version = "1.0.0" +name = "imbl" +version = "7.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46bad832b9b463ed9398b8506488cc2e3b897a9d44f13af115f382aac71f4fec" +dependencies = [ + "archery", + "equivalent 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "imbl-sized-chunks", + "rand_core 0.9.5", + "rand_xoshiro", + "serde_core", + "version_check", + "wide", +] [[package]] -name = "makepad-futures-legacy" -version = "1.0.0" +name = "imbl-sized-chunks" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0813be332553f857953298749fa19549e8b61b80589757c29b4e2a804fa9c6" [[package]] -name = "makepad-gif" -version = "0.1.0" +name = "impartial-ord" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc680e03d1a08bdc1c01b60e0c39723430ced63d8cabd93cee0449b6ba916a25" dependencies = [ - "weezl 0.1.12", + "proc-macro2", + "quote", + "syn 3.0.5", ] [[package]] -name = "makepad-git" -version = "0.1.0" -dependencies = [ - "makepad-fast-inflate", -] +name = "indenter" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" [[package]] -name = "makepad-half" -version = "2.7.1" +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ - "cfg-if 1.0.4", - "crunchy 0.2.4", - "num-traits 0.2.19", - "zerocopy 0.8.39", + "autocfg", + "hashbrown 0.12.3", + "serde", ] [[package]] -name = "makepad-html" -version = "1.0.0" +name = "indexmap" +version = "2.13.0" dependencies = [ - "makepad-live-id", + "equivalent 1.0.2", + "hashbrown 0.16.1", ] [[package]] -name = "makepad-image-tiles" -version = "0.1.0" +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" dependencies = [ - "makepad-network", - "makepad-platform-video", - "makepad-sqlite", - "makepad-widgets", + "equivalent 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "hashbrown 0.17.1", + "serde", + "serde_core", ] [[package]] -name = "makepad-jni-sys" -version = "0.4.0" +name = "inout" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9775cbec5fa0647500c3e5de7c850280a88335d1d2d770e5aa2332b801ba7064" - -[[package]] -name = "makepad-latex-math" -version = "0.1.0" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ - "ttf-parser 0.24.1", + "block-padding 0.3.3", + "generic-array", ] [[package]] -name = "makepad-live-id" -version = "1.0.0" +name = "inout" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" dependencies = [ - "makepad-live-id-macros", - "serde", + "block-padding 0.4.2", + "hybrid-array", ] [[package]] -name = "makepad-live-id-macros" -version = "1.0.0" -dependencies = [ - "makepad-micro-proc-macro", -] +name = "ipnet" +version = "2.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" [[package]] -name = "makepad-live-reload-core" -version = "0.1.0" +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" dependencies = [ - "makepad-filesystem-watcher", + "once_cell 1.21.4", ] [[package]] -name = "makepad-markdown" -version = "0.1.0" +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" dependencies = [ - "blake3", - "comrak", - "emojis", - "html5ever", - "pulldown-cmark 0.13.4", - "unicode-segmentation 1.13.3", - "url", + "is-docker", + "once_cell 1.21.4", ] [[package]] -name = "makepad-markdown-widgets" -version = "0.1.0" -dependencies = [ - "blake3", - "core-foundation", - "image", - "makepad-latex-math", - "makepad-markdown", - "makepad-widgets", - "mermaid-rs-renderer", - "resvg", - "roxmltree 0.21.1", - "syntect", - "tiny-skia", - "ttf-parser 0.24.1", - "unicode-segmentation 1.12.0", -] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] -name = "makepad-math" -version = "1.0.0" +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ - "makepad-micro-serde", + "either", ] [[package]] -name = "makepad-mbtile-reader" -version = "1.0.0" +name = "itertools" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b4baf93f58d4425749ca49a51c50ebab072c5df6994d08fed93541c331481dc" dependencies = [ - "brotli", - "makepad-fast-inflate", - "makepad-sqlite", + "either", ] [[package]] -name = "makepad-micro-proc-macro" -version = "1.0.0" +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] -name = "makepad-micro-serde" -version = "1.0.0" +name = "jetscii" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47f142fe24a9c9944451e8349de0a56af5f3e7226dc46f3ed4d4ecc0b85af75e" + +[[package]] +name = "jiff" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1baf72f08796de0260609515130699b890ac25f30e610ad894bc5856cafdb" dependencies = [ - "makepad-live-id", - "makepad-micro-serde-derive", + "defmt 1.1.1", + "jiff-core", + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", + "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "makepad-micro-serde-derive" -version = "1.0.0" +name = "jiff-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e52fe76043ccecc9005d2305ebaadf7d7fc0cc89ca6baa10a94d6bc68c7128c" dependencies = [ - "makepad-micro-proc-macro", + "defmt 1.1.1", + "log", ] [[package]] -name = "makepad-network" -version = "1.0.0" +name = "jiff-static" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "378268a1116ad67ae6228701118ac9f491d78fda38a40a1f1a9e1348de6f7212" dependencies = [ - "makepad-apple-sys", - "makepad-error-log", - "makepad-futures-legacy", - "makepad-live-id", - "makepad-micro-serde", - "makepad-script", - "windows 0.62.2", + "jiff-core", + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] -name = "makepad-objc-sys" -version = "1.0.0" +name = "jiff-tzdb" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "142bd39932ad231f10513df9ab62661fead8719872150b7ad02a2df79f4e141e" [[package]] -name = "makepad-photos" -version = "0.1.0" +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" dependencies = [ - "makepad-ai-services", - "makepad-app-module", - "makepad-image-tiles", - "makepad-strict-json", - "makepad-widgets", - "makepad-wm-theme", + "jiff-tzdb", ] [[package]] -name = "makepad-platform" -version = "2.0.0" +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" dependencies = [ - "ash", - "bitflags 2.10.0", - "hilog-sys", - "makepad-android-state", - "makepad-apple-sys", - "makepad-futures", - "makepad-futures-legacy", - "makepad-jni-sys", - "makepad-live-reload-core", - "makepad-network", - "makepad-objc-sys", - "makepad-platform-video", - "makepad-script", - "makepad-script-std", - "makepad-shared-bytes", - "makepad-studio-protocol", - "makepad-tsdf", - "makepad-wasm-bridge", - "makepad-zune-png", - "naga", - "napi-derive-ohos", - "napi-ohos", - "ohos-sys", - "rqrr", - "smallvec 1.15.1", - "wayland-client", - "wayland-egl", - "wayland-protocols", - "windows 0.62.2", - "windows-core 0.62.2", - "windows-targets 0.52.6", + "cesu8", + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "combine", + "jni-sys 0.3.1", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", ] [[package]] -name = "makepad-platform-video" -version = "1.0.0" +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" dependencies = [ - "makepad-apple-sys", - "makepad-objc-sys", - "windows 0.62.2", + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "combine", + "jni-macros", + "jni-sys 0.4.1", + "log", + "simd_cesu8", + "thiserror 2.0.20", + "walkdir", + "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "makepad-regex" -version = "0.1.0" - -[[package]] -name = "makepad-script" -version = "1.0.0" +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" dependencies = [ - "makepad-error-log", - "makepad-html", - "makepad-live-id", - "makepad-math", - "makepad-regex", - "makepad-script-derive", - "makepad-stitch", - "smallvec 1.15.1", + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.119", ] [[package]] -name = "makepad-script-derive" -version = "1.0.0" +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" dependencies = [ - "makepad-micro-proc-macro", + "jni-sys 0.4.1", ] [[package]] -name = "makepad-script-std" -version = "1.0.0" +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" dependencies = [ - "makepad-network", - "makepad-script", + "jni-sys-macros", ] [[package]] -name = "makepad-shared-bytes" -version = "1.0.0" - -[[package]] -name = "makepad-sheets" -version = "0.1.0" +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" dependencies = [ - "makepad-ai-services", - "makepad-app-module", - "makepad-widgets", - "makepad-wm-theme", + "quote", + "syn 2.0.119", ] [[package]] -name = "makepad-sqlite" -version = "1.0.0" - -[[package]] -name = "makepad-stitch" -version = "0.1.0" - -[[package]] -name = "makepad-strict-json" -version = "0.1.0" - -[[package]] -name = "makepad-studio-protocol" -version = "0.1.0" +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" dependencies = [ - "bitflags 2.10.0", - "makepad-error-log", - "makepad-live-id", - "makepad-micro-serde", - "makepad-script", + "getrandom 0.4.3", + "libc", ] [[package]] -name = "makepad-svg" -version = "1.0.0" +name = "js-sys" +version = "0.3.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e" dependencies = [ - "makepad-html", - "makepad-live-id", + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-util", + "wasm-bindgen", ] [[package]] -name = "makepad-tsdf" -version = "0.1.0" +name = "js_int" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d937f95470b270ce8b8950207715d71aa8e153c0d44c6684d59397ed4949160a" dependencies = [ - "makepad-math", - "makepad-micro-serde", + "serde", ] [[package]] -name = "makepad-wasm-bridge" -version = "1.0.0" +name = "js_option" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7dd3e281add16813cf673bf74a32249b0aa0d1c8117519a17b3ada5e8552b3c" dependencies = [ - "makepad-derive-wasm-bridge", - "makepad-live-id", + "serde_core", ] [[package]] -name = "makepad-webp" -version = "0.2.4" +name = "json5" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "733a844dbd6fef128e98cb4487b887cb55454d92cd9994b1bafe004fabbe670c" dependencies = [ - "makepad-byteorder-lite", + "serde", + "ucd-trie", ] [[package]] -name = "makepad-widgets" -version = "2.0.0" +name = "jsonwebtoken" +version = "9.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" dependencies = [ - "i_overlay", - "makepad-derive-widget", - "makepad-draw", - "makepad-fast-inflate", - "makepad-html", - "makepad-latex-math", - "makepad-mbtile-reader", - "octoscript-node", - "pulldown-cmark 0.12.2", + "base64 0.22.1", + "js-sys", + "pem", + "ring", "serde", "serde_json", - "ttf-parser 0.24.1", - "unicode-segmentation 1.12.0", + "simple_asn1", ] [[package]] -name = "makepad-wm-api" -version = "0.1.0" +name = "keyring" +version = "3.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c" dependencies = [ - "makepad-widgets", + "byteorder 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "linux-keyutils", + "log", + "security-framework 2.11.1", + "security-framework 3.7.0", + "windows-sys 0.60.2", + "zeroize", ] [[package]] -name = "makepad-wm-theme" -version = "0.1.0" +name = "konst" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f660d5f887e3562f9ab6f4a14988795b694099d66b4f5dedc02d197ba9becb1d" dependencies = [ - "makepad-widgets", + "const_panic", + "typewit", ] [[package]] -name = "makepad-zune-bmp" -version = "0.5.2" +name = "kstring" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a09b82a7f771ed02dc0dd9b27130a0fa5499fa15ed3027116c1e5e4e591bd9e" dependencies = [ - "makepad-error-log", - "makepad-zune-core", + "static_assertions", ] [[package]] -name = "makepad-zune-core" -version = "0.5.1" +name = "kurbo" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b60dfc32f652b926df6192e55525b16d186c69d47876c3ead4da5cc9f8450e2" dependencies = [ - "makepad-error-log", + "arrayvec 0.7.8", + "euclid 0.22.14", + "polycool", + "smallvec 1.16.1", ] [[package]] -name = "makepad-zune-inflate" -version = "0.2.54" +name = "landlock" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cca98e95f35b29d469dade6724c6f96cec9236640f745a0e99b0334ec320ab1" dependencies = [ - "simd-adler32 0.3.9", + "enumflags2", + "libc", + "thiserror 2.0.20", ] [[package]] -name = "makepad-zune-jpeg" -version = "0.5.15" +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lddtree" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "547a362ad16dead8837d5b30402a81b05bb57c352044389f8748f4f2dbd51914" dependencies = [ - "makepad-zune-core", + "fs-err", + "glob", + "goblin", ] [[package]] -name = "makepad-zune-png" -version = "0.5.2" +name = "lettre" +version = "0.11.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2c646bd5cc763b1087b15493e29a64be6147ba8f19342004fa52048ee596eae" dependencies = [ - "makepad-fast-inflate", - "makepad-zune-core", - "makepad-zune-inflate", + "async-trait", + "base64 0.23.1", + "email-encoding", + "email_address", + "fastrand", + "futures-io", + "futures-util", + "httpdate", + "idna", + "mime", + "nom", + "percent-encoding", + "quoted_printable", + "rustls", + "socket2", + "tokio", + "tokio-rustls", + "url", + "webpki-roots", ] [[package]] -name = "makepad-zune-qoi" -version = "0.5.2" +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "libloading" +version = "0.8.9" dependencies = [ - "makepad-zune-core", + "cfg-if 1.0.4", + "windows-link 0.2.1", ] [[package]] -name = "maplit" -version = "1.0.2" +name = "libloading" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] -name = "markup5ever" -version = "0.39.0" +name = "libm" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7122d987ec5f704ee56f6e5b41a7d93722e9aae27ae07cafa4036c4d3f9757de" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6480ccc157a1389bb2e4891b24751b0f798ba640d22386f23143fbcc89da195a" dependencies = [ - "log", - "tendril", - "web_atoms", + "libc", ] [[package]] -name = "matchers" -version = "0.2.0" +name = "libsqlite3-sys" +version = "0.38.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +checksum = "f1d20bef17f513b9b3004532233187769cd072d790971f4e4da0e346eb6401e8" dependencies = [ - "regex-automata", + "cc", + "pkg-config 0.3.34", + "vcpkg", ] [[package]] -name = "matchit" -version = "0.8.4" +name = "libyml" +version = "0.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" +checksum = "3302702afa434ffa30847a83305f0a69d6abd74293b6554c18ec85c7ef30c980" +dependencies = [ + "anyhow", + "version_check", +] [[package]] -name = "matrix-pickle" -version = "0.2.3" +name = "linkify" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d65d46b7379dd0afa4a42f9b2269821d31afdee0111b5e0d74e3bee03553a0" +checksum = "f1dfa36d52c581e9ec783a7ce2a5e0143da6237be5811a0b3153fedfdbe9f780" dependencies = [ - "matrix-pickle-derive", - "thiserror 2.0.20", + "memchr 2.8.3", ] [[package]] -name = "matrix-pickle-derive" -version = "0.2.3" +name = "linux-keyutils" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414b5e4c34009f2bc3fe35dd018f25755ca38858096574841c7332f99e2c7e77" +checksum = "83270a18e9f90d0707c41e9f35efada77b64c0e6f3f1810e71c8368a864d5590" dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.119", + "bitflags 2.13.2", + "libc", ] [[package]] -name = "matrix-sdk" -version = "0.18.0" -source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" dependencies = [ - "anymap2", - "as_variant", - "async-channel", - "async-stream", - "async-trait", - "axum", - "backon", - "bytes", - "bytesize", "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "event-listener", - "eyeball", - "eyeball-im", - "futures-core", - "futures-util", - "gloo-timers 0.4.0", - "hex", - "http", - "imbl", - "indexmap 2.14.2", - "itertools 0.15.0", - "js_int", - "language-tags", - "matrix-sdk-base", - "matrix-sdk-common", - "matrix-sdk-indexeddb", - "matrix-sdk-sqlite", - "mime", - "mime2ext", - "oauth2", - "oauth2-reqwest", - "percent-encoding", - "pin-project-lite", - "rand 0.10.2", - "reqwest", - "ruma", - "serde", - "serde_html_form", - "serde_json", - "sha2 0.11.0", - "tempfile", - "thiserror 2.0.20", - "tokio", - "tokio-stream", - "tokio-util", - "tower", + "generator", + "scoped-tls 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "tracing", - "url", - "urlencoding", - "vodozemac", - "zeroize", + "tracing-subscriber", ] [[package]] -name = "matrix-sdk-base" -version = "0.18.0" -source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +name = "lopdf" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59fa2559e99ba0f26a12458aabc754432c805bbb8cba516c427825a997af1fb7" dependencies = [ - "as_variant", - "async-trait", + "aes 0.8.4", "bitflags 2.13.2", - "decancer", - "eyeball", - "eyeball-im", - "futures-util", - "growable-bloom-filter", - "matrix-sdk-common", - "matrix-sdk-crypto", - "matrix-sdk-store-encryption", - "regex", - "ruma", - "serde", - "serde_bytes", - "serde_json", + "cbc 0.1.2", + "ecb", + "encoding_rs", + "flate2", + "indexmap 2.14.2", + "itoa", + "log", + "md-5", + "nom", + "nom_locate", + "rand 0.9.5", + "rangemap", + "sha2 0.10.9", + "stringprep", "thiserror 2.0.20", - "tokio", - "tracing", - "unicode-normalization", + "weezl 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "matrix-sdk-common" -version = "0.18.0" -source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +name = "lru" +version = "0.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" dependencies = [ - "eyeball-im", - "futures-core", - "futures-executor", - "futures-util", - "gloo-timers 0.4.0", - "imbl", - "ruma", - "serde", - "serde_json", - "thiserror 2.0.20", - "tokio", - "tracing", - "tracing-subscriber", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", + "hashbrown 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "matrix-sdk-crypto" -version = "0.18.0" -source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +name = "lru-slab" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4050469837a6ff301cd14c1f8f24f88549e6d548f24f64e2148eb0f72cebc51f" + +[[package]] +name = "lzma-rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e" +dependencies = [ + "byteorder 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crc", +] + +[[package]] +name = "lzma-sys" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" +dependencies = [ + "cc", + "libc", + "pkg-config 0.3.34", +] + +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "mach2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +dependencies = [ + "libc", +] + +[[package]] +name = "macroific" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89f276537b4b8f981bf1c13d79470980f71134b7bdcc5e6e911e910e556b0285" +dependencies = [ + "macroific_attr_parse 2.0.0", + "macroific_core 2.0.0", + "macroific_macro 2.0.0", +] + +[[package]] +name = "macroific" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc8ab95c98294764c0f1eacebdb5b027849201b71fbc383871fe991a1de7eb83" +dependencies = [ + "macroific_attr_parse 3.1.0", + "macroific_core 3.1.0", + "macroific_macro 3.1.0", +] + +[[package]] +name = "macroific_attr_parse" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad4023761b45fcd36abed8fb7ae6a80456b0a38102d55e89a57d9a594a236be9" +dependencies = [ + "proc-macro2", + "quote", + "sealed 0.6.0", + "syn 2.0.119", +] + +[[package]] +name = "macroific_attr_parse" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf9b7bdb92825e6a507392b12f0570ade17480be20f4ca951b9071b40deba1d1" +dependencies = [ + "proc-macro2", + "quote", + "sealed 0.7.0", + "syn 3.0.5", +] + +[[package]] +name = "macroific_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a7594d3c14916fa55bef7e9d18c5daa9ed410dd37504251e4b75bbdeec33e3" +dependencies = [ + "proc-macro2", + "quote", + "sealed 0.6.0", + "syn 2.0.119", +] + +[[package]] +name = "macroific_core" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edc880da2bc00d6d87f748805e242d670e248d1b9b6fcef3190503210b6cf056" +dependencies = [ + "proc-macro2", + "quote", + "sealed 0.7.0", + "syn 3.0.5", +] + +[[package]] +name = "macroific_macro" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4da6f2ed796261b0a74e2b52b42c693bb6dee1effba3a482c49592659f824b3b" +dependencies = [ + "macroific_attr_parse 2.0.0", + "macroific_core 2.0.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "macroific_macro" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201faf668c264432727fb64e44407bcfcf1f5f8153bf4ec248eb6bbe4a39b144" +dependencies = [ + "macroific_attr_parse 3.1.0", + "macroific_core 3.1.0", + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "makepad-ai" +version = "1.0.0" +dependencies = [ + "makepad-live-id", + "makepad-micro-serde", + "makepad-widgets", +] + +[[package]] +name = "makepad-ai-services" +version = "0.1.0" +dependencies = [ + "makepad-micro-serde", + "makepad-platform", + "makepad-strict-json", +] + +[[package]] +name = "makepad-aichat" +version = "0.1.0" +dependencies = [ + "makepad-ai-services", + "makepad-strict-json", + "makepad-widgets", + "makepad-wm-api", + "makepad-wm-theme", +] + +[[package]] +name = "makepad-android-state" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd004cda8be459fd76954218b76a1249a079fb9360bbca4e724cb7ddb2962857" +dependencies = [ + "makepad-jni-sys", +] + +[[package]] +name = "makepad-app-module" +version = "0.1.0" +dependencies = [ + "makepad-ai-services", + "makepad-strict-json", + "makepad-widgets", +] + +[[package]] +name = "makepad-apple-sys" +version = "1.0.0" +dependencies = [ + "makepad-objc-sys", +] + +[[package]] +name = "makepad-byteorder-lite" +version = "0.1.0" + +[[package]] +name = "makepad-code-editor" +version = "2.0.0" +dependencies = [ + "makepad-widgets", +] + +[[package]] +name = "makepad-derive-wasm-bridge" +version = "1.0.0" +dependencies = [ + "makepad-micro-proc-macro", +] + +[[package]] +name = "makepad-derive-widget" +version = "2.0.0" +dependencies = [ + "makepad-live-id", + "makepad-micro-proc-macro", +] + +[[package]] +name = "makepad-diagram-kit" +version = "0.1.0" +source = "git+https://github.com/Project-Robius-China/makepad-diagram-kit.git?branch=main#0536492116e5db6f5db88bb8b3e33bb9580abfea" +dependencies = [ + "makepad-widgets", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "makepad-draw" +version = "2.0.0" +dependencies = [ + "ab_glyph_rasterizer", + "fxhash", + "makepad-gif", + "makepad-live-id", + "makepad-math", + "makepad-platform", + "makepad-svg", + "makepad-webp", + "makepad-zune-bmp", + "makepad-zune-jpeg", + "makepad-zune-png", + "makepad-zune-qoi", + "rustybuzz 0.18.0", + "sdfer", + "serde", + "ttf-parser 0.24.1", + "unicode-bidi 0.3.18", + "unicode-linebreak", + "unicode-segmentation 1.12.0", +] + +[[package]] +name = "makepad-error-log" +version = "1.0.0" +dependencies = [ + "makepad-micro-serde", +] + +[[package]] +name = "makepad-fast-inflate" +version = "0.1.0" + +[[package]] +name = "makepad-filesystem-watcher" +version = "0.1.0" +dependencies = [ + "makepad-git", +] + +[[package]] +name = "makepad-futures" +version = "1.0.0" + +[[package]] +name = "makepad-futures-legacy" +version = "1.0.0" + +[[package]] +name = "makepad-gif" +version = "0.1.0" +dependencies = [ + "weezl 0.1.12", +] + +[[package]] +name = "makepad-git" +version = "0.1.0" +dependencies = [ + "makepad-fast-inflate", +] + +[[package]] +name = "makepad-half" +version = "2.7.1" +dependencies = [ + "cfg-if 1.0.4", + "crunchy 0.2.4", + "num-traits 0.2.19", + "zerocopy 0.8.39", +] + +[[package]] +name = "makepad-html" +version = "1.0.0" +dependencies = [ + "makepad-live-id", +] + +[[package]] +name = "makepad-image-tiles" +version = "0.1.0" +dependencies = [ + "makepad-network", + "makepad-platform-video", + "makepad-sqlite", + "makepad-widgets", +] + +[[package]] +name = "makepad-jni-sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9775cbec5fa0647500c3e5de7c850280a88335d1d2d770e5aa2332b801ba7064" + +[[package]] +name = "makepad-latex-math" +version = "0.1.0" +dependencies = [ + "ttf-parser 0.24.1", +] + +[[package]] +name = "makepad-live-id" +version = "1.0.0" +dependencies = [ + "makepad-live-id-macros", + "serde", +] + +[[package]] +name = "makepad-live-id-macros" +version = "1.0.0" +dependencies = [ + "makepad-micro-proc-macro", +] + +[[package]] +name = "makepad-live-reload-core" +version = "0.1.0" +dependencies = [ + "makepad-filesystem-watcher", +] + +[[package]] +name = "makepad-markdown" +version = "0.1.0" +dependencies = [ + "blake3", + "comrak", + "emojis", + "html5ever 0.39.0", + "pulldown-cmark 0.13.4", + "unicode-segmentation 1.13.3", + "url", +] + +[[package]] +name = "makepad-markdown-widgets" +version = "0.1.0" +dependencies = [ + "blake3", + "core-foundation 0.10.1", + "image", + "makepad-latex-math", + "makepad-markdown", + "makepad-widgets", + "mermaid-rs-renderer", + "resvg", + "roxmltree 0.21.1", + "syntect", + "tiny-skia", + "ttf-parser 0.24.1", + "unicode-segmentation 1.12.0", +] + +[[package]] +name = "makepad-math" +version = "1.0.0" +dependencies = [ + "makepad-micro-serde", +] + +[[package]] +name = "makepad-mbtile-reader" +version = "1.0.0" +dependencies = [ + "brotli", + "makepad-fast-inflate", + "makepad-sqlite", +] + +[[package]] +name = "makepad-micro-proc-macro" +version = "1.0.0" + +[[package]] +name = "makepad-micro-serde" +version = "1.0.0" +dependencies = [ + "makepad-live-id", + "makepad-micro-serde-derive", +] + +[[package]] +name = "makepad-micro-serde-derive" +version = "1.0.0" +dependencies = [ + "makepad-micro-proc-macro", +] + +[[package]] +name = "makepad-network" +version = "1.0.0" +dependencies = [ + "makepad-apple-sys", + "makepad-error-log", + "makepad-futures-legacy", + "makepad-live-id", + "makepad-micro-serde", + "makepad-script", + "windows 0.62.2", +] + +[[package]] +name = "makepad-objc-sys" +version = "1.0.0" + +[[package]] +name = "makepad-pdf-parse" +version = "1.0.0" +dependencies = [ + "makepad-zune-inflate", +] + +[[package]] +name = "makepad-photos" +version = "0.1.0" +dependencies = [ + "makepad-ai-services", + "makepad-app-module", + "makepad-image-tiles", + "makepad-strict-json", + "makepad-widgets", + "makepad-wm-theme", +] + +[[package]] +name = "makepad-platform" +version = "2.0.0" +dependencies = [ + "ash", + "bitflags 2.10.0", + "hilog-sys", + "makepad-android-state", + "makepad-apple-sys", + "makepad-futures", + "makepad-futures-legacy", + "makepad-jni-sys", + "makepad-live-reload-core", + "makepad-network", + "makepad-objc-sys", + "makepad-platform-video", + "makepad-script", + "makepad-script-std", + "makepad-shared-bytes", + "makepad-studio-protocol", + "makepad-tsdf", + "makepad-wasm-bridge", + "makepad-zune-png", + "naga", + "napi-derive-ohos", + "napi-ohos", + "ohos-sys", + "rqrr", + "smallvec 1.15.1", + "wayland-client", + "wayland-egl", + "wayland-protocols", + "windows 0.62.2", + "windows-core 0.62.2", + "windows-targets 0.52.6", +] + +[[package]] +name = "makepad-platform-video" +version = "1.0.0" +dependencies = [ + "makepad-apple-sys", + "makepad-objc-sys", + "windows 0.62.2", +] + +[[package]] +name = "makepad-regex" +version = "0.1.0" + +[[package]] +name = "makepad-script" +version = "1.0.0" +dependencies = [ + "makepad-error-log", + "makepad-html", + "makepad-live-id", + "makepad-math", + "makepad-regex", + "makepad-script-derive", + "makepad-stitch", + "smallvec 1.15.1", +] + +[[package]] +name = "makepad-script-derive" +version = "1.0.0" +dependencies = [ + "makepad-micro-proc-macro", +] + +[[package]] +name = "makepad-script-std" +version = "1.0.0" +dependencies = [ + "makepad-network", + "makepad-script", +] + +[[package]] +name = "makepad-shared-bytes" +version = "1.0.0" + +[[package]] +name = "makepad-sheets" +version = "0.1.0" +dependencies = [ + "makepad-ai-services", + "makepad-app-module", + "makepad-widgets", + "makepad-wm-theme", +] + +[[package]] +name = "makepad-sqlite" +version = "1.0.0" + +[[package]] +name = "makepad-stitch" +version = "0.1.0" + +[[package]] +name = "makepad-strict-json" +version = "0.1.0" + +[[package]] +name = "makepad-studio-protocol" +version = "0.1.0" +dependencies = [ + "bitflags 2.10.0", + "makepad-error-log", + "makepad-live-id", + "makepad-micro-serde", + "makepad-script", +] + +[[package]] +name = "makepad-svg" +version = "1.0.0" +dependencies = [ + "makepad-html", + "makepad-live-id", +] + +[[package]] +name = "makepad-tsdf" +version = "0.1.0" +dependencies = [ + "makepad-math", + "makepad-micro-serde", +] + +[[package]] +name = "makepad-wasm-bridge" +version = "1.0.0" +dependencies = [ + "makepad-derive-wasm-bridge", + "makepad-live-id", +] + +[[package]] +name = "makepad-webp" +version = "0.2.4" +dependencies = [ + "makepad-byteorder-lite", +] + +[[package]] +name = "makepad-widgets" +version = "2.0.0" +dependencies = [ + "i_overlay", + "makepad-derive-widget", + "makepad-draw", + "makepad-fast-inflate", + "makepad-html", + "makepad-latex-math", + "makepad-mbtile-reader", + "makepad-pdf-parse", + "octoscript-node", + "pulldown-cmark 0.12.2", + "serde", + "serde_json", + "ttf-parser 0.24.1", + "unicode-segmentation 1.12.0", +] + +[[package]] +name = "makepad-wm-api" +version = "0.1.0" +dependencies = [ + "makepad-widgets", +] + +[[package]] +name = "makepad-wm-theme" +version = "0.1.0" +dependencies = [ + "makepad-widgets", +] + +[[package]] +name = "makepad-zune-bmp" +version = "0.5.2" +dependencies = [ + "makepad-error-log", + "makepad-zune-core", +] + +[[package]] +name = "makepad-zune-core" +version = "0.5.1" +dependencies = [ + "makepad-error-log", +] + +[[package]] +name = "makepad-zune-inflate" +version = "0.2.54" +dependencies = [ + "simd-adler32 0.3.9", +] + +[[package]] +name = "makepad-zune-jpeg" +version = "0.5.15" +dependencies = [ + "makepad-zune-core", +] + +[[package]] +name = "makepad-zune-png" +version = "0.5.2" +dependencies = [ + "makepad-fast-inflate", + "makepad-zune-core", + "makepad-zune-inflate", +] + +[[package]] +name = "makepad-zune-qoi" +version = "0.5.2" +dependencies = [ + "makepad-zune-core", +] + +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + +[[package]] +name = "markup5ever" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "311fe69c934650f8f19652b3946075f0fc41ad8757dbb68f1ca14e7900ecc1c3" +dependencies = [ + "log", + "tendril 0.4.3", + "web_atoms 0.1.3", +] + +[[package]] +name = "markup5ever" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862" +dependencies = [ + "log", + "tendril 0.5.1", + "web_atoms 0.2.6", +] + +[[package]] +name = "markup5ever" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7122d987ec5f704ee56f6e5b41a7d93722e9aae27ae07cafa4036c4d3f9757de" +dependencies = [ + "log", + "tendril 0.5.1", + "web_atoms 0.2.6", +] + +[[package]] +name = "markup5ever_rcdom" +version = "0.35.0+unofficial" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8bcd53df4748257345b8bc156d620340ce0f015ec1c7ef1cff475543888a31d" +dependencies = [ + "html5ever 0.35.0", + "markup5ever 0.35.0", + "tendril 0.4.3", + "xml5ever 0.35.0", +] + +[[package]] +name = "markup5ever_rcdom" +version = "0.38.0+unofficial" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "333171ccdf66e915257740d44e38ea5b1b19ce7b45d33cc35cb6f118fbd981ff" +dependencies = [ + "html5ever 0.38.0", + "markup5ever 0.38.0", + "tendril 0.5.1", + "xml5ever 0.38.0", +] + +[[package]] +name = "match_token" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac84fd3f360fcc43dc5f5d186f02a94192761a080e8bc58621ad4d12296a58cf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "matrix-pickle" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d65d46b7379dd0afa4a42f9b2269821d31afdee0111b5e0d74e3bee03553a0" +dependencies = [ + "matrix-pickle-derive", + "thiserror 2.0.20", +] + +[[package]] +name = "matrix-pickle-derive" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "414b5e4c34009f2bc3fe35dd018f25755ca38858096574841c7332f99e2c7e77" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "matrix-sdk" +version = "0.18.0" +source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +dependencies = [ + "anymap2", + "as_variant", + "async-channel", + "async-stream", + "async-trait", + "axum", + "backon", + "bytes", + "bytesize", + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "event-listener", + "eyeball", + "eyeball-im", + "futures-core", + "futures-util", + "gloo-timers 0.4.0", + "hex", + "http", + "imbl", + "indexmap 2.14.2", + "itertools 0.15.0", + "js_int", + "language-tags", + "matrix-sdk-base", + "matrix-sdk-common", + "matrix-sdk-indexeddb", + "matrix-sdk-sqlite", + "mime", + "mime2ext", + "oauth2", + "oauth2-reqwest", + "percent-encoding", + "pin-project-lite", + "rand 0.10.2", + "reqwest 0.13.5", + "ruma", + "serde", + "serde_html_form", + "serde_json", + "sha2 0.11.0", + "tempfile", + "thiserror 2.0.20", + "tokio", + "tokio-stream", + "tokio-util", + "tower", + "tracing", + "url", + "urlencoding", + "vodozemac", + "zeroize", +] + +[[package]] +name = "matrix-sdk-base" +version = "0.18.0" +source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +dependencies = [ + "as_variant", + "async-trait", + "bitflags 2.13.2", + "decancer", + "eyeball", + "eyeball-im", + "futures-util", + "growable-bloom-filter", + "matrix-sdk-common", + "matrix-sdk-crypto", + "matrix-sdk-store-encryption", + "regex", + "ruma", + "serde", + "serde_bytes", + "serde_json", + "thiserror 2.0.20", + "tokio", + "tracing", + "unicode-normalization", +] + +[[package]] +name = "matrix-sdk-common" +version = "0.18.0" +source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +dependencies = [ + "eyeball-im", + "futures-core", + "futures-executor", + "futures-util", + "gloo-timers 0.4.0", + "imbl", + "ruma", + "serde", + "serde_json", + "thiserror 2.0.20", + "tokio", + "tracing", + "tracing-subscriber", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "matrix-sdk-crypto" +version = "0.18.0" +source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +dependencies = [ + "aes 0.9.3", + "as_variant", + "async-trait", + "bs58", + "byteorder 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "ctr", + "eyeball", + "futures-core", + "futures-util", + "hkdf", + "hmac 0.13.0", + "itertools 0.15.0", + "js_option", + "matrix-sdk-common", + "pbkdf2 0.13.0", + "rand 0.10.2", + "rmp-serde", + "ruma", + "serde", + "serde_json", + "sha2 0.11.0", + "subtle", + "thiserror 2.0.20", + "time", + "tokio", + "tokio-stream", + "tracing", + "ulid", + "url", + "vodozemac", + "zeroize", +] + +[[package]] +name = "matrix-sdk-indexeddb" +version = "0.18.0" +source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +dependencies = [ + "async-trait", + "base64 0.23.1", + "futures-util", + "getrandom 0.4.3", + "gloo-utils", + "hkdf", + "js-sys", + "matrix-sdk-base", + "matrix-sdk-crypto", + "matrix-sdk-store-encryption", + "matrix_indexed_db_futures", + "rmp-serde", + "ruma", + "serde", + "serde-wasm-bindgen", + "serde_bytes", + "serde_json", + "sha2 0.11.0", + "thiserror 2.0.20", + "tokio", + "tracing", + "uuid", + "wasm-bindgen", + "web-sys", + "zeroize", +] + +[[package]] +name = "matrix-sdk-sqlite" +version = "0.18.0" +source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +dependencies = [ + "as_variant", + "async-trait", + "deadpool", + "deadpool-sync", + "itertools 0.15.0", + "matrix-sdk-base", + "matrix-sdk-crypto", + "matrix-sdk-store-encryption", + "num_cpus", + "rmp-serde", + "ruma", + "rusqlite", + "serde", + "serde_json", + "serde_path_to_error", + "thiserror 2.0.20", + "tokio", + "tracing", + "vodozemac", + "zeroize", +] + +[[package]] +name = "matrix-sdk-store-encryption" +version = "0.18.0" +source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +dependencies = [ + "base64 0.23.1", + "blake3", + "chacha20poly1305 0.11.0", + "getrandom 0.4.3", + "hkdf", + "hmac 0.13.0", + "pbkdf2 0.13.0", + "rand 0.10.2", + "rmp-serde", + "serde", + "serde_bytes", + "serde_json", + "sha2 0.11.0", + "thiserror 2.0.20", + "zeroize", +] + +[[package]] +name = "matrix-sdk-ui" +version = "0.18.0" +source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +dependencies = [ + "as_variant", + "async-rx", + "async-stream", + "async_cell", + "bitflags 2.13.2", + "chrono", + "emojis", + "eyeball", + "eyeball-im", + "eyeball-im-util", + "futures-core", + "futures-util", + "fuzzy-matcher", + "growable-bloom-filter", + "imbl", + "indexmap 2.14.2", + "itertools 0.15.0", + "matrix-sdk", + "matrix-sdk-base", + "matrix-sdk-common", + "mime", + "pin-project-lite", + "ruma", + "serde", + "serde_json", + "thiserror 2.0.20", + "tokio", + "tokio-stream", + "tracing", + "unicode-normalization", + "unicode-segmentation 1.13.3", +] + +[[package]] +name = "matrix_indexed_db_futures" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245ff6a224b4df7b0c90dda2dd5a6eb46112708d49e8bdd8b007fccb09fea8e4" +dependencies = [ + "accessory", + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "delegate-display", + "derive_more 2.1.1", + "fancy_constructor", + "futures-core", + "js-sys", + "matrix_indexed_db_futures_macros_internal", + "sealed 0.6.0", + "serde", + "serde-wasm-bindgen", + "smallvec 1.16.1", + "thiserror 2.0.20", + "tokio", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm_evt_listener", + "web-sys", + "web-time", +] + +[[package]] +name = "matrix_indexed_db_futures_macros_internal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b428aee5c0fe9e5babd29e99d289b7f64718c444989aac0442d1fd6d3e3f66d1" +dependencies = [ + "macroific 2.0.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "maybe-async" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "746873a384ad60adc5db74471dfaba74bd278afbdcfd81db93fafcdfc8b5ca0c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.10.7", +] + +[[package]] +name = "memchr" +version = "2.7.6" + +[[package]] +name = "memchr" +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 = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mermaid-rs-renderer" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb5b469222ce6b9896edf0a969cf68464f5a028ef394fd6643f00bd943df117" +dependencies = [ + "anyhow", + "fontdb", + "json5", + "once_cell 1.21.4", + "regex", + "serde", + "serde_json", + "thiserror 2.0.20", + "ttf-parser 0.25.1", +] + +[[package]] +name = "metrics" +version = "0.24.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89550ee9f79e88fef3119de263694973a8adb26c21d75322164fb8c493039fe2" +dependencies = [ + "portable-atomic", + "rapidhash", +] + +[[package]] +name = "metrics-exporter-prometheus" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd7399781913e5393588a8d8c6a2867bf85fb38eaf2502fdce465aad2dc6f034" +dependencies = [ + "base64 0.22.1", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "indexmap 2.14.2", + "ipnet", + "metrics", + "metrics-util", + "quanta", + "thiserror 1.0.69", + "tokio", + "tracing", +] + +[[package]] +name = "metrics-util" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8496cc523d1f94c1385dd8f0f0c2c480b2b8aeccb5b7e4485ad6365523ae376" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", + "hashbrown 0.15.5", + "metrics", + "quanta", + "rand 0.9.5", + "rand_xoshiro", + "sketches-ddsketch", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime2ext" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf6f36070878c42c5233846cd3de24cf9016828fd47bc22957a687298bb21fc" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase 2.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32 0.3.10", +] + +[[package]] +name = "miniz_oxide" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" +dependencies = [ + "adler2", + "simd-adler32 0.3.10", +] + +[[package]] +name = "mio" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "mmap-rs" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ecce9d566cb9234ae3db9e249c8b55665feaaf32b0859ff1e27e310d2beb3d8" +dependencies = [ + "bitflags 2.13.2", + "combine", + "libc", + "mach2", + "nix 0.30.1", + "sysctl", + "thiserror 2.0.20", + "widestring", + "windows 0.48.0", +] + +[[package]] +name = "moxcms" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "pxfm", +] + +[[package]] +name = "multer" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http", + "httparse", + "memchr 2.8.3", + "mime", + "spin", + "version_check", +] + +[[package]] +name = "multiversion" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ca4bea16ffc3f443cf7d866912118196bfef4c6a1556ca00f9f9b00bb43f7c" +dependencies = [ + "multiversion-macros", +] + +[[package]] +name = "multiversion-macros" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d416831a7317ef4b08bee00b69cbbb9c8763da7959a7026244d6266869f9c83" +dependencies = [ + "proc-macro2", + "quote", + "rustversion", + "syn 3.0.5", +] + +[[package]] +name = "multiversion_no_op" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d" + +[[package]] +name = "naga" +version = "27.0.3" +dependencies = [ + "arrayvec 0.7.6", + "bit-set 0.8.0", + "bitflags 2.10.0", + "cfg-if 1.0.4", + "cfg_aliases 0.2.1", + "hashbrown 0.16.1", + "hexf-parse", + "indexmap 2.13.0", + "makepad-error-log", + "makepad-half", + "num-traits 0.2.19", + "once_cell 1.21.3", + "rustc-hash 1.1.0", + "spirv", + "thiserror 2.0.18", + "unicode-ident 1.0.22", +] + +[[package]] +name = "napi-derive-backend-ohos" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b18d697bedddd2d4c9f8f76b49fe65bd81ed1c55a7eec21ba40c176c236ddc" +dependencies = [ + "convert_case 0.6.0", + "once_cell 1.21.4", + "proc-macro2", + "quote", + "regex", + "syn 2.0.119", +] + +[[package]] +name = "napi-derive-ohos" +version = "0.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8462d74a2d6c7a671bd610f99f9ba34c739aadd2da4d8dd9f109a7e666cc2ad2" dependencies = [ - "aes", - "as_variant", - "async-trait", - "bs58", - "byteorder 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "ctr", - "eyeball", - "futures-core", - "futures-util", - "hkdf", - "hmac", - "itertools 0.15.0", - "js_option", - "matrix-sdk-common", - "pbkdf2", - "rand 0.10.2", - "rmp-serde", - "ruma", - "serde", - "serde_json", - "sha2 0.11.0", - "subtle", - "thiserror 2.0.20", - "time", - "tokio", - "tokio-stream", - "tracing", - "ulid", - "url", - "vodozemac", - "zeroize", + "convert_case 0.6.0", + "napi-derive-backend-ohos", + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] -name = "matrix-sdk-indexeddb" -version = "0.18.0" -source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +name = "napi-ohos" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad5a3bbb2ae61f345b8c11776f2e79fc2bb71d1901af9a5f81f03c9238a05d86" dependencies = [ - "async-trait", - "base64 0.23.1", - "futures-util", - "getrandom 0.4.3", - "gloo-utils", - "hkdf", - "js-sys", - "matrix-sdk-base", - "matrix-sdk-crypto", - "matrix-sdk-store-encryption", - "matrix_indexed_db_futures", - "rmp-serde", - "ruma", - "serde", - "serde-wasm-bindgen", - "serde_bytes", - "serde_json", - "sha2 0.11.0", - "thiserror 2.0.20", - "tokio", - "tracing", - "uuid", - "wasm-bindgen", - "web-sys", - "zeroize", + "bitflags 2.13.2", + "ctor", + "napi-sys-ohos", + "once_cell 1.21.4", ] [[package]] -name = "matrix-sdk-sqlite" -version = "0.18.0" -source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +name = "napi-sys-ohos" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f101404db01422d034db5afa63eefff6d9c8f66c0894278bc456b4c30954e166" dependencies = [ - "as_variant", - "async-trait", - "deadpool", - "deadpool-sync", - "itertools 0.15.0", - "matrix-sdk-base", - "matrix-sdk-crypto", - "matrix-sdk-store-encryption", - "num_cpus", - "rmp-serde", - "ruma", - "rusqlite", - "serde", - "serde_json", - "serde_path_to_error", - "thiserror 2.0.20", - "tokio", - "tracing", - "vodozemac", - "zeroize", + "libloading 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "matrix-sdk-store-encryption" -version = "0.18.0" -source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" dependencies = [ - "base64 0.23.1", - "blake3", - "chacha20poly1305", - "getrandom 0.4.3", - "hkdf", - "hmac", - "pbkdf2", - "rand 0.10.2", - "rmp-serde", - "serde", - "serde_bytes", - "serde_json", - "sha2 0.11.0", - "thiserror 2.0.20", - "zeroize", + "smallvec 1.16.1", ] [[package]] -name = "matrix-sdk-ui" -version = "0.18.0" -source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "as_variant", - "async-rx", - "async-stream", - "async_cell", "bitflags 2.13.2", - "chrono", - "emojis", - "eyeball", - "eyeball-im", - "eyeball-im-util", - "futures-core", - "futures-util", - "fuzzy-matcher", - "growable-bloom-filter", - "imbl", - "indexmap 2.14.2", - "itertools 0.15.0", - "matrix-sdk", - "matrix-sdk-base", - "matrix-sdk-common", - "mime", - "pin-project-lite", - "ruma", - "serde", - "serde_json", - "thiserror 2.0.20", - "tokio", - "tokio-stream", - "tracing", - "unicode-normalization", - "unicode-segmentation 1.13.3", + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg_aliases 0.2.2", + "libc", ] [[package]] -name = "matrix_indexed_db_futures" -version = "0.7.0" +name = "nix" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245ff6a224b4df7b0c90dda2dd5a6eb46112708d49e8bdd8b007fccb09fea8e4" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "accessory", + "bitflags 2.13.2", "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "delegate-display", - "derive_more 2.1.1", - "fancy_constructor", - "futures-core", - "js-sys", - "matrix_indexed_db_futures_macros_internal", - "sealed 0.6.0", - "serde", - "serde-wasm-bindgen", - "smallvec 1.16.1", - "thiserror 2.0.20", - "tokio", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm_evt_listener", - "web-sys", - "web-time", + "cfg_aliases 0.2.2", + "libc", +] + +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags 2.13.2", + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg_aliases 0.2.2", + "libc", +] + +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr 2.8.3", +] + +[[package]] +name = "nom_locate" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b577e2d69827c4740cba2b52efaad1c4cc7c73042860b199710b3575c68438d" +dependencies = [ + "bytecount", + "memchr 2.8.3", + "nom", +] + +[[package]] +name = "ntapi" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" +dependencies = [ + "winapi", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-integer" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" +dependencies = [ + "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "matrix_indexed_db_futures_macros_internal" -version = "1.0.0" +name = "num-traits" +version = "0.2.19" + +[[package]] +name = "num-traits" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b428aee5c0fe9e5babd29e99d289b7f64718c444989aac0442d1fd6d3e3f66d1" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ - "macroific 2.0.0", - "proc-macro2", - "quote", - "syn 2.0.119", + "autocfg", ] [[package]] -name = "memchr" -version = "2.7.6" +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] [[package]] -name = "memchr" -version = "2.8.3" +name = "oauth2" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" +checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d" +dependencies = [ + "base64 0.22.1", + "chrono", + "getrandom 0.2.17", + "http", + "rand 0.8.8", + "reqwest 0.12.28", + "serde", + "serde_json", + "serde_path_to_error", + "sha2 0.10.9", + "thiserror 1.0.69", + "url", +] [[package]] -name = "memmap2" -version = "0.9.11" +name = "oauth2-reqwest" +version = "0.1.0-alpha.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" +checksum = "234fb5c965bbce983ee5de636a7a51d6a3223da8067ea02f9ab2d2d78ac08be2" dependencies = [ - "libc", + "oauth2", + "reqwest 0.13.5", ] [[package]] -name = "memoffset" -version = "0.9.1" +name = "objc2" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" dependencies = [ - "autocfg", + "objc2-encode", ] [[package]] -name = "mermaid-rs-renderer" -version = "0.3.1" +name = "objc2-app-kit" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb5b469222ce6b9896edf0a969cf68464f5a028ef394fd6643f00bd943df117" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ - "anyhow", - "fontdb", - "json5", - "once_cell 1.21.4", - "regex", - "serde", - "serde_json", - "thiserror 2.0.20", - "ttf-parser 0.25.1", + "bitflags 2.13.2", + "block2", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", ] [[package]] -name = "mime" -version = "0.3.17" +name = "objc2-authentication-services" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +checksum = "ee6d6f7dab884a28adaec1012eb3889257a49cc145724e35f93ece2d209f8b25" +dependencies = [ + "block2", + "objc2", + "objc2-app-kit", + "objc2-foundation", +] [[package]] -name = "mime2ext" -version = "0.1.54" +name = "objc2-core-foundation" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf6f36070878c42c5233846cd3de24cf9016828fd47bc22957a687298bb21fc" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.13.2", + "dispatch2", + "objc2", +] [[package]] -name = "mime_guess" -version = "2.0.5" +name = "objc2-core-graphics" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ - "mime", - "unicase 2.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 2.13.2", + "dispatch2", + "objc2", + "objc2-core-foundation", + "objc2-io-surface", ] [[package]] -name = "miniz_oxide" -version = "0.8.9" +name = "objc2-core-location" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" dependencies = [ - "adler2", - "simd-adler32 0.3.10", + "objc2", + "objc2-foundation", ] [[package]] -name = "miniz_oxide" -version = "0.9.1" +name = "objc2-encode" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "adler2", - "simd-adler32 0.3.10", + "bitflags 2.13.2", + "block2", + "objc2", + "objc2-core-foundation", ] [[package]] -name = "mio" -version = "1.2.3" +name = "objc2-io-surface" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" dependencies = [ - "libc", - "wasi", - "windows-sys 0.61.2", + "bitflags 2.13.2", + "objc2", + "objc2-core-foundation", ] [[package]] -name = "moxcms" -version = "0.8.1" +name = "objc2-photos-ui" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +checksum = "fdddde9dfb986546746e3115021a85c4a83e1c5e9037b3c865a688fe9bf94ebb" dependencies = [ - "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", - "pxfm", + "bitflags 2.13.2", + "objc2", + "objc2-foundation", ] [[package]] -name = "naga" -version = "27.0.3" +name = "objc2-ui-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" dependencies = [ - "arrayvec 0.7.6", - "bit-set 0.8.0", - "bitflags 2.10.0", - "cfg-if 1.0.4", - "cfg_aliases 0.2.1", - "hashbrown 0.16.1", - "hexf-parse", - "indexmap 2.13.0", - "makepad-error-log", - "makepad-half", - "num-traits 0.2.19", - "once_cell 1.21.3", - "rustc-hash 1.1.0", - "spirv", - "thiserror 2.0.18", - "unicode-ident 1.0.22", + "bitflags 2.13.2", + "block2", + "objc2", + "objc2-core-foundation", + "objc2-foundation", + "objc2-uniform-type-identifiers", ] [[package]] -name = "napi-derive-backend-ohos" -version = "0.0.7" +name = "objc2-uniform-type-identifiers" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6b18d697bedddd2d4c9f8f76b49fe65bd81ed1c55a7eec21ba40c176c236ddc" +checksum = "7902ac02859fc1f7045f8b598c63f1ae0cc7efeaa06a9bc9f3d9a3c955974fa4" dependencies = [ - "convert_case", - "once_cell 1.21.4", - "proc-macro2", - "quote", - "regex", - "syn 2.0.119", + "objc2", + "objc2-foundation", ] [[package]] -name = "napi-derive-ohos" -version = "0.0.9" +name = "object" +version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8462d74a2d6c7a671bd610f99f9ba34c739aadd2da4d8dd9f109a7e666cc2ad2" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" dependencies = [ - "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "convert_case", - "napi-derive-backend-ohos", - "proc-macro2", - "quote", - "syn 2.0.119", + "memchr 2.8.3", ] [[package]] -name = "napi-ohos" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad5a3bbb2ae61f345b8c11776f2e79fc2bb71d1901af9a5f81f03c9238a05d86" +name = "octos-agent" +version = "2.0.3-rc.12" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133#9a1a8a7088625061d2bcc09d4a3ea35561206133" dependencies = [ - "bitflags 2.13.2", - "ctor", - "napi-sys-ohos", - "once_cell 1.21.4", + "async-trait", + "base64 0.22.1", + "chromiumoxide", + "chrono", + "dirs", + "dunce", + "eyre", + "flate2", + "futures", + "gix", + "glob", + "globset", + "hound", + "htmd", + "html5ever 0.35.0", + "ignore", + "keyring", + "lettre", + "libc", + "markup5ever_rcdom 0.35.0+unofficial", + "metrics", + "oauth2", + "octos-bus", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133)", + "octos-llm", + "octos-memory", + "octos-plugin", + "pdf-extract", + "redb", + "regex", + "reqwest 0.12.28", + "reqwest 0.13.5", + "rmcp", + "rustix", + "serde", + "serde_json", + "sha2 0.10.9", + "shlex 1.3.0", + "similar", + "tar", + "tempfile", + "tiny_http", + "tokio", + "tokio-util", + "toml 0.8.23", + "tracing", + "tree-sitter", + "tree-sitter-javascript", + "tree-sitter-python", + "tree-sitter-rust", + "tree-sitter-typescript", + "url", + "webbrowser", + "which 7.0.3", +] + +[[package]] +name = "octos-app" +version = "0.1.0" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133#9a1a8a7088625061d2bcc09d4a3ea35561206133" +dependencies = [ + "blake3", + "chrono", + "keyring", + "makepad-ai", + "makepad-code-editor", + "makepad-diagram-kit", + "makepad-widgets", + "octos-app-render", + "octos-app-store", + "octos-app-transport", + "octos-core 2.0.3-rc.12 (git+https://github.com/octos-org/octos.git?rev=18fcd3f16e527d2b601d7ae244f056fb711bb6b8)", + "octoscript-node", + "octoscript-ui-l0", + "reqwest 0.12.28", + "robius-open", + "serde", + "serde_json", + "streaming-markdown-kit", + "tokio", + "url", ] [[package]] -name = "napi-sys-ohos" -version = "0.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f101404db01422d034db5afa63eefff6d9c8f66c0894278bc456b4c30954e166" +name = "octos-app-render" +version = "0.1.0" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133#9a1a8a7088625061d2bcc09d4a3ea35561206133" dependencies = [ - "libloading 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)", + "streaming-markdown-kit", ] [[package]] -name = "ndk-context" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" - -[[package]] -name = "new_debug_unreachable" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" - -[[package]] -name = "nu-ansi-term" -version = "0.50.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +name = "octos-app-store" +version = "0.1.0" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133#9a1a8a7088625061d2bcc09d4a3ea35561206133" dependencies = [ - "windows-sys 0.61.2", + "chrono", + "keyring", + "log", + "octos-core 2.0.3-rc.12 (git+https://github.com/octos-org/octos.git?rev=18fcd3f16e527d2b601d7ae244f056fb711bb6b8)", + "serde", + "serde_json", + "uuid", ] [[package]] -name = "num-conv" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" - -[[package]] -name = "num-traits" -version = "0.2.19" - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +name = "octos-app-transport" +version = "0.1.0" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133#9a1a8a7088625061d2bcc09d4a3ea35561206133" dependencies = [ - "autocfg", + "android_logger", + "chrono", + "futures-util", + "log", + "octos-cli", + "octos-core 2.0.3-rc.12 (git+https://github.com/octos-org/octos.git?rev=18fcd3f16e527d2b601d7ae244f056fb711bb6b8)", + "reqwest 0.12.28", + "serde", + "serde_json", + "tokio", + "tokio-tungstenite 0.24.0", + "tracing-subscriber", + "url", + "uuid", ] [[package]] -name = "num_cpus" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +name = "octos-bus" +version = "2.0.3-rc.12" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133#9a1a8a7088625061d2bcc09d4a3ea35561206133" dependencies = [ - "hermit-abi", - "libc", + "aes 0.8.4", + "async-trait", + "axum", + "base64 0.22.1", + "cbc 0.1.2", + "chrono", + "chrono-tz", + "cron", + "eyre", + "futures", + "lru", + "metrics", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133)", + "reqwest 0.12.28", + "serde", + "serde_json", + "serde_yml", + "sha2 0.10.9", + "subtle", + "tokio", + "tracing", + "uuid", ] [[package]] -name = "oauth2" -version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d" +name = "octos-cli" +version = "2.0.3-rc.12" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133#9a1a8a7088625061d2bcc09d4a3ea35561206133" dependencies = [ + "agent-client-protocol", + "argon2", + "async-trait", + "axum", "base64 0.22.1", + "chacha20poly1305 0.10.1", "chrono", + "clap", + "clap_complete", + "color-eyre", + "colored", + "constant_time_eq 0.3.1", + "dirs", + "dunce", + "eyre", + "flate2", + "fs2", + "futures", "getrandom 0.2.17", - "http", + "iana-time-zone", + "image", + "keyring", + "lettre", + "libc", + "metrics", + "metrics-exporter-prometheus", + "octos-agent", + "octos-bus", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133)", + "octos-diagnostics", + "octos-fleet", + "octos-fleet-worker", + "octos-llm", + "octos-memory", + "octos-pipeline", + "octos-plugin", + "octos-sandbox", + "octos-services", + "octos-store", + "octos-swarm", + "octos-workflows", + "open", + "percent-encoding", + "qrcode", + "quick-xml", "rand 0.8.8", + "redb", + "regex", + "reqwest 0.12.28", + "rpassword", + "rust-embed", + "rustix", + "rustls", + "rustls-native-certs", + "rustyline", "serde", "serde_json", - "serde_path_to_error", "sha2 0.10.9", - "thiserror 1.0.69", + "subtle", + "sysinfo", + "tar", + "tempfile", + "tokio", + "tokio-tungstenite 0.26.2", + "tokio-util", + "toml 0.8.23", + "tower-http", + "tracing", + "tracing-appender", + "tracing-subscriber", "url", + "uuid", + "zip", ] [[package]] -name = "oauth2-reqwest" -version = "0.1.0-alpha.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234fb5c965bbce983ee5de636a7a51d6a3223da8067ea02f9ab2d2d78ac08be2" +name = "octos-core" +version = "2.0.3-rc.12" +source = "git+https://github.com/octos-org/octos.git?rev=18fcd3f16e527d2b601d7ae244f056fb711bb6b8#18fcd3f16e527d2b601d7ae244f056fb711bb6b8" dependencies = [ - "oauth2", - "reqwest", + "chrono", + "eyre", + "serde", + "serde_json", + "sha2 0.10.9", + "tracing", + "uuid", ] [[package]] -name = "objc2" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +name = "octos-core" +version = "2.0.3-rc.12" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133#9a1a8a7088625061d2bcc09d4a3ea35561206133" dependencies = [ - "objc2-encode", + "chrono", + "eyre", + "serde", + "serde_json", + "sha2 0.10.9", + "tracing", + "uuid", ] [[package]] -name = "objc2-app-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +name = "octos-diagnostics" +version = "2.0.3-rc.12" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133#9a1a8a7088625061d2bcc09d4a3ea35561206133" dependencies = [ - "bitflags 2.13.2", - "block2", - "objc2", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation", + "eyre", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133)", + "reqwest 0.12.28", + "serde", + "serde_json", ] [[package]] -name = "objc2-authentication-services" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6d6f7dab884a28adaec1012eb3889257a49cc145724e35f93ece2d209f8b25" +name = "octos-fleet" +version = "2.0.3-rc.12" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133#9a1a8a7088625061d2bcc09d4a3ea35561206133" dependencies = [ - "block2", - "objc2", - "objc2-app-kit", - "objc2-foundation", + "eyre", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133)", + "redb", + "rusqlite", + "serde", + "serde_json", + "tokio", + "tracing", + "uuid", ] [[package]] -name = "objc2-core-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +name = "octos-fleet-worker" +version = "2.0.3-rc.12" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133#9a1a8a7088625061d2bcc09d4a3ea35561206133" dependencies = [ - "bitflags 2.13.2", - "dispatch2", - "objc2", + "async-trait", + "eyre", + "octos-agent", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133)", + "octos-fleet", + "octos-llm", + "octos-memory", + "serde_json", + "tokio", + "tracing", ] [[package]] -name = "objc2-core-graphics" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +name = "octos-llm" +version = "2.0.3-rc.12" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133#9a1a8a7088625061d2bcc09d4a3ea35561206133" dependencies = [ - "bitflags 2.13.2", - "dispatch2", - "objc2", - "objc2-core-foundation", - "objc2-io-surface", + "async-trait", + "base64 0.22.1", + "chrono", + "eyre", + "futures", + "jsonwebtoken", + "libc", + "metrics", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133)", + "redb", + "reqwest 0.12.28", + "secrecy", + "serde", + "serde_json", + "sha2 0.10.9", + "tokio", + "tracing", ] [[package]] -name = "objc2-core-location" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +name = "octos-memory" +version = "2.0.3-rc.12" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133#9a1a8a7088625061d2bcc09d4a3ea35561206133" dependencies = [ - "objc2", - "objc2-foundation", + "bincode", + "chrono", + "eyre", + "hnsw_rs", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133)", + "redb", + "regex", + "serde", + "serde_json", + "tokio", + "tracing", + "uuid", ] [[package]] -name = "objc2-encode" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" +name = "octos-pipeline" +version = "2.0.3-rc.12" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133#9a1a8a7088625061d2bcc09d4a3ea35561206133" +dependencies = [ + "async-trait", + "chrono", + "eyre", + "futures", + "glob", + "octos-agent", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133)", + "octos-llm", + "octos-memory", + "octos-plugin", + "regex", + "serde", + "serde_json", + "tokio", + "tracing", +] [[package]] -name = "objc2-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +name = "octos-plugin" +version = "2.0.3-rc.12" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133#9a1a8a7088625061d2bcc09d4a3ea35561206133" dependencies = [ - "bitflags 2.13.2", - "block2", - "objc2", - "objc2-core-foundation", + "eyre", + "metrics", + "serde", + "serde_json", + "tokio", + "tracing", + "which 7.0.3", ] [[package]] -name = "objc2-io-surface" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +name = "octos-sandbox" +version = "0.1.0" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133#9a1a8a7088625061d2bcc09d4a3ea35561206133" dependencies = [ - "bitflags 2.13.2", - "objc2", - "objc2-core-foundation", + "clap", + "eyre", + "landlock", + "lddtree", + "libc", + "rappct", + "seccompiler", + "windows 0.62.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "objc2-photos-ui" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdddde9dfb986546746e3115021a85c4a83e1c5e9037b3c865a688fe9bf94ebb" +name = "octos-services" +version = "2.0.3-rc.12" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133#9a1a8a7088625061d2bcc09d4a3ea35561206133" dependencies = [ - "bitflags 2.13.2", - "objc2", - "objc2-foundation", + "chrono", + "dirs", + "eyre", + "flate2", + "futures", + "octos-bus", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133)", + "octos-llm", + "reqwest 0.12.28", + "serde", + "serde_json", + "tar", + "tokio", + "tracing", +] + +[[package]] +name = "octos-store" +version = "2.0.3-rc.12" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133#9a1a8a7088625061d2bcc09d4a3ea35561206133" +dependencies = [ + "base64 0.22.1", + "chrono", + "constant_time_eq 0.3.1", + "eyre", + "getrandom 0.2.17", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133)", + "redb", + "serde", + "serde_json", + "sha2 0.10.9", + "tokio", + "tracing", + "uuid", ] [[package]] -name = "objc2-ui-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +name = "octos-swarm" +version = "2.0.3-rc.12" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133#9a1a8a7088625061d2bcc09d4a3ea35561206133" dependencies = [ - "bitflags 2.13.2", - "block2", - "objc2", - "objc2-core-foundation", - "objc2-foundation", - "objc2-uniform-type-identifiers", + "async-trait", + "chrono", + "eyre", + "metrics", + "octos-agent", + "redb", + "serde", + "serde_json", + "sha2 0.10.9", + "tokio", + "tracing", + "uuid", ] [[package]] -name = "objc2-uniform-type-identifiers" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7902ac02859fc1f7045f8b598c63f1ae0cc7efeaa06a9bc9f3d9a3c955974fa4" +name = "octos-workflows" +version = "2.0.3-rc.12" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133#9a1a8a7088625061d2bcc09d4a3ea35561206133" dependencies = [ - "objc2", - "objc2-foundation", + "chrono", + "eyre", + "octos-agent", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133)", + "octos-pipeline", + "serde", + "serde_json", + "tokio", + "tracing", ] [[package]] @@ -4183,10 +7067,21 @@ dependencies = [ "makepad-widgets", "makepad-wm-api", "makepad-wm-theme", + "octosense-appcard", "octosense-reference", "rinx", ] +[[package]] +name = "octosense-appcard" +version = "0.1.0" +dependencies = [ + "makepad-app-module", + "makepad-widgets", + "octos-app", + "serde_json", +] + [[package]] name = "octosense-reference" version = "0.1.0" @@ -4217,6 +7112,22 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "open" +version = "5.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa576c76302b7b808eecc68061e67336c47833ef9d22caa74dda10fa9675eebc" +dependencies = [ + "is-wsl", + "libc", +] + [[package]] name = "openssl-probe" version = "0.2.1" @@ -4239,6 +7150,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "owo-colors" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c45bb4a6ae1280ec0803b1ef9d3455eb50f01efbbe1447ab020f1d54fba9d8" + [[package]] name = "parking" version = "2.2.1" @@ -4268,12 +7185,39 @@ dependencies = [ "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "pastey" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" +[[package]] +name = "pastey" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4" + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest 0.10.7", + "hmac 0.12.1", +] + [[package]] name = "pbkdf2" version = "0.13.0" @@ -4283,30 +7227,96 @@ dependencies = [ "digest 0.11.3", ] +[[package]] +name = "pdf-extract" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c2f44c6c642e359e2fe7f662bf5438db3811b6b4be60afc6de04b619ce51e1a" +dependencies = [ + "adobe-cmap-parser", + "cff-parser", + "encoding_rs", + "euclid 0.20.14", + "log", + "lopdf", + "postscript", + "type1-encoding-parser", + "unicode-normalization", +] + +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64 0.22.1", + "serde_core", +] + [[package]] name = "percent-encoding" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_shared 0.11.3", +] + +[[package]] +name = "phf" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7" +dependencies = [ + "phf_shared 0.12.1", +] + [[package]] name = "phf" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" dependencies = [ - "phf_shared", + "phf_macros", + "phf_shared 0.13.1", "serde", ] +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", +] + [[package]] name = "phf_codegen" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" dependencies = [ - "phf_generator", - "phf_shared", + "phf_generator 0.13.1", + "phf_shared 0.13.1", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared 0.11.3", + "rand 0.8.8", ] [[package]] @@ -4316,7 +7326,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" dependencies = [ "fastrand", - "phf_shared", + "phf_shared 0.13.1", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator 0.13.1", + "phf_shared 0.13.1", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "phf_shared" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981" +dependencies = [ + "siphasher", ] [[package]] @@ -4334,6 +7375,26 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "pin-project-lite" version = "0.2.17" @@ -4361,6 +7422,12 @@ version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + [[package]] name = "png" version = "0.17.16" @@ -4407,6 +7474,17 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash 0.5.1", +] + [[package]] name = "poly1305" version = "0.9.1" @@ -4414,7 +7492,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e2d0073b297041425c7c3df6eb4792d598a15323fe63346852b092eca02904c" dependencies = [ "cpufeatures 0.3.1", - "universal-hash", + "universal-hash 0.6.1", ] [[package]] @@ -4426,6 +7504,33 @@ dependencies = [ "arrayvec 0.7.8", ] +[[package]] +name = "pom" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60f6ce597ecdcc9a098e7fddacb1065093a3d66446fa16c675e7e71d1b5c28e6" + +[[package]] +name = "portable-atomic" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" + +[[package]] +name = "portable-atomic-util" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "postscript" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78451badbdaebaf17f053fd9152b3ffb33b516104eacb45e7864aaa9c712f306" + [[package]] name = "potential_utf" version = "0.1.6" @@ -4462,7 +7567,7 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit", + "toml_edit 0.25.15+spec-1.1.0", ] [[package]] @@ -4474,6 +7579,29 @@ dependencies = [ "unicode-ident 1.0.24", ] +[[package]] +name = "process-wrap" +version = "9.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f95f6259ebd0640685b7cd26d635861f39208df0f89b2b61ff5cf76310fa54f6" +dependencies = [ + "futures", + "indexmap 2.14.2", + "nix 0.31.3", + "tokio", + "tracing", + "windows 0.62.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "prodash" +version = "31.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "962200e2d7d551451297d9fdce85138374019ada198e30ea9ede38034e27604c" +dependencies = [ + "parking_lot", +] + [[package]] name = "prost" version = "0.14.4" @@ -4531,12 +7659,42 @@ version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea" +[[package]] +name = "qrcode" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d68782463e408eb1e668cf6152704bd856c78c5b6417adaee3203d8f4c1fc9ec" + +[[package]] +name = "quanta" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7" +dependencies = [ + "crossbeam-utils", + "libc", + "once_cell 1.21.4", + "raw-cpuid", + "wasi", + "web-sys", + "winapi", +] + [[package]] name = "quick-error" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" +[[package]] +name = "quick-xml" +version = "0.37.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +dependencies = [ + "memchr 2.8.3", +] + [[package]] name = "quinn" version = "0.11.11" @@ -4603,12 +7761,34 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "quoted_printable" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478e0585659a122aa407eb7e3c0e1fa51b1d8a870038bd29f0cf4a8551eea972" + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "r-efi" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + [[package]] name = "rand" version = "0.8.8" @@ -4616,17 +7796,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" dependencies = [ "libc", - "rand_chacha", + "rand_chacha 0.3.1", "rand_core 0.6.4", ] +[[package]] +name = "rand" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + [[package]] name = "rand" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" dependencies = [ - "chacha20", + "chacha20 0.10.2", "getrandom 0.4.3", "rand_core 0.10.1", ] @@ -4641,6 +7831,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + [[package]] name = "rand_core" version = "0.6.4" @@ -4655,6 +7855,9 @@ name = "rand_core" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] [[package]] name = "rand_core" @@ -4686,12 +7889,60 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a611d15b50743feb4c76b7d03edcb0e64f399c26961e4efe6975bc398be6aa3d" +[[package]] +name = "rapidhash" +version = "4.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5da7e78a036ce858e8d55b7e7dc8ba3a88b78350fd2155d3591bbd966b58589e" +dependencies = [ + "rustversion", +] + +[[package]] +name = "rappct" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11762a6aea3b0a87a51b21cb2085d252f10c01cd0f3f505e1da002cf6e2427da" +dependencies = [ + "thiserror 2.0.20", + "windows 0.62.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags 2.13.2", +] + [[package]] name = "raw-window-handle" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "readlock" version = "0.1.11" @@ -4707,6 +7958,15 @@ dependencies = [ "tokio", ] +[[package]] +name = "redb" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eca1e9d98d5a7e9002d0013e18d5a9b000aee942eb134883a82f06ebffb6c01" +dependencies = [ + "libc", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -4716,6 +7976,17 @@ dependencies = [ "bitflags 2.13.2", ] +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 1.0.69", +] + [[package]] name = "redox_users" version = "0.5.3" @@ -4726,6 +7997,26 @@ dependencies = [ "thiserror 2.0.20", ] +[[package]] +name = "ref-cast" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + [[package]] name = "regex" version = "1.13.1" @@ -4755,6 +8046,49 @@ version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams 0.4.2", + "web-sys", + "webpki-roots", +] + [[package]] name = "reqwest" version = "0.13.5" @@ -4780,6 +8114,8 @@ dependencies = [ "rustls", "rustls-pki-types", "rustls-platform-verifier", + "serde", + "serde_json", "sync_wrapper", "tokio", "tokio-rustls", @@ -4790,7 +8126,7 @@ dependencies = [ "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams", + "wasm-streams 0.5.0", "web-sys", ] @@ -4915,7 +8251,7 @@ dependencies = [ "serde_json", "thiserror 2.0.20", "tokio", - "toml", + "toml 1.1.6+spec-1.1.0", "tracing-subscriber", "unicode-segmentation 1.13.3", "url", @@ -4923,6 +8259,54 @@ dependencies = [ "winresource", ] +[[package]] +name = "rmcp" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1f571c72940a19d9532fe52dbea8bc9912bf1d766c2970bb824056b86f3f59" +dependencies = [ + "async-trait", + "base64 0.22.1", + "bytes", + "chrono", + "futures", + "http", + "http-body", + "http-body-util", + "oauth2", + "pastey 0.2.3", + "pin-project-lite", + "process-wrap", + "rand 0.10.2", + "reqwest 0.13.5", + "rmcp-macros", + "schemars 1.2.2", + "serde", + "serde_json", + "sse-stream", + "thiserror 2.0.20", + "tokio", + "tokio-stream", + "tokio-util", + "tower-service", + "tracing", + "url", + "uuid", +] + +[[package]] +name = "rmcp-macros" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aad0035b69380782d78ea95b508327e6deaa2235909053e596eea8f27b5e1d5" +dependencies = [ + "darling 0.23.0", + "proc-macro2", + "quote", + "serde_json", + "syn 2.0.119", +] + [[package]] name = "rmp" version = "0.8.15" @@ -4963,7 +8347,7 @@ name = "robius-directories" version = "6.0.1" source = "git+https://github.com/project-robius/robius#cca3cc3dc99d8fcb6c69a127cd28d47ba8f3e3eb" dependencies = [ - "dirs-sys", + "dirs-sys 0.5.0", "jni 0.21.1", "robius-android-env", ] @@ -5090,6 +8474,17 @@ dependencies = [ "memchr 2.8.3", ] +[[package]] +name = "rpassword" +version = "7.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da316a15f47e3d053de9cb2c439650bd8fa4aaeb9365f2e5f27f492ff73c196" +dependencies = [ + "libc", + "rtoolbox", + "windows-sys 0.61.2", +] + [[package]] name = "rqrr" version = "0.10.1" @@ -5101,6 +8496,26 @@ dependencies = [ "lru", ] +[[package]] +name = "rsqlite-vfs" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51c9ae4df8a7fba42103df5c621fa3c37eccf3a3c650879e90fc48b11cc192c" +dependencies = [ + "hashbrown 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror 2.0.20", +] + +[[package]] +name = "rtoolbox" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a1efe12a1469752d0e6ff5ebec0b6ef4924cc5c4c71046b0ec730040535819d" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "ruma" version = "0.16.0" @@ -5199,7 +8614,7 @@ version = "0.8.0" source = "git+https://github.com/project-robius/ruma.git?branch=tsp#7a912c8579871e72468ee5a4deecc16615e73099" dependencies = [ "as_variant", - "html5ever", + "html5ever 0.39.0", "ruma-common", "tracing", "wildmatch", @@ -5227,7 +8642,7 @@ dependencies = [ "ruma-identifiers-validation", "serde", "syn 3.0.5", - "toml", + "toml 1.1.6+spec-1.1.0", ] [[package]] @@ -5242,8 +8657,50 @@ dependencies = [ "hashlink", "libsqlite3-sys", "smallvec 1.16.1", + "sqlite-wasm-rs", ] +[[package]] +name = "rust-embed" +version = "8.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e7760e252aaba7b09f4be00e36476cf585bdb68a53552ac954cdf504ab4bc9" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bcfc4d6f53af43755f7a723e4b6b8794fcce052a178dd8c6c1dadc5f5343097" +dependencies = [ + "mime_guess", + "proc-macro2", + "quote", + "rust-embed-utils", + "syn 2.0.119", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ffa149f6aa81b58a5b3011d01a857c4ed12c7a732d2c51947a4c7c692185f0" +dependencies = [ + "sha2 0.11.0", + "walkdir", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb" + [[package]] name = "rustc-hash" version = "1.1.0" @@ -5283,7 +8740,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6725596c3f2c3a0aef021139e145d4eafe314a6623e4680ca83852b2c67ab2ba" dependencies = [ "aws-lc-rs", + "log", "once_cell 1.21.4", + "ring", "rustls-pki-types", "rustls-webpki", "subtle", @@ -5299,7 +8758,7 @@ dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework", + "security-framework 3.7.0", ] [[package]] @@ -5318,7 +8777,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" dependencies = [ - "core-foundation", + "core-foundation 0.10.1", "core-foundation-sys", "jni 0.22.4", "log", @@ -5327,7 +8786,7 @@ dependencies = [ "rustls-native-certs", "rustls-platform-verifier-android", "rustls-webpki", - "security-framework", + "security-framework 3.7.0", "security-framework-sys", "webpki-root-certs", "windows-sys 0.61.2", @@ -5357,6 +8816,58 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" +[[package]] +name = "rusty-mermaid" +version = "0.2.0" +source = "git+https://github.com/ZhangHanDong/rusty-mermaid.git#68a429b0472d57bce992c7bdddb59aa0c967e2f8" +dependencies = [ + "rusty-mermaid-core", + "rusty-mermaid-diagrams", + "rusty-mermaid-svg", +] + +[[package]] +name = "rusty-mermaid-core" +version = "0.2.0" +source = "git+https://github.com/ZhangHanDong/rusty-mermaid.git#68a429b0472d57bce992c7bdddb59aa0c967e2f8" + +[[package]] +name = "rusty-mermaid-dagre" +version = "0.2.0" +source = "git+https://github.com/ZhangHanDong/rusty-mermaid.git#68a429b0472d57bce992c7bdddb59aa0c967e2f8" +dependencies = [ + "rusty-mermaid-core", + "rusty-mermaid-graph", +] + +[[package]] +name = "rusty-mermaid-diagrams" +version = "0.2.0" +source = "git+https://github.com/ZhangHanDong/rusty-mermaid.git#68a429b0472d57bce992c7bdddb59aa0c967e2f8" +dependencies = [ + "rusty-mermaid-core", + "rusty-mermaid-dagre", + "rusty-mermaid-graph", + "thiserror 2.0.20", + "winnow 0.7.15", +] + +[[package]] +name = "rusty-mermaid-graph" +version = "0.2.0" +source = "git+https://github.com/ZhangHanDong/rusty-mermaid.git#68a429b0472d57bce992c7bdddb59aa0c967e2f8" +dependencies = [ + "rusty-mermaid-core", +] + +[[package]] +name = "rusty-mermaid-svg" +version = "0.2.0" +source = "git+https://github.com/ZhangHanDong/rusty-mermaid.git#68a429b0472d57bce992c7bdddb59aa0c967e2f8" +dependencies = [ + "rusty-mermaid-core", +] + [[package]] name = "rustybuzz" version = "0.18.0" @@ -5390,6 +8901,28 @@ dependencies = [ "unicode-script 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rustyline" +version = "15.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ee1e066dc922e513bda599c6ccb5f3bb2b0ea5870a579448f2622993f0a9a2f" +dependencies = [ + "bitflags 2.13.2", + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "clipboard-win", + "fd-lock", + "home", + "libc", + "log", + "memchr 2.8.3", + "nix 0.29.0", + "radix_trie", + "unicode-segmentation 1.13.3", + "unicode-width", + "utf8parse", + "windows-sys 0.59.0", +] + [[package]] name = "ryu" version = "1.0.23" @@ -5432,6 +8965,44 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a" +dependencies = [ + "chrono", + "dyn-clone", + "ref-cast", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98c67716b46af2f0b8cf752abc930f6f9aecfbf671ecfb531db8a31dbe4e2ba" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 3.0.5", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -5448,6 +9019,26 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scroll" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1257cd4248b4132760d6524d6dda4e053bc648c9070b960929bf50cfb1e7add" +dependencies = [ + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1a36a382ed65dbcc0ab47fd5e9a94112417ccd34560a392ef3b7b0f0ec39148" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + [[package]] name = "sdfer" version = "0.2.1" @@ -5474,6 +9065,38 @@ dependencies = [ "syn 3.0.5", ] +[[package]] +name = "seccompiler" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4ae55de56877481d112a559bbc12667635fdaf5e005712fd4e2b2fa50ffc884" +dependencies = [ + "libc", +] + +[[package]] +name = "secrecy" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" +dependencies = [ + "serde", + "zeroize", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.13.2", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + [[package]] name = "security-framework" version = "3.7.0" @@ -5481,7 +9104,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ "bitflags 2.13.2", - "core-foundation", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -5554,6 +9177,17 @@ dependencies = [ "syn 3.0.5", ] +[[package]] +name = "serde_derive_internals" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f852137cce035d6a4df67ccce505ff6b3e9fd3a10e3e52b24dc71e650bb1a9bd" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + [[package]] name = "serde_html_form" version = "0.4.1" @@ -5573,6 +9207,7 @@ version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ + "indexmap 2.14.2", "itoa", "memchr 2.8.3", "serde", @@ -5602,6 +9237,15 @@ dependencies = [ "syn 3.0.5", ] +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + [[package]] name = "serde_spanned" version = "1.1.1" @@ -5623,6 +9267,54 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "935177bb8c0cd8ca1a4e6d1a2ac8988bea69cab4f9d3a31311e012ad27868ea4" +dependencies = [ + "base64 0.23.1", + "bs58", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.2", + "jiff", + "schemars 0.9.0", + "schemars 1.2.2", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d607aa01a3cb0ad757d6fd216136910db3c97b102fe686585689615a02dbcdc" +dependencies = [ + "darling 0.24.1", + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "serde_yml" +version = "0.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59e2dd588bf1597a252c3b920e0143eb99b0f76e4e082f4c92ce34fbc9e71ddd" +dependencies = [ + "indexmap 2.14.2", + "itoa", + "libyml", + "memchr 2.8.3", + "ryu", + "serde", + "version_check", +] + [[package]] name = "serdect" version = "0.4.3" @@ -5633,6 +9325,27 @@ dependencies = [ "serde", ] +[[package]] +name = "sha1" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" +dependencies = [ + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha1-checked" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89f599ac0c323ebb1c6082821a54962b839832b03984598375bff3975b804423" +dependencies = [ + "digest 0.10.7", + "sha1", +] + [[package]] name = "sha2" version = "0.10.9" @@ -5664,6 +9377,18 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shell-words" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "shlex" version = "2.0.1" @@ -5715,6 +9440,24 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" +[[package]] +name = "similar" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" + +[[package]] +name = "simple_asn1" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" +dependencies = [ + "num-bigint", + "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror 2.0.20", + "time", +] + [[package]] name = "simplecss" version = "0.2.2" @@ -5730,6 +9473,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" +[[package]] +name = "sketches-ddsketch" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6f73aeb92d671e0cc4dca167e59b2deb6387c375391bc99ee743f326994a2b" + [[package]] name = "slab" version = "0.4.12" @@ -5768,6 +9517,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "spin" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" + [[package]] name = "spirv" version = "0.3.0+sdk-1.3.268.0" @@ -5775,12 +9530,60 @@ dependencies = [ "bitflags 2.10.0", ] +[[package]] +name = "sqlite-wasm-rs" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc3efc0da82635d7e1ced0053bbbfa8c7ab9645d0bf36ceb4f7127bb85315d75" +dependencies = [ + "cc", + "js-sys", + "rsqlite-vfs", + "wasm-bindgen", +] + +[[package]] +name = "sse-stream" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c25ac7aff0abd1dbc474536e40416e1102c7dd9bfba0b9861c6d357f835dcfb4" +dependencies = [ + "bytes", + "futures-util", + "http-body", + "http-body-util", + "pin-project-lite", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520" + +[[package]] +name = "streaming-markdown-kit" +version = "0.1.0" +source = "git+https://github.com/Project-Robius-China/streaming-markdown-kit.git?branch=main#68b8cdf91ea7579f4bd607b09f005cecfa8aca6f" +dependencies = [ + "anyhow", + "resvg", + "rusty-mermaid", + "usvg", +] + [[package]] name = "strict-num" version = "0.1.1" @@ -5790,6 +9593,19 @@ dependencies = [ "float-cmp", ] +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared 0.11.3", + "precomputed-hash", + "serde", +] + [[package]] name = "string_cache" version = "0.9.0" @@ -5798,8 +9614,21 @@ checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" dependencies = [ "new_debug_unreachable", "parking_lot", - "phf_shared", + "phf_shared 0.13.1", "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", ] [[package]] @@ -5808,18 +9637,50 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" dependencies = [ - "phf_generator", - "phf_shared", + "phf_generator 0.13.1", + "phf_shared 0.13.1", "proc-macro2", "quote", ] +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-normalization", + "unicode-properties 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "subtle" version = "2.6.1" @@ -5836,6 +9697,12 @@ dependencies = [ "siphasher", ] +[[package]] +name = "symlink" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" + [[package]] name = "syn" version = "1.0.109" @@ -5879,32 +9746,70 @@ dependencies = [ ] [[package]] -name = "synstructure" -version = "0.13.2" +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "syntect" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "656b45c05d95a5704399aeef6bd0ddec7b2b3531b7c9e900abbf7c4d2190c925" +dependencies = [ + "bincode", + "fancy-regex", + "flate2", + "fnv", + "once_cell 1.21.4", + "regex-syntax", + "serde", + "serde_derive", + "thiserror 2.0.20", + "walkdir", +] + +[[package]] +name = "sysctl" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01198a2debb237c62b6826ec7081082d951f46dbb64b0e8c7649a452230d1dfc" +dependencies = [ + "bitflags 2.13.2", + "byteorder 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "enum-as-inner", + "libc", + "thiserror 1.0.69", + "walkdir", +] + +[[package]] +name = "sysinfo" +version = "0.34.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +checksum = "a4b93974b3d3aeaa036504b8eefd4c039dced109171c1ae973f1dc63b2c7e4b2" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", + "libc", + "memchr 2.8.3", + "ntapi", + "objc2-core-foundation", + "windows 0.56.0", ] [[package]] -name = "syntect" -version = "5.3.0" +name = "tar" +version = "0.4.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "656b45c05d95a5704399aeef6bd0ddec7b2b3531b7c9e900abbf7c4d2190c925" +checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" dependencies = [ - "bincode", - "fancy-regex", - "flate2", - "fnv", - "once_cell 1.21.4", - "regex-syntax", - "serde", - "serde_derive", - "thiserror 2.0.20", - "walkdir", + "filetime", + "libc", + "xattr", ] [[package]] @@ -5920,6 +9825,17 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + [[package]] name = "tendril" version = "0.5.1" @@ -6062,6 +9978,18 @@ dependencies = [ "strict-num", ] +[[package]] +name = "tiny_http" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82" +dependencies = [ + "ascii", + "chunked_transfer", + "httpdate", + "log", +] + [[package]] name = "tinystr" version = "0.8.4" @@ -6087,7 +10015,9 @@ dependencies = [ "bytes", "libc", "mio", + "parking_lot", "pin-project-lite", + "signal-hook-registry", "socket2", "tokio-macros", "windows-sys 0.61.2", @@ -6126,6 +10056,50 @@ dependencies = [ "tokio-util", ] +[[package]] +name = "tokio-tungstenite" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" +dependencies = [ + "futures-util", + "log", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tungstenite 0.24.0", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" +dependencies = [ + "futures-util", + "log", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tungstenite 0.26.2", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f72a05e828585856dacd553fba484c242c46e391fb0e58917c942ee9202915c" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite 0.29.0", +] + [[package]] name = "tokio-util" version = "0.7.19" @@ -6140,6 +10114,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + [[package]] name = "toml" version = "1.1.6+spec-1.1.0" @@ -6148,11 +10134,20 @@ checksum = "920602543f0911ab71da12c50d59701da54c196d1a2bf5cb4b75667f137a406a" dependencies = [ "indexmap 2.14.2", "serde_core", - "serde_spanned", - "toml_datetime", + "serde_spanned 1.1.1", + "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", "toml_writer", - "winnow", + "winnow 1.0.4", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", ] [[package]] @@ -6164,6 +10159,20 @@ dependencies = [ "serde_core", ] +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap 2.14.2", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_write", + "winnow 0.7.15", +] + [[package]] name = "toml_edit" version = "0.25.15+spec-1.1.0" @@ -6171,9 +10180,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1340ea94a5856333492c9064b02c778b191dd2c853778d9609debdcdfea3a614" dependencies = [ "indexmap 2.14.2", - "toml_datetime", + "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow", + "winnow 1.0.4", ] [[package]] @@ -6182,9 +10191,15 @@ version = "1.1.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" dependencies = [ - "winnow", + "winnow 1.0.4", ] +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + [[package]] name = "toml_writer" version = "1.1.2+spec-1.1.0" @@ -6227,6 +10242,7 @@ dependencies = [ "tower", "tower-layer", "tower-service", + "tracing", "url", ] @@ -6254,6 +10270,19 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-appender" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c" +dependencies = [ + "crossbeam-channel", + "symlink", + "thiserror 2.0.20", + "time", + "tracing-subscriber", +] + [[package]] name = "tracing-attributes" version = "0.1.31" @@ -6275,6 +10304,16 @@ dependencies = [ "valuable", ] +[[package]] +name = "tracing-error" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" +dependencies = [ + "tracing", + "tracing-subscriber", +] + [[package]] name = "tracing-log" version = "0.2.0" @@ -6286,6 +10325,16 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", +] + [[package]] name = "tracing-subscriber" version = "0.3.23" @@ -6296,12 +10345,74 @@ dependencies = [ "nu-ansi-term", "once_cell 1.21.4", "regex-automata", + "serde", + "serde_json", "sharded-slab", "smallvec 1.16.1", "thread_local", "tracing", "tracing-core", "tracing-log", + "tracing-serde", +] + +[[package]] +name = "tree-sitter" +version = "0.24.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5387dffa7ffc7d2dae12b50c6f7aab8ff79d6210147c6613561fc3d474c6f75" +dependencies = [ + "cc", + "regex", + "regex-syntax", + "streaming-iterator", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-javascript" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf40bf599e0416c16c125c3cec10ee5ddc7d1bb8b0c60fa5c4de249ad34dc1b1" +dependencies = [ + "cc", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-language" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca0d1bf6fdd806e43ae5198f82f527056d359def39e54e67a0f478ac09dac081" + +[[package]] +name = "tree-sitter-python" +version = "0.23.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d065aaa27f3aaceaf60c1f0e0ac09e1cb9eb8ed28e7bcdaa52129cffc7f4b04" +dependencies = [ + "cc", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-rust" +version = "0.23.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8ccb3e3a3495c8a943f6c3fd24c3804c471fd7f4f16087623c7fa4c0068e8a" +dependencies = [ + "cc", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-typescript" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5f76ed8d947a75cc446d5fccd8b602ebf0cde64ccf2ffa434d873d7a575eff" +dependencies = [ + "cc", + "tree-sitter-language", ] [[package]] @@ -6323,6 +10434,87 @@ dependencies = [ "core_maths", ] +[[package]] +name = "tungstenite" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" +dependencies = [ + "byteorder 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.8.8", + "rustls", + "rustls-pki-types", + "sha1", + "thiserror 1.0.69", + "utf-8", +] + +[[package]] +name = "tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.9.5", + "rustls", + "rustls-pki-types", + "sha1", + "thiserror 2.0.20", + "utf-8", +] + +[[package]] +name = "tungstenite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.9.5", + "sha1", + "thiserror 2.0.20", + "utf-8", +] + +[[package]] +name = "tungstenite" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c01152af293afb9c7c2a57e4b559c5620b421f6d133261c60dd2d0cdb38e6b8" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.9.5", + "sha1", + "thiserror 2.0.20", +] + +[[package]] +name = "type1-encoding-parser" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa10c302f5a53b7ad27fd42a3996e23d096ba39b5b8dd6d9e683a05b01bee749" +dependencies = [ + "pom", +] + [[package]] name = "typed-arena" version = "2.0.2" @@ -6368,6 +10560,15 @@ dependencies = [ "web-time", ] +[[package]] +name = "uluru" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c8a2469e56e6e5095c82ccd3afb98dad95f7af7929aab6d8ba8d6e0f73657da" +dependencies = [ + "arrayvec 0.7.8", +] + [[package]] name = "unicase" version = "2.9.0" @@ -6398,6 +10599,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfa6e8c60bb66d49db113e0125ee8711b7647b5579dc7f5f19c42357ed039fe" +[[package]] +name = "unicode-bom" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217" + [[package]] name = "unicode-ccc" version = "0.3.0" @@ -6479,6 +10686,16 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common 0.1.7", + "subtle", +] + [[package]] name = "universal-hash" version = "0.6.1" @@ -6541,6 +10758,12 @@ dependencies = [ "xmlwriter", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -6588,18 +10811,18 @@ name = "vodozemac" version = "0.10.0" source = "git+https://github.com/matrix-org/vodozemac/?rev=cfc4eee#cfc4eeebdf0953314144a6b709449b0434019ca2" dependencies = [ - "aes", + "aes 0.9.3", "arrayvec 0.7.8", "base64 0.22.1", "base64ct", - "cbc", - "chacha20poly1305", - "cipher", + "cbc 0.2.1", + "chacha20poly1305 0.11.0", + "cipher 0.5.2", "curve25519-dalek", "ed25519-dalek", "getrandom 0.4.3", "hkdf", - "hmac", + "hmac 0.13.0", "hpke", "matrix-pickle", "prost", @@ -6639,6 +10862,15 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" version = "0.2.128" @@ -6694,6 +10926,19 @@ dependencies = [ "unicode-ident 1.0.24", ] +[[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-streams" version = "0.5.0" @@ -6790,16 +11035,44 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web_atoms" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57ffde1dc01240bdf9992e3205668b235e59421fd085e8a317ed98da0178d414" +dependencies = [ + "phf 0.11.3", + "phf_codegen 0.11.3", + "string_cache 0.8.9", + "string_cache_codegen 0.5.4", +] + [[package]] name = "web_atoms" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba8b815c1b593dc0baf78dd0f4fc8fdb2de53198fb1163738093e9a311c33fb3" dependencies = [ - "phf", - "phf_codegen", - "string_cache", - "string_cache_codegen", + "phf 0.13.1", + "phf_codegen 0.13.1", + "string_cache 0.9.0", + "string_cache_codegen 0.6.1", +] + +[[package]] +name = "webbrowser" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62c35be770821a214dbc362fc26908c853e776c0004294d0b10b8a6bad582f94" +dependencies = [ + "jni 0.22.4", + "log", + "ndk-context", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "url", + "web-sys", ] [[package]] @@ -6811,6 +11084,15 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "webpki-roots" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "weezl" version = "0.1.12" @@ -6821,6 +11103,27 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" +[[package]] +name = "which" +version = "7.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d643ce3fd3e5b54854602a080f34fb10ab75e0b813ee32d00ca2b44fa74762" +dependencies = [ + "either", + "env_home", + "rustix", + "winsafe", +] + +[[package]] +name = "which" +version = "8.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bae2f2b2b816647a1cab1acc91f5bd20812d53cb344382635ec2181940c8034f" +dependencies = [ + "libc", +] + [[package]] name = "wide" version = "0.7.33" @@ -6831,12 +11134,34 @@ dependencies = [ "safe_arch", ] +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + [[package]] name = "wildmatch" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29333c3ea1ba8b17211763463ff24ee84e41c78224c16b001cd907e663a38c68" +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + [[package]] name = "winapi-util" version = "0.1.11" @@ -6846,6 +11171,21 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows" version = "0.56.0" @@ -6866,7 +11206,7 @@ dependencies = [ "windows-core 0.61.2", "windows-future 0.2.1", "windows-link 0.1.3", - "windows-numerics", + "windows-numerics 0.2.0", ] [[package]] @@ -6878,6 +11218,18 @@ dependencies = [ "windows-future 0.3.2", ] +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "windows-core 0.62.2 (registry+https://github.com/rust-lang/crates.io-index)", + "windows-future 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "windows-numerics 0.3.1", +] + [[package]] name = "windows-collections" version = "0.2.0" @@ -6894,6 +11246,15 @@ dependencies = [ "windows-core 0.62.2", ] +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core 0.62.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "windows-core" version = "0.56.0" @@ -6949,7 +11310,7 @@ checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ "windows-core 0.61.2", "windows-link 0.1.3", - "windows-threading", + "windows-threading 0.1.0", ] [[package]] @@ -6959,6 +11320,17 @@ dependencies = [ "windows-core 0.62.2", ] +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core 0.62.2 (registry+https://github.com/rust-lang/crates.io-index)", + "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "windows-threading 0.2.1", +] + [[package]] name = "windows-implement" version = "0.56.0" @@ -7029,6 +11401,27 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core 0.62.2 (registry+https://github.com/rust-lang/crates.io-index)", + "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "windows-result 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "windows-strings 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "windows-result" version = "0.1.2" @@ -7097,6 +11490,15 @@ dependencies = [ "windows-targets 0.42.2", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -7106,6 +11508,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +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" @@ -7139,6 +11550,21 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -7181,12 +11607,27 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -7205,6 +11646,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -7223,6 +11670,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -7253,6 +11706,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -7271,6 +11730,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -7289,6 +11754,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -7307,6 +11778,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -7319,6 +11796,24 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" +[[package]] +name = "winnow" +version = "0.6.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e90edd2ac1aa278a5c4599b1d89cf03074b610800f866d4026dc199d7929a28" +dependencies = [ + "memchr 2.8.3", +] + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr 2.8.3", +] + [[package]] name = "winnow" version = "1.0.4" @@ -7334,10 +11829,22 @@ version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0986a8b1d586b7d3e4fe3d9ea39fb451ae22869dcea4aa109d287a374d866087" dependencies = [ - "toml", + "toml 1.1.6+spec-1.1.0", "version_check", ] +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "writeable" version = "0.6.4" @@ -7373,6 +11880,36 @@ dependencies = [ "zeroize", ] +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix", +] + +[[package]] +name = "xml5ever" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee3f1e41afb31a75aef076563b0ad3ecc24f5bd9d12a72b132222664eb76b494" +dependencies = [ + "log", + "markup5ever 0.35.0", +] + +[[package]] +name = "xml5ever" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dc9559429edf0cd3f327cc0afd9d6b36fa8cec6d93107b7fbe64f806b5f2d9" +dependencies = [ + "log", + "markup5ever 0.38.0", +] + [[package]] name = "xmlwriter" version = "0.1.0" @@ -7385,6 +11922,15 @@ version = "0.8.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aee1b19627c7c60102ab80d3a9cbe18de90bfe03bfa6c3715447681f0e8c8af6" +[[package]] +name = "xz2" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" +dependencies = [ + "lzma-sys", +] + [[package]] name = "yoke" version = "0.8.3" @@ -7437,7 +11983,7 @@ dependencies = [ "uds_windows", "uuid", "windows-sys 0.61.2", - "winnow", + "winnow 1.0.4", "zbus_macros", "zbus_names", "zvariant", @@ -7465,7 +12011,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e" dependencies = [ "serde", - "winnow", + "winnow 1.0.4", "zvariant", ] @@ -7576,6 +12122,36 @@ dependencies = [ "syn 3.0.5", ] +[[package]] +name = "zip" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" +dependencies = [ + "aes 0.8.4", + "arbitrary", + "bzip2", + "constant_time_eq 0.3.1", + "crc32fast", + "crossbeam-utils", + "deflate64", + "displaydoc", + "flate2", + "getrandom 0.3.4", + "hmac 0.12.1", + "indexmap 2.14.2", + "lzma-rs", + "memchr 2.8.3", + "pbkdf2 0.12.2", + "sha1", + "thiserror 2.0.20", + "time", + "xz2", + "zeroize", + "zopfli", + "zstd", +] + [[package]] name = "zlib-rs" version = "0.6.7" @@ -7588,6 +12164,46 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32 0.3.10", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64d80649ab6db9d9f6f9c80a40becd948eda4714a0a5ac8c4d157a32231c7882" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.1.0+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ef0a8027ec3ee71300ab3bcbcd0393f434aa72b91ca6d635a39941deae8eea0" +dependencies = [ + "cc", + "pkg-config 0.3.34", +] + [[package]] name = "zune-core" version = "0.5.3" @@ -7612,7 +12228,7 @@ dependencies = [ "endi", "enumflags2", "serde", - "winnow", + "winnow 1.0.4", "zcheapstr", "zvariant_derive", "zvariant_utils", @@ -7641,9 +12257,5 @@ dependencies = [ "quote", "serde", "syn 3.0.5", - "winnow", + "winnow 1.0.4", ] - -[[patch.unused]] -name = "makepad-ai" -version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index defe73e5..3fc45fd5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,10 +56,7 @@ makepad-photos = { git = "https://github.com/OctoSense-org/makepad.git", rev = " octosense-reference = { path = "apps/reference", optional = true } # The whole Octoscript-AppCard app hosted in a tile, in-process (Phase B of # the AppCard port): octos-app's AppShell — routing brain, cards, kernel agent. -# MVP (Rinx module): AppCard is out of this graph. Its Octos (octos-core -# deb433e9, rusqlite 0.32) links libsqlite3-sys 0.30, which conflicts with the -# Matrix SDK's 0.38 in Rinx. Octos >= dc96b927 (rusqlite 0.40) resolves this. -# octosense-appcard = { path = "apps/appcard", optional = true } +octosense-appcard = { path = "apps/appcard", optional = true } # Rinx (the Matrix client with Moments and articles) as an in-process module. # Built without its standalone window entry point. rinx = { git = "https://github.com/upstreamlabs/Rinx", rev = "65aa09c2f6022e173b5a875a007806de22974f53", default-features = false, features = ["octosense-module"], optional = true } @@ -72,21 +69,21 @@ makepad-aichat = { git = "https://github.com/OctoSense-org/makepad.git", rev = " # available modules into native mobile builds without requiring CLI flags. [target.'cfg(any(target_os = "android", target_os = "ios"))'.dependencies] octosense-reference = { path = "apps/reference" } -# octosense-appcard = { path = "apps/appcard" } # see the MVP note above +octosense-appcard = { path = "apps/appcard" } makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d9372c6142b359b98b94b57914686ee35321c32b", default-features = false } makepad-photos = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d9372c6142b359b98b94b57914686ee35321c32b", default-features = false } [features] default = [] # Also usable on desktop for testing the same embedded apps with --module. -mobile-apps = ["app-reference", "app-sheets", "app-photos"] +mobile-apps = ["app-reference", "app-sheets", "app-photos", "app-appcard"] app-reference = ["dep:octosense-reference"] # Sheets linked in as a module (hosting is still Process unless switched). app-sheets = ["dep:makepad-sheets"] # Photos (the picture wall) linked in as a module, the same way. app-photos = ["dep:makepad-photos"] # The AppCard module (one L0 weather card in a tile) linked in. -app-appcard = [] +app-appcard = ["dep:octosense-appcard"] # The aichat module linked in (the pane seats it in-process where there are # no processes, or when `aichat` is switched to module hosting). app-aichat = ["dep:makepad-aichat"] @@ -94,8 +91,7 @@ app-aichat = ["dep:makepad-aichat"] app-rinx = ["dep:rinx"] [workspace] -members = [".", "apps/reference"] -exclude = ["apps/appcard"] +members = [".", "apps/reference", "apps/appcard"] default-members = ["."] resolver = "2" @@ -155,4 +151,3 @@ makepad-code-editor = { path = "../makepad/code_editor" } # Rinx's Matrix patches (see Rinx's Cargo.toml). [patch."https://github.com/ruma/ruma"] ruma = { git = "https://github.com/project-robius/ruma.git", branch = "tsp" } - diff --git a/apps/appcard/Cargo.toml b/apps/appcard/Cargo.toml index 2afbca33..475d6060 100644 --- a/apps/appcard/Cargo.toml +++ b/apps/appcard/Cargo.toml @@ -6,12 +6,12 @@ license = "Apache-2.0" publish = false [dependencies] -makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d4502ef1e4d196d2829a07aa137740cb0581e9b4", features = ["appcard", "maps"] } -makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d4502ef1e4d196d2829a07aa137740cb0581e9b4" } +makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d9372c6142b359b98b94b57914686ee35321c32b", features = ["appcard", "maps"] } +makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d9372c6142b359b98b94b57914686ee35321c32b" } # The whole Octoscript-AppCard app as a library: routing brain, card store and # transport, L0 lowering, sessions, composer, kernel agent — mounted here as # its `AppShell` widget. No default features: the app's own entry points # (`app_main!`, the dev monitor) stay out; this module is the entry point. -octos-app = { git = "https://github.com/OctoSense-org/Octoscript-AppCard.git", rev = "a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18", default-features = false } +octos-app = { git = "https://github.com/OctoSense-org/Octoscript-AppCard.git", rev = "9a1a8a7088625061d2bcc09d4a3ea35561206133", default-features = false } # Tool arguments (`ask` takes `{"text": ...}`). serde_json = "1.0" diff --git a/src/apps.rs b/src/apps.rs index 44e0f558..7775bfaa 100644 --- a/src/apps.rs +++ b/src/apps.rs @@ -48,7 +48,8 @@ fn linked_modules() -> Vec<&'static dyn AppModule> { out.push(&makepad_sheets::SHEETS_MODULE); #[cfg(any(feature = "app-photos", target_os = "android", target_os = "ios"))] out.push(&makepad_photos::PHOTOS_MODULE); - // MVP (Rinx module): AppCard is out of the graph; see Cargo.toml. + #[cfg(any(feature = "app-appcard", target_os = "android", target_os = "ios"))] + out.push(&octosense_appcard::APPCARD_MODULE); #[cfg(feature = "app-rinx")] out.push(&rinx::module::RINX_MODULE); out @@ -221,8 +222,7 @@ mod tests { } } -// MVP (Rinx module): AppCard is out of the graph; see Cargo.toml. -#[cfg(any())] +#[cfg(all(test, feature = "mobile-apps"))] mod appcard_isolate_tests { /// The app's cards are Splash widgets, each in an ISOLATE that is minted /// without the framework's `sys`/`agent` engine; the AppCard module must From 3f68a38eb5bf08b68f09b5ce35b5389459aac0a7 Mon Sep 17 00:00:00 2001 From: ychen Date: Thu, 24 Sep 2026 10:12:18 -0700 Subject: [PATCH 06/42] Pin AppCard to e3bc1721 (octos-mate70-on-main rebased on AppCard main) Co-Authored-By: Claude Opus 5.5 (1M context) --- Cargo.lock | 64 ++++++++++++++++++++--------------------- apps/appcard/Cargo.toml | 2 +- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 17623389..4147b989 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6560,7 +6560,7 @@ dependencies = [ [[package]] name = "octos-agent" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133#9a1a8a7088625061d2bcc09d4a3ea35561206133" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60#e3bc1721bc64488dcb424f577c2248768c77ad60" dependencies = [ "async-trait", "base64 0.22.1", @@ -6585,7 +6585,7 @@ dependencies = [ "metrics", "oauth2", "octos-bus", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60)", "octos-llm", "octos-memory", "octos-plugin", @@ -6621,7 +6621,7 @@ dependencies = [ [[package]] name = "octos-app" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133#9a1a8a7088625061d2bcc09d4a3ea35561206133" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60#e3bc1721bc64488dcb424f577c2248768c77ad60" dependencies = [ "blake3", "chrono", @@ -6648,7 +6648,7 @@ dependencies = [ [[package]] name = "octos-app-render" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133#9a1a8a7088625061d2bcc09d4a3ea35561206133" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60#e3bc1721bc64488dcb424f577c2248768c77ad60" dependencies = [ "streaming-markdown-kit", ] @@ -6656,7 +6656,7 @@ dependencies = [ [[package]] name = "octos-app-store" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133#9a1a8a7088625061d2bcc09d4a3ea35561206133" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60#e3bc1721bc64488dcb424f577c2248768c77ad60" dependencies = [ "chrono", "keyring", @@ -6670,7 +6670,7 @@ dependencies = [ [[package]] name = "octos-app-transport" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133#9a1a8a7088625061d2bcc09d4a3ea35561206133" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60#e3bc1721bc64488dcb424f577c2248768c77ad60" dependencies = [ "android_logger", "chrono", @@ -6691,7 +6691,7 @@ dependencies = [ [[package]] name = "octos-bus" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133#9a1a8a7088625061d2bcc09d4a3ea35561206133" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60#e3bc1721bc64488dcb424f577c2248768c77ad60" dependencies = [ "aes 0.8.4", "async-trait", @@ -6705,7 +6705,7 @@ dependencies = [ "futures", "lru", "metrics", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60)", "reqwest 0.12.28", "serde", "serde_json", @@ -6720,7 +6720,7 @@ dependencies = [ [[package]] name = "octos-cli" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133#9a1a8a7088625061d2bcc09d4a3ea35561206133" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60#e3bc1721bc64488dcb424f577c2248768c77ad60" dependencies = [ "agent-client-protocol", "argon2", @@ -6750,7 +6750,7 @@ dependencies = [ "metrics-exporter-prometheus", "octos-agent", "octos-bus", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60)", "octos-diagnostics", "octos-fleet", "octos-fleet-worker", @@ -6814,7 +6814,7 @@ dependencies = [ [[package]] name = "octos-core" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133#9a1a8a7088625061d2bcc09d4a3ea35561206133" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60#e3bc1721bc64488dcb424f577c2248768c77ad60" dependencies = [ "chrono", "eyre", @@ -6828,10 +6828,10 @@ dependencies = [ [[package]] name = "octos-diagnostics" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133#9a1a8a7088625061d2bcc09d4a3ea35561206133" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60#e3bc1721bc64488dcb424f577c2248768c77ad60" dependencies = [ "eyre", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60)", "reqwest 0.12.28", "serde", "serde_json", @@ -6840,10 +6840,10 @@ dependencies = [ [[package]] name = "octos-fleet" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133#9a1a8a7088625061d2bcc09d4a3ea35561206133" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60#e3bc1721bc64488dcb424f577c2248768c77ad60" dependencies = [ "eyre", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60)", "redb", "rusqlite", "serde", @@ -6856,12 +6856,12 @@ dependencies = [ [[package]] name = "octos-fleet-worker" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133#9a1a8a7088625061d2bcc09d4a3ea35561206133" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60#e3bc1721bc64488dcb424f577c2248768c77ad60" dependencies = [ "async-trait", "eyre", "octos-agent", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60)", "octos-fleet", "octos-llm", "octos-memory", @@ -6873,7 +6873,7 @@ dependencies = [ [[package]] name = "octos-llm" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133#9a1a8a7088625061d2bcc09d4a3ea35561206133" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60#e3bc1721bc64488dcb424f577c2248768c77ad60" dependencies = [ "async-trait", "base64 0.22.1", @@ -6883,7 +6883,7 @@ dependencies = [ "jsonwebtoken", "libc", "metrics", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60)", "redb", "reqwest 0.12.28", "secrecy", @@ -6897,13 +6897,13 @@ dependencies = [ [[package]] name = "octos-memory" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133#9a1a8a7088625061d2bcc09d4a3ea35561206133" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60#e3bc1721bc64488dcb424f577c2248768c77ad60" dependencies = [ "bincode", "chrono", "eyre", "hnsw_rs", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60)", "redb", "regex", "serde", @@ -6916,7 +6916,7 @@ dependencies = [ [[package]] name = "octos-pipeline" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133#9a1a8a7088625061d2bcc09d4a3ea35561206133" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60#e3bc1721bc64488dcb424f577c2248768c77ad60" dependencies = [ "async-trait", "chrono", @@ -6924,7 +6924,7 @@ dependencies = [ "futures", "glob", "octos-agent", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60)", "octos-llm", "octos-memory", "octos-plugin", @@ -6938,7 +6938,7 @@ dependencies = [ [[package]] name = "octos-plugin" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133#9a1a8a7088625061d2bcc09d4a3ea35561206133" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60#e3bc1721bc64488dcb424f577c2248768c77ad60" dependencies = [ "eyre", "metrics", @@ -6952,7 +6952,7 @@ dependencies = [ [[package]] name = "octos-sandbox" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133#9a1a8a7088625061d2bcc09d4a3ea35561206133" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60#e3bc1721bc64488dcb424f577c2248768c77ad60" dependencies = [ "clap", "eyre", @@ -6967,7 +6967,7 @@ dependencies = [ [[package]] name = "octos-services" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133#9a1a8a7088625061d2bcc09d4a3ea35561206133" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60#e3bc1721bc64488dcb424f577c2248768c77ad60" dependencies = [ "chrono", "dirs", @@ -6975,7 +6975,7 @@ dependencies = [ "flate2", "futures", "octos-bus", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60)", "octos-llm", "reqwest 0.12.28", "serde", @@ -6988,14 +6988,14 @@ dependencies = [ [[package]] name = "octos-store" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133#9a1a8a7088625061d2bcc09d4a3ea35561206133" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60#e3bc1721bc64488dcb424f577c2248768c77ad60" dependencies = [ "base64 0.22.1", "chrono", "constant_time_eq 0.3.1", "eyre", "getrandom 0.2.17", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60)", "redb", "serde", "serde_json", @@ -7008,7 +7008,7 @@ dependencies = [ [[package]] name = "octos-swarm" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133#9a1a8a7088625061d2bcc09d4a3ea35561206133" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60#e3bc1721bc64488dcb424f577c2248768c77ad60" dependencies = [ "async-trait", "chrono", @@ -7027,12 +7027,12 @@ dependencies = [ [[package]] name = "octos-workflows" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133#9a1a8a7088625061d2bcc09d4a3ea35561206133" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60#e3bc1721bc64488dcb424f577c2248768c77ad60" dependencies = [ "chrono", "eyre", "octos-agent", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9a1a8a7088625061d2bcc09d4a3ea35561206133)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60)", "octos-pipeline", "serde", "serde_json", diff --git a/apps/appcard/Cargo.toml b/apps/appcard/Cargo.toml index 475d6060..8033eea0 100644 --- a/apps/appcard/Cargo.toml +++ b/apps/appcard/Cargo.toml @@ -12,6 +12,6 @@ makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev # transport, L0 lowering, sessions, composer, kernel agent — mounted here as # its `AppShell` widget. No default features: the app's own entry points # (`app_main!`, the dev monitor) stay out; this module is the entry point. -octos-app = { git = "https://github.com/OctoSense-org/Octoscript-AppCard.git", rev = "9a1a8a7088625061d2bcc09d4a3ea35561206133", default-features = false } +octos-app = { git = "https://github.com/OctoSense-org/Octoscript-AppCard.git", rev = "e3bc1721bc64488dcb424f577c2248768c77ad60", default-features = false } # Tool arguments (`ask` takes `{"text": ...}`). serde_json = "1.0" From e9a178ac53f018b47a02251c93db1a28d72e4fb2 Mon Sep 17 00:00:00 2001 From: ychen Date: Thu, 24 Sep 2026 10:15:30 -0700 Subject: [PATCH 07/42] Pin AppCard to its main (64998ed4, #108 merged) Co-Authored-By: Claude Opus 5.5 (1M context) --- Cargo.lock | 64 ++++++++++++++++++++--------------------- apps/appcard/Cargo.toml | 2 +- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4147b989..6191cbb1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6560,7 +6560,7 @@ dependencies = [ [[package]] name = "octos-agent" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60#e3bc1721bc64488dcb424f577c2248768c77ad60" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5#64998ed4462c0d56d04e8122dc756afe1bd26de5" dependencies = [ "async-trait", "base64 0.22.1", @@ -6585,7 +6585,7 @@ dependencies = [ "metrics", "oauth2", "octos-bus", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5)", "octos-llm", "octos-memory", "octos-plugin", @@ -6621,7 +6621,7 @@ dependencies = [ [[package]] name = "octos-app" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60#e3bc1721bc64488dcb424f577c2248768c77ad60" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5#64998ed4462c0d56d04e8122dc756afe1bd26de5" dependencies = [ "blake3", "chrono", @@ -6648,7 +6648,7 @@ dependencies = [ [[package]] name = "octos-app-render" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60#e3bc1721bc64488dcb424f577c2248768c77ad60" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5#64998ed4462c0d56d04e8122dc756afe1bd26de5" dependencies = [ "streaming-markdown-kit", ] @@ -6656,7 +6656,7 @@ dependencies = [ [[package]] name = "octos-app-store" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60#e3bc1721bc64488dcb424f577c2248768c77ad60" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5#64998ed4462c0d56d04e8122dc756afe1bd26de5" dependencies = [ "chrono", "keyring", @@ -6670,7 +6670,7 @@ dependencies = [ [[package]] name = "octos-app-transport" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60#e3bc1721bc64488dcb424f577c2248768c77ad60" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5#64998ed4462c0d56d04e8122dc756afe1bd26de5" dependencies = [ "android_logger", "chrono", @@ -6691,7 +6691,7 @@ dependencies = [ [[package]] name = "octos-bus" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60#e3bc1721bc64488dcb424f577c2248768c77ad60" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5#64998ed4462c0d56d04e8122dc756afe1bd26de5" dependencies = [ "aes 0.8.4", "async-trait", @@ -6705,7 +6705,7 @@ dependencies = [ "futures", "lru", "metrics", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5)", "reqwest 0.12.28", "serde", "serde_json", @@ -6720,7 +6720,7 @@ dependencies = [ [[package]] name = "octos-cli" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60#e3bc1721bc64488dcb424f577c2248768c77ad60" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5#64998ed4462c0d56d04e8122dc756afe1bd26de5" dependencies = [ "agent-client-protocol", "argon2", @@ -6750,7 +6750,7 @@ dependencies = [ "metrics-exporter-prometheus", "octos-agent", "octos-bus", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5)", "octos-diagnostics", "octos-fleet", "octos-fleet-worker", @@ -6814,7 +6814,7 @@ dependencies = [ [[package]] name = "octos-core" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60#e3bc1721bc64488dcb424f577c2248768c77ad60" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5#64998ed4462c0d56d04e8122dc756afe1bd26de5" dependencies = [ "chrono", "eyre", @@ -6828,10 +6828,10 @@ dependencies = [ [[package]] name = "octos-diagnostics" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60#e3bc1721bc64488dcb424f577c2248768c77ad60" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5#64998ed4462c0d56d04e8122dc756afe1bd26de5" dependencies = [ "eyre", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5)", "reqwest 0.12.28", "serde", "serde_json", @@ -6840,10 +6840,10 @@ dependencies = [ [[package]] name = "octos-fleet" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60#e3bc1721bc64488dcb424f577c2248768c77ad60" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5#64998ed4462c0d56d04e8122dc756afe1bd26de5" dependencies = [ "eyre", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5)", "redb", "rusqlite", "serde", @@ -6856,12 +6856,12 @@ dependencies = [ [[package]] name = "octos-fleet-worker" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60#e3bc1721bc64488dcb424f577c2248768c77ad60" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5#64998ed4462c0d56d04e8122dc756afe1bd26de5" dependencies = [ "async-trait", "eyre", "octos-agent", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5)", "octos-fleet", "octos-llm", "octos-memory", @@ -6873,7 +6873,7 @@ dependencies = [ [[package]] name = "octos-llm" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60#e3bc1721bc64488dcb424f577c2248768c77ad60" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5#64998ed4462c0d56d04e8122dc756afe1bd26de5" dependencies = [ "async-trait", "base64 0.22.1", @@ -6883,7 +6883,7 @@ dependencies = [ "jsonwebtoken", "libc", "metrics", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5)", "redb", "reqwest 0.12.28", "secrecy", @@ -6897,13 +6897,13 @@ dependencies = [ [[package]] name = "octos-memory" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60#e3bc1721bc64488dcb424f577c2248768c77ad60" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5#64998ed4462c0d56d04e8122dc756afe1bd26de5" dependencies = [ "bincode", "chrono", "eyre", "hnsw_rs", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5)", "redb", "regex", "serde", @@ -6916,7 +6916,7 @@ dependencies = [ [[package]] name = "octos-pipeline" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60#e3bc1721bc64488dcb424f577c2248768c77ad60" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5#64998ed4462c0d56d04e8122dc756afe1bd26de5" dependencies = [ "async-trait", "chrono", @@ -6924,7 +6924,7 @@ dependencies = [ "futures", "glob", "octos-agent", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5)", "octos-llm", "octos-memory", "octos-plugin", @@ -6938,7 +6938,7 @@ dependencies = [ [[package]] name = "octos-plugin" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60#e3bc1721bc64488dcb424f577c2248768c77ad60" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5#64998ed4462c0d56d04e8122dc756afe1bd26de5" dependencies = [ "eyre", "metrics", @@ -6952,7 +6952,7 @@ dependencies = [ [[package]] name = "octos-sandbox" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60#e3bc1721bc64488dcb424f577c2248768c77ad60" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5#64998ed4462c0d56d04e8122dc756afe1bd26de5" dependencies = [ "clap", "eyre", @@ -6967,7 +6967,7 @@ dependencies = [ [[package]] name = "octos-services" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60#e3bc1721bc64488dcb424f577c2248768c77ad60" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5#64998ed4462c0d56d04e8122dc756afe1bd26de5" dependencies = [ "chrono", "dirs", @@ -6975,7 +6975,7 @@ dependencies = [ "flate2", "futures", "octos-bus", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5)", "octos-llm", "reqwest 0.12.28", "serde", @@ -6988,14 +6988,14 @@ dependencies = [ [[package]] name = "octos-store" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60#e3bc1721bc64488dcb424f577c2248768c77ad60" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5#64998ed4462c0d56d04e8122dc756afe1bd26de5" dependencies = [ "base64 0.22.1", "chrono", "constant_time_eq 0.3.1", "eyre", "getrandom 0.2.17", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5)", "redb", "serde", "serde_json", @@ -7008,7 +7008,7 @@ dependencies = [ [[package]] name = "octos-swarm" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60#e3bc1721bc64488dcb424f577c2248768c77ad60" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5#64998ed4462c0d56d04e8122dc756afe1bd26de5" dependencies = [ "async-trait", "chrono", @@ -7027,12 +7027,12 @@ dependencies = [ [[package]] name = "octos-workflows" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60#e3bc1721bc64488dcb424f577c2248768c77ad60" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5#64998ed4462c0d56d04e8122dc756afe1bd26de5" dependencies = [ "chrono", "eyre", "octos-agent", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=e3bc1721bc64488dcb424f577c2248768c77ad60)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5)", "octos-pipeline", "serde", "serde_json", diff --git a/apps/appcard/Cargo.toml b/apps/appcard/Cargo.toml index 8033eea0..a0ab9624 100644 --- a/apps/appcard/Cargo.toml +++ b/apps/appcard/Cargo.toml @@ -12,6 +12,6 @@ makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev # transport, L0 lowering, sessions, composer, kernel agent — mounted here as # its `AppShell` widget. No default features: the app's own entry points # (`app_main!`, the dev monitor) stay out; this module is the entry point. -octos-app = { git = "https://github.com/OctoSense-org/Octoscript-AppCard.git", rev = "e3bc1721bc64488dcb424f577c2248768c77ad60", default-features = false } +octos-app = { git = "https://github.com/OctoSense-org/Octoscript-AppCard.git", rev = "64998ed4462c0d56d04e8122dc756afe1bd26de5", default-features = false } # Tool arguments (`ask` takes `{"text": ...}`). serde_json = "1.0" From fbf92848d592126484b3289f6ed3863c67ec794c Mon Sep 17 00:00:00 2001 From: ychen Date: Thu, 24 Sep 2026 10:17:58 -0700 Subject: [PATCH 08/42] Lock the Octoscript-Makepad release with makepad d9372c61 bd124a7a (branch release/rinx-module-d9372c61) is b1596d9 with makepad d4502ef1 -> d9372c61: the module windows, modal bounds and Rinx markdown/math support this branch builds on. Co-Authored-By: Claude Opus 5.5 (1M context) --- native-runtime.lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native-runtime.lock.json b/native-runtime.lock.json index 095afdca..8326c55d 100644 --- a/native-runtime.lock.json +++ b/native-runtime.lock.json @@ -1,5 +1,5 @@ { "schema_version": 1, "url": "https://github.com/OctoSense-org/Octoscript-Makepad.git", - "revision": "b1596d9c2cee6e80cca9d27b1b0c2d57b9d28dd8" + "revision": "bd124a7ae21374bdaa876e352528aa980ac028b3" } From d9f840a82583846e9d348574d2676dc8c20a7c98 Mon Sep 17 00:00:00 2001 From: ychen Date: Thu, 24 Sep 2026 11:53:16 -0700 Subject: [PATCH 09/42] Lock Octoscript-Makepad main (082314ed): makepad main 2bfbb99c, Rinx 7f9d8630 makepad#25 put the module windows, modal bounds and Markdown/math libraries on makepad main, and Octoscript-Makepad#36 released it, so this branch no longer needs the off-main release/rinx-module-d9372c61. Co-Authored-By: Claude Opus 5.5 (1M context) --- Cargo.lock | 6 +++--- Cargo.toml | 28 ++++++++++++++-------------- apps/appcard/Cargo.toml | 4 ++-- apps/reference/Cargo.toml | 4 ++-- native-runtime.lock.json | 2 +- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6191cbb1..b0ee6e93 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -356,7 +356,7 @@ dependencies = [ [[package]] name = "article-core" version = "0.1.0" -source = "git+https://github.com/upstreamlabs/Rinx?rev=65aa09c2f6022e173b5a875a007806de22974f53#65aa09c2f6022e173b5a875a007806de22974f53" +source = "git+https://github.com/upstreamlabs/Rinx?rev=7f9d8630b0105ab1724acdce4b455a2894966a64#7f9d8630b0105ab1724acdce4b455a2894966a64" dependencies = [ "blake3", "image", @@ -373,7 +373,7 @@ dependencies = [ [[package]] name = "article-makepad" version = "0.1.0" -source = "git+https://github.com/upstreamlabs/Rinx?rev=65aa09c2f6022e173b5a875a007806de22974f53#65aa09c2f6022e173b5a875a007806de22974f53" +source = "git+https://github.com/upstreamlabs/Rinx?rev=7f9d8630b0105ab1724acdce4b455a2894966a64#7f9d8630b0105ab1724acdce4b455a2894966a64" dependencies = [ "article-core", "libc", @@ -8197,7 +8197,7 @@ dependencies = [ [[package]] name = "rinx" version = "1.0.0-beta.1" -source = "git+https://github.com/upstreamlabs/Rinx?rev=65aa09c2f6022e173b5a875a007806de22974f53#65aa09c2f6022e173b5a875a007806de22974f53" +source = "git+https://github.com/upstreamlabs/Rinx?rev=7f9d8630b0105ab1724acdce4b455a2894966a64#7f9d8630b0105ab1724acdce4b455a2894966a64" dependencies = [ "anyhow", "arboard", diff --git a/Cargo.toml b/Cargo.toml index 3fc45fd5..713207ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,38 +40,38 @@ path = "src/main.rs" [dependencies] # `appcard,maps`: the Octoscript-AppCard widget set (kit, text roles, weather, # finance, matplot) and the map navigation layer the appcard module renders. -makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d9372c6142b359b98b94b57914686ee35321c32b", features = ["appcard", "maps"] } -makepad-studio-protocol = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d9372c6142b359b98b94b57914686ee35321c32b" } -makepad-network = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d9372c6142b359b98b94b57914686ee35321c32b" } -makepad-wm-api = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d9372c6142b359b98b94b57914686ee35321c32b" } +makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "2bfbb99c8e56ff0c15d7d31061aa49e239e220e2", features = ["appcard", "maps"] } +makepad-studio-protocol = { git = "https://github.com/OctoSense-org/makepad.git", rev = "2bfbb99c8e56ff0c15d7d31061aa49e239e220e2" } +makepad-network = { git = "https://github.com/OctoSense-org/makepad.git", rev = "2bfbb99c8e56ff0c15d7d31061aa49e239e220e2" } +makepad-wm-api = { git = "https://github.com/OctoSense-org/makepad.git", rev = "2bfbb99c8e56ff0c15d7d31061aa49e239e220e2" } # The AI services wire only: the WM relays frames between the aichat child and the apps. -makepad-ai-services = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d9372c6142b359b98b94b57914686ee35321c32b" } -makepad-strict-json = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d9372c6142b359b98b94b57914686ee35321c32b" } +makepad-ai-services = { git = "https://github.com/OctoSense-org/makepad.git", rev = "2bfbb99c8e56ff0c15d7d31061aa49e239e220e2" } +makepad-strict-json = { git = "https://github.com/OctoSense-org/makepad.git", rev = "2bfbb99c8e56ff0c15d7d31061aa49e239e220e2" } # In-process hosting (aicontrol §3): the module contract, the palette applied # into each instance's isolate, and the modules this build links. -makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d9372c6142b359b98b94b57914686ee35321c32b" } -makepad-wm-theme = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d9372c6142b359b98b94b57914686ee35321c32b" } -makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d9372c6142b359b98b94b57914686ee35321c32b", default-features = false, optional = true } -makepad-photos = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d9372c6142b359b98b94b57914686ee35321c32b", default-features = false, optional = true } +makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "2bfbb99c8e56ff0c15d7d31061aa49e239e220e2" } +makepad-wm-theme = { git = "https://github.com/OctoSense-org/makepad.git", rev = "2bfbb99c8e56ff0c15d7d31061aa49e239e220e2" } +makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "2bfbb99c8e56ff0c15d7d31061aa49e239e220e2", default-features = false, optional = true } +makepad-photos = { git = "https://github.com/OctoSense-org/makepad.git", rev = "2bfbb99c8e56ff0c15d7d31061aa49e239e220e2", default-features = false, optional = true } octosense-reference = { path = "apps/reference", optional = true } # The whole Octoscript-AppCard app hosted in a tile, in-process (Phase B of # the AppCard port): octos-app's AppShell — routing brain, cards, kernel agent. octosense-appcard = { path = "apps/appcard", optional = true } # Rinx (the Matrix client with Moments and articles) as an in-process module. # Built without its standalone window entry point. -rinx = { git = "https://github.com/upstreamlabs/Rinx", rev = "65aa09c2f6022e173b5a875a007806de22974f53", default-features = false, features = ["octosense-module"], optional = true } +rinx = { git = "https://github.com/upstreamlabs/Rinx", rev = "7f9d8630b0105ab1724acdce4b455a2894966a64", default-features = false, features = ["octosense-module"], optional = true } # The assistant as a module, seated in the pane in-process: the web build's # only chat, a desktop option. Without the engine feature: no model runtime, # NoModel and the tool console (the child process keeps the real models). -makepad-aichat = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d9372c6142b359b98b94b57914686ee35321c32b", default-features = false, optional = true } +makepad-aichat = { git = "https://github.com/OctoSense-org/makepad.git", rev = "2bfbb99c8e56ff0c15d7d31061aa49e239e220e2", default-features = false, optional = true } # A phone cannot spawn the desktop catalog's Cargo binaries. Link the # available modules into native mobile builds without requiring CLI flags. [target.'cfg(any(target_os = "android", target_os = "ios"))'.dependencies] octosense-reference = { path = "apps/reference" } octosense-appcard = { path = "apps/appcard" } -makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d9372c6142b359b98b94b57914686ee35321c32b", default-features = false } -makepad-photos = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d9372c6142b359b98b94b57914686ee35321c32b", default-features = false } +makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "2bfbb99c8e56ff0c15d7d31061aa49e239e220e2", default-features = false } +makepad-photos = { git = "https://github.com/OctoSense-org/makepad.git", rev = "2bfbb99c8e56ff0c15d7d31061aa49e239e220e2", default-features = false } [features] default = [] diff --git a/apps/appcard/Cargo.toml b/apps/appcard/Cargo.toml index a0ab9624..771d35af 100644 --- a/apps/appcard/Cargo.toml +++ b/apps/appcard/Cargo.toml @@ -6,8 +6,8 @@ license = "Apache-2.0" publish = false [dependencies] -makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d9372c6142b359b98b94b57914686ee35321c32b", features = ["appcard", "maps"] } -makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d9372c6142b359b98b94b57914686ee35321c32b" } +makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "2bfbb99c8e56ff0c15d7d31061aa49e239e220e2", features = ["appcard", "maps"] } +makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "2bfbb99c8e56ff0c15d7d31061aa49e239e220e2" } # The whole Octoscript-AppCard app as a library: routing brain, card store and # transport, L0 lowering, sessions, composer, kernel agent — mounted here as # its `AppShell` widget. No default features: the app's own entry points diff --git a/apps/reference/Cargo.toml b/apps/reference/Cargo.toml index 17afed3a..8bb80331 100644 --- a/apps/reference/Cargo.toml +++ b/apps/reference/Cargo.toml @@ -6,5 +6,5 @@ license = "Apache-2.0" publish = false [dependencies] -makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d9372c6142b359b98b94b57914686ee35321c32b" } -makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d9372c6142b359b98b94b57914686ee35321c32b" } +makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "2bfbb99c8e56ff0c15d7d31061aa49e239e220e2" } +makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "2bfbb99c8e56ff0c15d7d31061aa49e239e220e2" } diff --git a/native-runtime.lock.json b/native-runtime.lock.json index 8326c55d..70132c6e 100644 --- a/native-runtime.lock.json +++ b/native-runtime.lock.json @@ -1,5 +1,5 @@ { "schema_version": 1, "url": "https://github.com/OctoSense-org/Octoscript-Makepad.git", - "revision": "bd124a7ae21374bdaa876e352528aa980ac028b3" + "revision": "082314ed712f7fa318199d92b206692164f2b016" } From c4f24a2cca9ca683fbc5e80e8ae8fbce43bcbcb0 Mon Sep 17 00:00:00 2001 From: ychen Date: Thu, 24 Sep 2026 12:09:25 -0700 Subject: [PATCH 10/42] Follow main everywhere: Octoscript-Makepad 36ad1f22, makepad 57b31c99, Rinx 127cd2ec, AppCard 1cf78120 Co-Authored-By: Claude Opus 5.5 (1M context) --- Cargo.lock | 70 +++++++++++++++++++-------------------- Cargo.toml | 28 ++++++++-------- apps/appcard/Cargo.toml | 6 ++-- apps/reference/Cargo.toml | 4 +-- native-runtime.lock.json | 2 +- 5 files changed, 55 insertions(+), 55 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b0ee6e93..b9732846 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -356,7 +356,7 @@ dependencies = [ [[package]] name = "article-core" version = "0.1.0" -source = "git+https://github.com/upstreamlabs/Rinx?rev=7f9d8630b0105ab1724acdce4b455a2894966a64#7f9d8630b0105ab1724acdce4b455a2894966a64" +source = "git+https://github.com/upstreamlabs/Rinx?rev=127cd2ec997f3a3f7056d0bf818ef2bc57c06f57#127cd2ec997f3a3f7056d0bf818ef2bc57c06f57" dependencies = [ "blake3", "image", @@ -373,7 +373,7 @@ dependencies = [ [[package]] name = "article-makepad" version = "0.1.0" -source = "git+https://github.com/upstreamlabs/Rinx?rev=7f9d8630b0105ab1724acdce4b455a2894966a64#7f9d8630b0105ab1724acdce4b455a2894966a64" +source = "git+https://github.com/upstreamlabs/Rinx?rev=127cd2ec997f3a3f7056d0bf818ef2bc57c06f57#127cd2ec997f3a3f7056d0bf818ef2bc57c06f57" dependencies = [ "article-core", "libc", @@ -6560,7 +6560,7 @@ dependencies = [ [[package]] name = "octos-agent" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5#64998ed4462c0d56d04e8122dc756afe1bd26de5" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39#1cf78120033b1a1a3f597b627032e366e73a7e39" dependencies = [ "async-trait", "base64 0.22.1", @@ -6585,7 +6585,7 @@ dependencies = [ "metrics", "oauth2", "octos-bus", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39)", "octos-llm", "octos-memory", "octos-plugin", @@ -6621,7 +6621,7 @@ dependencies = [ [[package]] name = "octos-app" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5#64998ed4462c0d56d04e8122dc756afe1bd26de5" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39#1cf78120033b1a1a3f597b627032e366e73a7e39" dependencies = [ "blake3", "chrono", @@ -6648,7 +6648,7 @@ dependencies = [ [[package]] name = "octos-app-render" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5#64998ed4462c0d56d04e8122dc756afe1bd26de5" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39#1cf78120033b1a1a3f597b627032e366e73a7e39" dependencies = [ "streaming-markdown-kit", ] @@ -6656,7 +6656,7 @@ dependencies = [ [[package]] name = "octos-app-store" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5#64998ed4462c0d56d04e8122dc756afe1bd26de5" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39#1cf78120033b1a1a3f597b627032e366e73a7e39" dependencies = [ "chrono", "keyring", @@ -6670,7 +6670,7 @@ dependencies = [ [[package]] name = "octos-app-transport" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5#64998ed4462c0d56d04e8122dc756afe1bd26de5" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39#1cf78120033b1a1a3f597b627032e366e73a7e39" dependencies = [ "android_logger", "chrono", @@ -6691,7 +6691,7 @@ dependencies = [ [[package]] name = "octos-bus" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5#64998ed4462c0d56d04e8122dc756afe1bd26de5" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39#1cf78120033b1a1a3f597b627032e366e73a7e39" dependencies = [ "aes 0.8.4", "async-trait", @@ -6705,7 +6705,7 @@ dependencies = [ "futures", "lru", "metrics", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39)", "reqwest 0.12.28", "serde", "serde_json", @@ -6720,7 +6720,7 @@ dependencies = [ [[package]] name = "octos-cli" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5#64998ed4462c0d56d04e8122dc756afe1bd26de5" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39#1cf78120033b1a1a3f597b627032e366e73a7e39" dependencies = [ "agent-client-protocol", "argon2", @@ -6750,7 +6750,7 @@ dependencies = [ "metrics-exporter-prometheus", "octos-agent", "octos-bus", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39)", "octos-diagnostics", "octos-fleet", "octos-fleet-worker", @@ -6814,7 +6814,7 @@ dependencies = [ [[package]] name = "octos-core" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5#64998ed4462c0d56d04e8122dc756afe1bd26de5" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39#1cf78120033b1a1a3f597b627032e366e73a7e39" dependencies = [ "chrono", "eyre", @@ -6828,10 +6828,10 @@ dependencies = [ [[package]] name = "octos-diagnostics" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5#64998ed4462c0d56d04e8122dc756afe1bd26de5" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39#1cf78120033b1a1a3f597b627032e366e73a7e39" dependencies = [ "eyre", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39)", "reqwest 0.12.28", "serde", "serde_json", @@ -6840,10 +6840,10 @@ dependencies = [ [[package]] name = "octos-fleet" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5#64998ed4462c0d56d04e8122dc756afe1bd26de5" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39#1cf78120033b1a1a3f597b627032e366e73a7e39" dependencies = [ "eyre", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39)", "redb", "rusqlite", "serde", @@ -6856,12 +6856,12 @@ dependencies = [ [[package]] name = "octos-fleet-worker" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5#64998ed4462c0d56d04e8122dc756afe1bd26de5" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39#1cf78120033b1a1a3f597b627032e366e73a7e39" dependencies = [ "async-trait", "eyre", "octos-agent", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39)", "octos-fleet", "octos-llm", "octos-memory", @@ -6873,7 +6873,7 @@ dependencies = [ [[package]] name = "octos-llm" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5#64998ed4462c0d56d04e8122dc756afe1bd26de5" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39#1cf78120033b1a1a3f597b627032e366e73a7e39" dependencies = [ "async-trait", "base64 0.22.1", @@ -6883,7 +6883,7 @@ dependencies = [ "jsonwebtoken", "libc", "metrics", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39)", "redb", "reqwest 0.12.28", "secrecy", @@ -6897,13 +6897,13 @@ dependencies = [ [[package]] name = "octos-memory" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5#64998ed4462c0d56d04e8122dc756afe1bd26de5" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39#1cf78120033b1a1a3f597b627032e366e73a7e39" dependencies = [ "bincode", "chrono", "eyre", "hnsw_rs", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39)", "redb", "regex", "serde", @@ -6916,7 +6916,7 @@ dependencies = [ [[package]] name = "octos-pipeline" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5#64998ed4462c0d56d04e8122dc756afe1bd26de5" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39#1cf78120033b1a1a3f597b627032e366e73a7e39" dependencies = [ "async-trait", "chrono", @@ -6924,7 +6924,7 @@ dependencies = [ "futures", "glob", "octos-agent", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39)", "octos-llm", "octos-memory", "octos-plugin", @@ -6938,7 +6938,7 @@ dependencies = [ [[package]] name = "octos-plugin" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5#64998ed4462c0d56d04e8122dc756afe1bd26de5" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39#1cf78120033b1a1a3f597b627032e366e73a7e39" dependencies = [ "eyre", "metrics", @@ -6952,7 +6952,7 @@ dependencies = [ [[package]] name = "octos-sandbox" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5#64998ed4462c0d56d04e8122dc756afe1bd26de5" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39#1cf78120033b1a1a3f597b627032e366e73a7e39" dependencies = [ "clap", "eyre", @@ -6967,7 +6967,7 @@ dependencies = [ [[package]] name = "octos-services" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5#64998ed4462c0d56d04e8122dc756afe1bd26de5" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39#1cf78120033b1a1a3f597b627032e366e73a7e39" dependencies = [ "chrono", "dirs", @@ -6975,7 +6975,7 @@ dependencies = [ "flate2", "futures", "octos-bus", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39)", "octos-llm", "reqwest 0.12.28", "serde", @@ -6988,14 +6988,14 @@ dependencies = [ [[package]] name = "octos-store" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5#64998ed4462c0d56d04e8122dc756afe1bd26de5" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39#1cf78120033b1a1a3f597b627032e366e73a7e39" dependencies = [ "base64 0.22.1", "chrono", "constant_time_eq 0.3.1", "eyre", "getrandom 0.2.17", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39)", "redb", "serde", "serde_json", @@ -7008,7 +7008,7 @@ dependencies = [ [[package]] name = "octos-swarm" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5#64998ed4462c0d56d04e8122dc756afe1bd26de5" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39#1cf78120033b1a1a3f597b627032e366e73a7e39" dependencies = [ "async-trait", "chrono", @@ -7027,12 +7027,12 @@ dependencies = [ [[package]] name = "octos-workflows" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5#64998ed4462c0d56d04e8122dc756afe1bd26de5" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39#1cf78120033b1a1a3f597b627032e366e73a7e39" dependencies = [ "chrono", "eyre", "octos-agent", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=64998ed4462c0d56d04e8122dc756afe1bd26de5)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39)", "octos-pipeline", "serde", "serde_json", @@ -8197,7 +8197,7 @@ dependencies = [ [[package]] name = "rinx" version = "1.0.0-beta.1" -source = "git+https://github.com/upstreamlabs/Rinx?rev=7f9d8630b0105ab1724acdce4b455a2894966a64#7f9d8630b0105ab1724acdce4b455a2894966a64" +source = "git+https://github.com/upstreamlabs/Rinx?rev=127cd2ec997f3a3f7056d0bf818ef2bc57c06f57#127cd2ec997f3a3f7056d0bf818ef2bc57c06f57" dependencies = [ "anyhow", "arboard", diff --git a/Cargo.toml b/Cargo.toml index 713207ae..905ef36e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,38 +40,38 @@ path = "src/main.rs" [dependencies] # `appcard,maps`: the Octoscript-AppCard widget set (kit, text roles, weather, # finance, matplot) and the map navigation layer the appcard module renders. -makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "2bfbb99c8e56ff0c15d7d31061aa49e239e220e2", features = ["appcard", "maps"] } -makepad-studio-protocol = { git = "https://github.com/OctoSense-org/makepad.git", rev = "2bfbb99c8e56ff0c15d7d31061aa49e239e220e2" } -makepad-network = { git = "https://github.com/OctoSense-org/makepad.git", rev = "2bfbb99c8e56ff0c15d7d31061aa49e239e220e2" } -makepad-wm-api = { git = "https://github.com/OctoSense-org/makepad.git", rev = "2bfbb99c8e56ff0c15d7d31061aa49e239e220e2" } +makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "57b31c9978635e51e633558131d1888d9ef4f45a", features = ["appcard", "maps"] } +makepad-studio-protocol = { git = "https://github.com/OctoSense-org/makepad.git", rev = "57b31c9978635e51e633558131d1888d9ef4f45a" } +makepad-network = { git = "https://github.com/OctoSense-org/makepad.git", rev = "57b31c9978635e51e633558131d1888d9ef4f45a" } +makepad-wm-api = { git = "https://github.com/OctoSense-org/makepad.git", rev = "57b31c9978635e51e633558131d1888d9ef4f45a" } # The AI services wire only: the WM relays frames between the aichat child and the apps. -makepad-ai-services = { git = "https://github.com/OctoSense-org/makepad.git", rev = "2bfbb99c8e56ff0c15d7d31061aa49e239e220e2" } -makepad-strict-json = { git = "https://github.com/OctoSense-org/makepad.git", rev = "2bfbb99c8e56ff0c15d7d31061aa49e239e220e2" } +makepad-ai-services = { git = "https://github.com/OctoSense-org/makepad.git", rev = "57b31c9978635e51e633558131d1888d9ef4f45a" } +makepad-strict-json = { git = "https://github.com/OctoSense-org/makepad.git", rev = "57b31c9978635e51e633558131d1888d9ef4f45a" } # In-process hosting (aicontrol §3): the module contract, the palette applied # into each instance's isolate, and the modules this build links. -makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "2bfbb99c8e56ff0c15d7d31061aa49e239e220e2" } -makepad-wm-theme = { git = "https://github.com/OctoSense-org/makepad.git", rev = "2bfbb99c8e56ff0c15d7d31061aa49e239e220e2" } -makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "2bfbb99c8e56ff0c15d7d31061aa49e239e220e2", default-features = false, optional = true } -makepad-photos = { git = "https://github.com/OctoSense-org/makepad.git", rev = "2bfbb99c8e56ff0c15d7d31061aa49e239e220e2", default-features = false, optional = true } +makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "57b31c9978635e51e633558131d1888d9ef4f45a" } +makepad-wm-theme = { git = "https://github.com/OctoSense-org/makepad.git", rev = "57b31c9978635e51e633558131d1888d9ef4f45a" } +makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "57b31c9978635e51e633558131d1888d9ef4f45a", default-features = false, optional = true } +makepad-photos = { git = "https://github.com/OctoSense-org/makepad.git", rev = "57b31c9978635e51e633558131d1888d9ef4f45a", default-features = false, optional = true } octosense-reference = { path = "apps/reference", optional = true } # The whole Octoscript-AppCard app hosted in a tile, in-process (Phase B of # the AppCard port): octos-app's AppShell — routing brain, cards, kernel agent. octosense-appcard = { path = "apps/appcard", optional = true } # Rinx (the Matrix client with Moments and articles) as an in-process module. # Built without its standalone window entry point. -rinx = { git = "https://github.com/upstreamlabs/Rinx", rev = "7f9d8630b0105ab1724acdce4b455a2894966a64", default-features = false, features = ["octosense-module"], optional = true } +rinx = { git = "https://github.com/upstreamlabs/Rinx", rev = "127cd2ec997f3a3f7056d0bf818ef2bc57c06f57", default-features = false, features = ["octosense-module"], optional = true } # The assistant as a module, seated in the pane in-process: the web build's # only chat, a desktop option. Without the engine feature: no model runtime, # NoModel and the tool console (the child process keeps the real models). -makepad-aichat = { git = "https://github.com/OctoSense-org/makepad.git", rev = "2bfbb99c8e56ff0c15d7d31061aa49e239e220e2", default-features = false, optional = true } +makepad-aichat = { git = "https://github.com/OctoSense-org/makepad.git", rev = "57b31c9978635e51e633558131d1888d9ef4f45a", default-features = false, optional = true } # A phone cannot spawn the desktop catalog's Cargo binaries. Link the # available modules into native mobile builds without requiring CLI flags. [target.'cfg(any(target_os = "android", target_os = "ios"))'.dependencies] octosense-reference = { path = "apps/reference" } octosense-appcard = { path = "apps/appcard" } -makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "2bfbb99c8e56ff0c15d7d31061aa49e239e220e2", default-features = false } -makepad-photos = { git = "https://github.com/OctoSense-org/makepad.git", rev = "2bfbb99c8e56ff0c15d7d31061aa49e239e220e2", default-features = false } +makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "57b31c9978635e51e633558131d1888d9ef4f45a", default-features = false } +makepad-photos = { git = "https://github.com/OctoSense-org/makepad.git", rev = "57b31c9978635e51e633558131d1888d9ef4f45a", default-features = false } [features] default = [] diff --git a/apps/appcard/Cargo.toml b/apps/appcard/Cargo.toml index 771d35af..340a31da 100644 --- a/apps/appcard/Cargo.toml +++ b/apps/appcard/Cargo.toml @@ -6,12 +6,12 @@ license = "Apache-2.0" publish = false [dependencies] -makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "2bfbb99c8e56ff0c15d7d31061aa49e239e220e2", features = ["appcard", "maps"] } -makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "2bfbb99c8e56ff0c15d7d31061aa49e239e220e2" } +makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "57b31c9978635e51e633558131d1888d9ef4f45a", features = ["appcard", "maps"] } +makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "57b31c9978635e51e633558131d1888d9ef4f45a" } # The whole Octoscript-AppCard app as a library: routing brain, card store and # transport, L0 lowering, sessions, composer, kernel agent — mounted here as # its `AppShell` widget. No default features: the app's own entry points # (`app_main!`, the dev monitor) stay out; this module is the entry point. -octos-app = { git = "https://github.com/OctoSense-org/Octoscript-AppCard.git", rev = "64998ed4462c0d56d04e8122dc756afe1bd26de5", default-features = false } +octos-app = { git = "https://github.com/OctoSense-org/Octoscript-AppCard.git", rev = "1cf78120033b1a1a3f597b627032e366e73a7e39", default-features = false } # Tool arguments (`ask` takes `{"text": ...}`). serde_json = "1.0" diff --git a/apps/reference/Cargo.toml b/apps/reference/Cargo.toml index 8bb80331..57b2dcf9 100644 --- a/apps/reference/Cargo.toml +++ b/apps/reference/Cargo.toml @@ -6,5 +6,5 @@ license = "Apache-2.0" publish = false [dependencies] -makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "2bfbb99c8e56ff0c15d7d31061aa49e239e220e2" } -makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "2bfbb99c8e56ff0c15d7d31061aa49e239e220e2" } +makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "57b31c9978635e51e633558131d1888d9ef4f45a" } +makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "57b31c9978635e51e633558131d1888d9ef4f45a" } diff --git a/native-runtime.lock.json b/native-runtime.lock.json index 70132c6e..4e48926e 100644 --- a/native-runtime.lock.json +++ b/native-runtime.lock.json @@ -1,5 +1,5 @@ { "schema_version": 1, "url": "https://github.com/OctoSense-org/Octoscript-Makepad.git", - "revision": "082314ed712f7fa318199d92b206692164f2b016" + "revision": "36ad1f220ff74d0cbccd89d2b17769133b3fafd1" } From f4e8779a260ce53e7bbf5564c524e1b39f6f185d Mon Sep 17 00:00:00 2001 From: ychen Date: Thu, 24 Sep 2026 12:34:36 -0700 Subject: [PATCH 11/42] Pin Rinx main (b65cb454, Rinx#19 merged) Co-Authored-By: Claude Opus 5.5 (1M context) --- Cargo.lock | 6 +++--- Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b9732846..6729f0ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -356,7 +356,7 @@ dependencies = [ [[package]] name = "article-core" version = "0.1.0" -source = "git+https://github.com/upstreamlabs/Rinx?rev=127cd2ec997f3a3f7056d0bf818ef2bc57c06f57#127cd2ec997f3a3f7056d0bf818ef2bc57c06f57" +source = "git+https://github.com/upstreamlabs/Rinx?rev=b65cb454cd5cd0c42ddbfaeaa1292b91a04e2663#b65cb454cd5cd0c42ddbfaeaa1292b91a04e2663" dependencies = [ "blake3", "image", @@ -373,7 +373,7 @@ dependencies = [ [[package]] name = "article-makepad" version = "0.1.0" -source = "git+https://github.com/upstreamlabs/Rinx?rev=127cd2ec997f3a3f7056d0bf818ef2bc57c06f57#127cd2ec997f3a3f7056d0bf818ef2bc57c06f57" +source = "git+https://github.com/upstreamlabs/Rinx?rev=b65cb454cd5cd0c42ddbfaeaa1292b91a04e2663#b65cb454cd5cd0c42ddbfaeaa1292b91a04e2663" dependencies = [ "article-core", "libc", @@ -8197,7 +8197,7 @@ dependencies = [ [[package]] name = "rinx" version = "1.0.0-beta.1" -source = "git+https://github.com/upstreamlabs/Rinx?rev=127cd2ec997f3a3f7056d0bf818ef2bc57c06f57#127cd2ec997f3a3f7056d0bf818ef2bc57c06f57" +source = "git+https://github.com/upstreamlabs/Rinx?rev=b65cb454cd5cd0c42ddbfaeaa1292b91a04e2663#b65cb454cd5cd0c42ddbfaeaa1292b91a04e2663" dependencies = [ "anyhow", "arboard", diff --git a/Cargo.toml b/Cargo.toml index 905ef36e..07fb7b86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,7 +59,7 @@ octosense-reference = { path = "apps/reference", optional = true } octosense-appcard = { path = "apps/appcard", optional = true } # Rinx (the Matrix client with Moments and articles) as an in-process module. # Built without its standalone window entry point. -rinx = { git = "https://github.com/upstreamlabs/Rinx", rev = "127cd2ec997f3a3f7056d0bf818ef2bc57c06f57", default-features = false, features = ["octosense-module"], optional = true } +rinx = { git = "https://github.com/upstreamlabs/Rinx", rev = "b65cb454cd5cd0c42ddbfaeaa1292b91a04e2663", default-features = false, features = ["octosense-module"], optional = true } # The assistant as a module, seated in the pane in-process: the web build's # only chat, a desktop option. Without the engine feature: no model runtime, # NoModel and the tool console (the child process keeps the real models). From 43d5f57c3eb118e6122816b43657240bef27c007 Mon Sep 17 00:00:00 2001 From: ychen Date: Thu, 24 Sep 2026 13:09:05 -0700 Subject: [PATCH 12/42] Window surfaces scope their app's overlays and blur (AppCard over raised windows) A window's popups, modals and glass (`begin_overlay_*`) went into the desktop window's overlay list, which paints after every window. AppCard's root is a GlassPanel, so its whole shell stayed on top of any window raised over it. WindowFrame, the texture each rounded window is drawn into, now opens a nested overlay and a CaptureGauss for the app (as makepad's apps/wm WindowFrame does). The app's overlays composite inside its own surface, and its glass samples a pyramid built from that surface, so they follow the window's stacking. Frames that go away forget their gauss state, because the pass slot is reused. Co-Authored-By: Claude Opus 5.5 (1M context) --- src/desk.rs | 8 +++++++- src/dock_warp.rs | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/desk.rs b/src/desk.rs index 9a6776b9..43c55905 100644 --- a/src/desk.rs +++ b/src/desk.rs @@ -1861,7 +1861,13 @@ impl Widget for WmDesk { // when the workspace returns. self.anims .retain(|client, anim| live.contains(client) || anim.close_t.is_some()); - self.desktop_frames.retain(|client, _| self.anims.contains_key(client)); + let gone: Vec = self.desktop_frames.keys() + .filter(|client| !self.anims.contains_key(client)).copied().collect(); + for client in gone { + if let Some(frame) = self.desktop_frames.remove(&client) { + frame.forget(cx); + } + } // Closing tiles paint under the live ones. let closing: Vec = self diff --git a/src/dock_warp.rs b/src/dock_warp.rs index e6a8ada5..0bd0dafe 100644 --- a/src/dock_warp.rs +++ b/src/dock_warp.rs @@ -1,5 +1,7 @@ //! A whole-window texture pulled into its dock icon. No app layout or resize //! occurs during the warp; the same frozen surface is used for restoration. +use makepad_widgets::gauss_view::CaptureGauss; +use makepad_widgets::makepad_draw::overlay::{Overlay, OverlayScope}; use makepad_widgets::*; script_mod! { @@ -53,12 +55,24 @@ pub struct DrawDockWarp { y_flip: f32, } +/// An app's frame in a texture of its own. Everything the app draws — its +/// root, the lists it lifts with `begin_overlay_*` (popups, a blur layer), +/// and the gauss pyramid those may ask for — records into THIS pass tree: +/// the overlay root is the capture's for the duration of `begin`/`end` +/// (never the window's overlay slot, which paints after every window), and +/// `request_window_gauss` inside resolves to a pyramid built from this very +/// texture (`CaptureGauss`). A presenter composites the texture and nothing +/// else, so a window's popups and glass stay in the window's stacking order. pub struct WindowFrame { pass: DrawPass, list: DrawList2d, texture: Texture, _depth: Texture, frozen: bool, + overlay: Overlay, + overlay_scope: Option, + gauss: CaptureGauss, + rect: Rect, } impl WindowFrame { pub fn new(cx: &mut Cx) -> Self { @@ -92,6 +106,10 @@ impl WindowFrame { texture, _depth: depth, frozen: false, + overlay: Overlay { draw_list: DrawList::new(cx) }, + overlay_scope: None, + gauss: CaptureGauss::new(cx), + rect: Rect::default(), } } pub fn frozen(&self) -> bool { @@ -101,6 +119,11 @@ impl WindowFrame { self.pass.draw_pass_id() } pub fn texture(&self) -> &Texture { &self.texture } + /// The capture is going away: its gauss request state is keyed by the + /// pass slot, which the next capture may reuse. + pub fn forget(self, cx: &mut Cx) { + CaptureGauss::forget(cx, self.pass.draw_pass_id()); + } pub fn begin(&mut self, cx: &mut Cx2d, rect: Rect) { self.frozen = false; if std::env::var_os("MAKEPAD_WM_TRACE_WARP").is_some() { @@ -114,11 +137,27 @@ impl WindowFrame { cx.begin_pass(&self.pass, Some(dpi)); self.list.begin_always(cx); cx.begin_root_turtle(root_size, Layout::flow_overlay()); + // From here every overlay the app lifts and every gauss it asks for + // is the capture's own. + self.rect = rect; + let pass = self.pass.draw_pass_id(); + self.overlay_scope = Some(self.overlay.begin_nested_for_pass(cx, pass)); + self.gauss.begin(cx, pass, rect.pos, rect.size, root_size); } pub fn end(&mut self, cx: &mut Cx2d) { + let pass = self.pass.draw_pass_id(); + // The scene (when a pyramid was built) goes under the overlays, + // the overlays last — the window's own order. + let gauss_changed = self.gauss.end(cx, pass, self.rect); + if let Some(scope) = self.overlay_scope.take() { + self.overlay.end_nested(cx, scope); + } cx.end_pass_sized_turtle(); self.list.end(cx); cx.end_pass(&self.pass); + if gauss_changed { + cx.repaint_pass_and_child_passes(pass); + } if std::env::var_os("MAKEPAD_WM_TRACE_WARP").is_some() { log!("warp: capture end"); } From 401fba84c5358d5c7cb0af81e7024b52e757a842 Mon Sep 17 00:00:00 2001 From: ychen Date: Thu, 24 Sep 2026 13:17:36 -0700 Subject: [PATCH 13/42] Pin AppCard main (7f468bb9) Co-Authored-By: Claude Opus 5.5 (1M context) --- Cargo.lock | 64 ++++++++++++++++++++--------------------- apps/appcard/Cargo.toml | 2 +- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6729f0ab..f1f2a61b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6560,7 +6560,7 @@ dependencies = [ [[package]] name = "octos-agent" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39#1cf78120033b1a1a3f597b627032e366e73a7e39" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8#7f468bb9554cb520297103eb98a76aaa0d01bee8" dependencies = [ "async-trait", "base64 0.22.1", @@ -6585,7 +6585,7 @@ dependencies = [ "metrics", "oauth2", "octos-bus", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8)", "octos-llm", "octos-memory", "octos-plugin", @@ -6621,7 +6621,7 @@ dependencies = [ [[package]] name = "octos-app" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39#1cf78120033b1a1a3f597b627032e366e73a7e39" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8#7f468bb9554cb520297103eb98a76aaa0d01bee8" dependencies = [ "blake3", "chrono", @@ -6648,7 +6648,7 @@ dependencies = [ [[package]] name = "octos-app-render" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39#1cf78120033b1a1a3f597b627032e366e73a7e39" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8#7f468bb9554cb520297103eb98a76aaa0d01bee8" dependencies = [ "streaming-markdown-kit", ] @@ -6656,7 +6656,7 @@ dependencies = [ [[package]] name = "octos-app-store" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39#1cf78120033b1a1a3f597b627032e366e73a7e39" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8#7f468bb9554cb520297103eb98a76aaa0d01bee8" dependencies = [ "chrono", "keyring", @@ -6670,7 +6670,7 @@ dependencies = [ [[package]] name = "octos-app-transport" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39#1cf78120033b1a1a3f597b627032e366e73a7e39" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8#7f468bb9554cb520297103eb98a76aaa0d01bee8" dependencies = [ "android_logger", "chrono", @@ -6691,7 +6691,7 @@ dependencies = [ [[package]] name = "octos-bus" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39#1cf78120033b1a1a3f597b627032e366e73a7e39" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8#7f468bb9554cb520297103eb98a76aaa0d01bee8" dependencies = [ "aes 0.8.4", "async-trait", @@ -6705,7 +6705,7 @@ dependencies = [ "futures", "lru", "metrics", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8)", "reqwest 0.12.28", "serde", "serde_json", @@ -6720,7 +6720,7 @@ dependencies = [ [[package]] name = "octos-cli" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39#1cf78120033b1a1a3f597b627032e366e73a7e39" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8#7f468bb9554cb520297103eb98a76aaa0d01bee8" dependencies = [ "agent-client-protocol", "argon2", @@ -6750,7 +6750,7 @@ dependencies = [ "metrics-exporter-prometheus", "octos-agent", "octos-bus", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8)", "octos-diagnostics", "octos-fleet", "octos-fleet-worker", @@ -6814,7 +6814,7 @@ dependencies = [ [[package]] name = "octos-core" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39#1cf78120033b1a1a3f597b627032e366e73a7e39" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8#7f468bb9554cb520297103eb98a76aaa0d01bee8" dependencies = [ "chrono", "eyre", @@ -6828,10 +6828,10 @@ dependencies = [ [[package]] name = "octos-diagnostics" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39#1cf78120033b1a1a3f597b627032e366e73a7e39" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8#7f468bb9554cb520297103eb98a76aaa0d01bee8" dependencies = [ "eyre", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8)", "reqwest 0.12.28", "serde", "serde_json", @@ -6840,10 +6840,10 @@ dependencies = [ [[package]] name = "octos-fleet" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39#1cf78120033b1a1a3f597b627032e366e73a7e39" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8#7f468bb9554cb520297103eb98a76aaa0d01bee8" dependencies = [ "eyre", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8)", "redb", "rusqlite", "serde", @@ -6856,12 +6856,12 @@ dependencies = [ [[package]] name = "octos-fleet-worker" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39#1cf78120033b1a1a3f597b627032e366e73a7e39" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8#7f468bb9554cb520297103eb98a76aaa0d01bee8" dependencies = [ "async-trait", "eyre", "octos-agent", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8)", "octos-fleet", "octos-llm", "octos-memory", @@ -6873,7 +6873,7 @@ dependencies = [ [[package]] name = "octos-llm" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39#1cf78120033b1a1a3f597b627032e366e73a7e39" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8#7f468bb9554cb520297103eb98a76aaa0d01bee8" dependencies = [ "async-trait", "base64 0.22.1", @@ -6883,7 +6883,7 @@ dependencies = [ "jsonwebtoken", "libc", "metrics", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8)", "redb", "reqwest 0.12.28", "secrecy", @@ -6897,13 +6897,13 @@ dependencies = [ [[package]] name = "octos-memory" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39#1cf78120033b1a1a3f597b627032e366e73a7e39" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8#7f468bb9554cb520297103eb98a76aaa0d01bee8" dependencies = [ "bincode", "chrono", "eyre", "hnsw_rs", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8)", "redb", "regex", "serde", @@ -6916,7 +6916,7 @@ dependencies = [ [[package]] name = "octos-pipeline" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39#1cf78120033b1a1a3f597b627032e366e73a7e39" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8#7f468bb9554cb520297103eb98a76aaa0d01bee8" dependencies = [ "async-trait", "chrono", @@ -6924,7 +6924,7 @@ dependencies = [ "futures", "glob", "octos-agent", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8)", "octos-llm", "octos-memory", "octos-plugin", @@ -6938,7 +6938,7 @@ dependencies = [ [[package]] name = "octos-plugin" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39#1cf78120033b1a1a3f597b627032e366e73a7e39" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8#7f468bb9554cb520297103eb98a76aaa0d01bee8" dependencies = [ "eyre", "metrics", @@ -6952,7 +6952,7 @@ dependencies = [ [[package]] name = "octos-sandbox" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39#1cf78120033b1a1a3f597b627032e366e73a7e39" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8#7f468bb9554cb520297103eb98a76aaa0d01bee8" dependencies = [ "clap", "eyre", @@ -6967,7 +6967,7 @@ dependencies = [ [[package]] name = "octos-services" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39#1cf78120033b1a1a3f597b627032e366e73a7e39" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8#7f468bb9554cb520297103eb98a76aaa0d01bee8" dependencies = [ "chrono", "dirs", @@ -6975,7 +6975,7 @@ dependencies = [ "flate2", "futures", "octos-bus", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8)", "octos-llm", "reqwest 0.12.28", "serde", @@ -6988,14 +6988,14 @@ dependencies = [ [[package]] name = "octos-store" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39#1cf78120033b1a1a3f597b627032e366e73a7e39" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8#7f468bb9554cb520297103eb98a76aaa0d01bee8" dependencies = [ "base64 0.22.1", "chrono", "constant_time_eq 0.3.1", "eyre", "getrandom 0.2.17", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8)", "redb", "serde", "serde_json", @@ -7008,7 +7008,7 @@ dependencies = [ [[package]] name = "octos-swarm" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39#1cf78120033b1a1a3f597b627032e366e73a7e39" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8#7f468bb9554cb520297103eb98a76aaa0d01bee8" dependencies = [ "async-trait", "chrono", @@ -7027,12 +7027,12 @@ dependencies = [ [[package]] name = "octos-workflows" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39#1cf78120033b1a1a3f597b627032e366e73a7e39" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8#7f468bb9554cb520297103eb98a76aaa0d01bee8" dependencies = [ "chrono", "eyre", "octos-agent", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=1cf78120033b1a1a3f597b627032e366e73a7e39)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8)", "octos-pipeline", "serde", "serde_json", diff --git a/apps/appcard/Cargo.toml b/apps/appcard/Cargo.toml index 340a31da..75072b8b 100644 --- a/apps/appcard/Cargo.toml +++ b/apps/appcard/Cargo.toml @@ -12,6 +12,6 @@ makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev # transport, L0 lowering, sessions, composer, kernel agent — mounted here as # its `AppShell` widget. No default features: the app's own entry points # (`app_main!`, the dev monitor) stay out; this module is the entry point. -octos-app = { git = "https://github.com/OctoSense-org/Octoscript-AppCard.git", rev = "1cf78120033b1a1a3f597b627032e366e73a7e39", default-features = false } +octos-app = { git = "https://github.com/OctoSense-org/Octoscript-AppCard.git", rev = "7f468bb9554cb520297103eb98a76aaa0d01bee8", default-features = false } # Tool arguments (`ask` takes `{"text": ...}`). serde_json = "1.0" From eaa13aa629375708d9829a9d5eb9a610e3e6ea8f Mon Sep 17 00:00:00 2001 From: ychen Date: Thu, 24 Sep 2026 15:15:56 -0700 Subject: [PATCH 14/42] Lock Octoscript-Makepad main c4c96822 (makepad main 1d3d383e); AppCard 9e8e4898, Rinx 5ad2c173 Co-Authored-By: Claude Opus 5.5 (1M context) --- Cargo.lock | 70 +++++++++++++++++++-------------------- Cargo.toml | 28 ++++++++-------- apps/appcard/Cargo.toml | 6 ++-- apps/reference/Cargo.toml | 4 +-- native-runtime.lock.json | 2 +- 5 files changed, 55 insertions(+), 55 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f1f2a61b..6717f751 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -356,7 +356,7 @@ dependencies = [ [[package]] name = "article-core" version = "0.1.0" -source = "git+https://github.com/upstreamlabs/Rinx?rev=b65cb454cd5cd0c42ddbfaeaa1292b91a04e2663#b65cb454cd5cd0c42ddbfaeaa1292b91a04e2663" +source = "git+https://github.com/upstreamlabs/Rinx?rev=5ad2c1739e5b4776588518ab76440a5367c9968e#5ad2c1739e5b4776588518ab76440a5367c9968e" dependencies = [ "blake3", "image", @@ -373,7 +373,7 @@ dependencies = [ [[package]] name = "article-makepad" version = "0.1.0" -source = "git+https://github.com/upstreamlabs/Rinx?rev=b65cb454cd5cd0c42ddbfaeaa1292b91a04e2663#b65cb454cd5cd0c42ddbfaeaa1292b91a04e2663" +source = "git+https://github.com/upstreamlabs/Rinx?rev=5ad2c1739e5b4776588518ab76440a5367c9968e#5ad2c1739e5b4776588518ab76440a5367c9968e" dependencies = [ "article-core", "libc", @@ -6560,7 +6560,7 @@ dependencies = [ [[package]] name = "octos-agent" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8#7f468bb9554cb520297103eb98a76aaa0d01bee8" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d#9e8e4898b8a7c03dbcb62993e7826577411e489d" dependencies = [ "async-trait", "base64 0.22.1", @@ -6585,7 +6585,7 @@ dependencies = [ "metrics", "oauth2", "octos-bus", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d)", "octos-llm", "octos-memory", "octos-plugin", @@ -6621,7 +6621,7 @@ dependencies = [ [[package]] name = "octos-app" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8#7f468bb9554cb520297103eb98a76aaa0d01bee8" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d#9e8e4898b8a7c03dbcb62993e7826577411e489d" dependencies = [ "blake3", "chrono", @@ -6648,7 +6648,7 @@ dependencies = [ [[package]] name = "octos-app-render" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8#7f468bb9554cb520297103eb98a76aaa0d01bee8" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d#9e8e4898b8a7c03dbcb62993e7826577411e489d" dependencies = [ "streaming-markdown-kit", ] @@ -6656,7 +6656,7 @@ dependencies = [ [[package]] name = "octos-app-store" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8#7f468bb9554cb520297103eb98a76aaa0d01bee8" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d#9e8e4898b8a7c03dbcb62993e7826577411e489d" dependencies = [ "chrono", "keyring", @@ -6670,7 +6670,7 @@ dependencies = [ [[package]] name = "octos-app-transport" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8#7f468bb9554cb520297103eb98a76aaa0d01bee8" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d#9e8e4898b8a7c03dbcb62993e7826577411e489d" dependencies = [ "android_logger", "chrono", @@ -6691,7 +6691,7 @@ dependencies = [ [[package]] name = "octos-bus" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8#7f468bb9554cb520297103eb98a76aaa0d01bee8" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d#9e8e4898b8a7c03dbcb62993e7826577411e489d" dependencies = [ "aes 0.8.4", "async-trait", @@ -6705,7 +6705,7 @@ dependencies = [ "futures", "lru", "metrics", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d)", "reqwest 0.12.28", "serde", "serde_json", @@ -6720,7 +6720,7 @@ dependencies = [ [[package]] name = "octos-cli" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8#7f468bb9554cb520297103eb98a76aaa0d01bee8" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d#9e8e4898b8a7c03dbcb62993e7826577411e489d" dependencies = [ "agent-client-protocol", "argon2", @@ -6750,7 +6750,7 @@ dependencies = [ "metrics-exporter-prometheus", "octos-agent", "octos-bus", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d)", "octos-diagnostics", "octos-fleet", "octos-fleet-worker", @@ -6814,7 +6814,7 @@ dependencies = [ [[package]] name = "octos-core" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8#7f468bb9554cb520297103eb98a76aaa0d01bee8" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d#9e8e4898b8a7c03dbcb62993e7826577411e489d" dependencies = [ "chrono", "eyre", @@ -6828,10 +6828,10 @@ dependencies = [ [[package]] name = "octos-diagnostics" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8#7f468bb9554cb520297103eb98a76aaa0d01bee8" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d#9e8e4898b8a7c03dbcb62993e7826577411e489d" dependencies = [ "eyre", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d)", "reqwest 0.12.28", "serde", "serde_json", @@ -6840,10 +6840,10 @@ dependencies = [ [[package]] name = "octos-fleet" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8#7f468bb9554cb520297103eb98a76aaa0d01bee8" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d#9e8e4898b8a7c03dbcb62993e7826577411e489d" dependencies = [ "eyre", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d)", "redb", "rusqlite", "serde", @@ -6856,12 +6856,12 @@ dependencies = [ [[package]] name = "octos-fleet-worker" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8#7f468bb9554cb520297103eb98a76aaa0d01bee8" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d#9e8e4898b8a7c03dbcb62993e7826577411e489d" dependencies = [ "async-trait", "eyre", "octos-agent", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d)", "octos-fleet", "octos-llm", "octos-memory", @@ -6873,7 +6873,7 @@ dependencies = [ [[package]] name = "octos-llm" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8#7f468bb9554cb520297103eb98a76aaa0d01bee8" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d#9e8e4898b8a7c03dbcb62993e7826577411e489d" dependencies = [ "async-trait", "base64 0.22.1", @@ -6883,7 +6883,7 @@ dependencies = [ "jsonwebtoken", "libc", "metrics", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d)", "redb", "reqwest 0.12.28", "secrecy", @@ -6897,13 +6897,13 @@ dependencies = [ [[package]] name = "octos-memory" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8#7f468bb9554cb520297103eb98a76aaa0d01bee8" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d#9e8e4898b8a7c03dbcb62993e7826577411e489d" dependencies = [ "bincode", "chrono", "eyre", "hnsw_rs", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d)", "redb", "regex", "serde", @@ -6916,7 +6916,7 @@ dependencies = [ [[package]] name = "octos-pipeline" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8#7f468bb9554cb520297103eb98a76aaa0d01bee8" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d#9e8e4898b8a7c03dbcb62993e7826577411e489d" dependencies = [ "async-trait", "chrono", @@ -6924,7 +6924,7 @@ dependencies = [ "futures", "glob", "octos-agent", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d)", "octos-llm", "octos-memory", "octos-plugin", @@ -6938,7 +6938,7 @@ dependencies = [ [[package]] name = "octos-plugin" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8#7f468bb9554cb520297103eb98a76aaa0d01bee8" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d#9e8e4898b8a7c03dbcb62993e7826577411e489d" dependencies = [ "eyre", "metrics", @@ -6952,7 +6952,7 @@ dependencies = [ [[package]] name = "octos-sandbox" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8#7f468bb9554cb520297103eb98a76aaa0d01bee8" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d#9e8e4898b8a7c03dbcb62993e7826577411e489d" dependencies = [ "clap", "eyre", @@ -6967,7 +6967,7 @@ dependencies = [ [[package]] name = "octos-services" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8#7f468bb9554cb520297103eb98a76aaa0d01bee8" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d#9e8e4898b8a7c03dbcb62993e7826577411e489d" dependencies = [ "chrono", "dirs", @@ -6975,7 +6975,7 @@ dependencies = [ "flate2", "futures", "octos-bus", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d)", "octos-llm", "reqwest 0.12.28", "serde", @@ -6988,14 +6988,14 @@ dependencies = [ [[package]] name = "octos-store" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8#7f468bb9554cb520297103eb98a76aaa0d01bee8" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d#9e8e4898b8a7c03dbcb62993e7826577411e489d" dependencies = [ "base64 0.22.1", "chrono", "constant_time_eq 0.3.1", "eyre", "getrandom 0.2.17", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d)", "redb", "serde", "serde_json", @@ -7008,7 +7008,7 @@ dependencies = [ [[package]] name = "octos-swarm" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8#7f468bb9554cb520297103eb98a76aaa0d01bee8" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d#9e8e4898b8a7c03dbcb62993e7826577411e489d" dependencies = [ "async-trait", "chrono", @@ -7027,12 +7027,12 @@ dependencies = [ [[package]] name = "octos-workflows" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8#7f468bb9554cb520297103eb98a76aaa0d01bee8" +source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d#9e8e4898b8a7c03dbcb62993e7826577411e489d" dependencies = [ "chrono", "eyre", "octos-agent", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=7f468bb9554cb520297103eb98a76aaa0d01bee8)", + "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d)", "octos-pipeline", "serde", "serde_json", @@ -8197,7 +8197,7 @@ dependencies = [ [[package]] name = "rinx" version = "1.0.0-beta.1" -source = "git+https://github.com/upstreamlabs/Rinx?rev=b65cb454cd5cd0c42ddbfaeaa1292b91a04e2663#b65cb454cd5cd0c42ddbfaeaa1292b91a04e2663" +source = "git+https://github.com/upstreamlabs/Rinx?rev=5ad2c1739e5b4776588518ab76440a5367c9968e#5ad2c1739e5b4776588518ab76440a5367c9968e" dependencies = [ "anyhow", "arboard", diff --git a/Cargo.toml b/Cargo.toml index 07fb7b86..c1728429 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,38 +40,38 @@ path = "src/main.rs" [dependencies] # `appcard,maps`: the Octoscript-AppCard widget set (kit, text roles, weather, # finance, matplot) and the map navigation layer the appcard module renders. -makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "57b31c9978635e51e633558131d1888d9ef4f45a", features = ["appcard", "maps"] } -makepad-studio-protocol = { git = "https://github.com/OctoSense-org/makepad.git", rev = "57b31c9978635e51e633558131d1888d9ef4f45a" } -makepad-network = { git = "https://github.com/OctoSense-org/makepad.git", rev = "57b31c9978635e51e633558131d1888d9ef4f45a" } -makepad-wm-api = { git = "https://github.com/OctoSense-org/makepad.git", rev = "57b31c9978635e51e633558131d1888d9ef4f45a" } +makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4", features = ["appcard", "maps"] } +makepad-studio-protocol = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4" } +makepad-network = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4" } +makepad-wm-api = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4" } # The AI services wire only: the WM relays frames between the aichat child and the apps. -makepad-ai-services = { git = "https://github.com/OctoSense-org/makepad.git", rev = "57b31c9978635e51e633558131d1888d9ef4f45a" } -makepad-strict-json = { git = "https://github.com/OctoSense-org/makepad.git", rev = "57b31c9978635e51e633558131d1888d9ef4f45a" } +makepad-ai-services = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4" } +makepad-strict-json = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4" } # In-process hosting (aicontrol §3): the module contract, the palette applied # into each instance's isolate, and the modules this build links. -makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "57b31c9978635e51e633558131d1888d9ef4f45a" } -makepad-wm-theme = { git = "https://github.com/OctoSense-org/makepad.git", rev = "57b31c9978635e51e633558131d1888d9ef4f45a" } -makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "57b31c9978635e51e633558131d1888d9ef4f45a", default-features = false, optional = true } -makepad-photos = { git = "https://github.com/OctoSense-org/makepad.git", rev = "57b31c9978635e51e633558131d1888d9ef4f45a", default-features = false, optional = true } +makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4" } +makepad-wm-theme = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4" } +makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4", default-features = false, optional = true } +makepad-photos = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4", default-features = false, optional = true } octosense-reference = { path = "apps/reference", optional = true } # The whole Octoscript-AppCard app hosted in a tile, in-process (Phase B of # the AppCard port): octos-app's AppShell — routing brain, cards, kernel agent. octosense-appcard = { path = "apps/appcard", optional = true } # Rinx (the Matrix client with Moments and articles) as an in-process module. # Built without its standalone window entry point. -rinx = { git = "https://github.com/upstreamlabs/Rinx", rev = "b65cb454cd5cd0c42ddbfaeaa1292b91a04e2663", default-features = false, features = ["octosense-module"], optional = true } +rinx = { git = "https://github.com/upstreamlabs/Rinx", rev = "5ad2c1739e5b4776588518ab76440a5367c9968e", default-features = false, features = ["octosense-module"], optional = true } # The assistant as a module, seated in the pane in-process: the web build's # only chat, a desktop option. Without the engine feature: no model runtime, # NoModel and the tool console (the child process keeps the real models). -makepad-aichat = { git = "https://github.com/OctoSense-org/makepad.git", rev = "57b31c9978635e51e633558131d1888d9ef4f45a", default-features = false, optional = true } +makepad-aichat = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4", default-features = false, optional = true } # A phone cannot spawn the desktop catalog's Cargo binaries. Link the # available modules into native mobile builds without requiring CLI flags. [target.'cfg(any(target_os = "android", target_os = "ios"))'.dependencies] octosense-reference = { path = "apps/reference" } octosense-appcard = { path = "apps/appcard" } -makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "57b31c9978635e51e633558131d1888d9ef4f45a", default-features = false } -makepad-photos = { git = "https://github.com/OctoSense-org/makepad.git", rev = "57b31c9978635e51e633558131d1888d9ef4f45a", default-features = false } +makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4", default-features = false } +makepad-photos = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4", default-features = false } [features] default = [] diff --git a/apps/appcard/Cargo.toml b/apps/appcard/Cargo.toml index 75072b8b..23fe5296 100644 --- a/apps/appcard/Cargo.toml +++ b/apps/appcard/Cargo.toml @@ -6,12 +6,12 @@ license = "Apache-2.0" publish = false [dependencies] -makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "57b31c9978635e51e633558131d1888d9ef4f45a", features = ["appcard", "maps"] } -makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "57b31c9978635e51e633558131d1888d9ef4f45a" } +makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4", features = ["appcard", "maps"] } +makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4" } # The whole Octoscript-AppCard app as a library: routing brain, card store and # transport, L0 lowering, sessions, composer, kernel agent — mounted here as # its `AppShell` widget. No default features: the app's own entry points # (`app_main!`, the dev monitor) stay out; this module is the entry point. -octos-app = { git = "https://github.com/OctoSense-org/Octoscript-AppCard.git", rev = "7f468bb9554cb520297103eb98a76aaa0d01bee8", default-features = false } +octos-app = { git = "https://github.com/OctoSense-org/Octoscript-AppCard.git", rev = "9e8e4898b8a7c03dbcb62993e7826577411e489d", default-features = false } # Tool arguments (`ask` takes `{"text": ...}`). serde_json = "1.0" diff --git a/apps/reference/Cargo.toml b/apps/reference/Cargo.toml index 57b2dcf9..18e2d10e 100644 --- a/apps/reference/Cargo.toml +++ b/apps/reference/Cargo.toml @@ -6,5 +6,5 @@ license = "Apache-2.0" publish = false [dependencies] -makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "57b31c9978635e51e633558131d1888d9ef4f45a" } -makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "57b31c9978635e51e633558131d1888d9ef4f45a" } +makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4" } +makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4" } diff --git a/native-runtime.lock.json b/native-runtime.lock.json index 4e48926e..cbc46b6e 100644 --- a/native-runtime.lock.json +++ b/native-runtime.lock.json @@ -1,5 +1,5 @@ { "schema_version": 1, "url": "https://github.com/OctoSense-org/Octoscript-Makepad.git", - "revision": "36ad1f220ff74d0cbccd89d2b17769133b3fafd1" + "revision": "c4c9682219d5bb549856e35086adf1b354844dc3" } From d44fcbd14411d2527275c089177808e237b4ea43 Mon Sep 17 00:00:00 2001 From: ychen Date: Sat, 26 Sep 2026 00:35:07 -0700 Subject: [PATCH 15/42] Setup: pin OctoSense-System-Apps and the contained-apps runtime patch tools/setup-native.py now prepares every sibling source itself, the way OctoSense ROM's Home does: - octoscript-makepad at native-runtime.lock.json, makepad and octoscript at that release's runtime.json; - makepad with the reviewed patch runtime-patches.lock.json names (OctoSense-org/makepad#30: contained script apps, host services), checked by sha256 and resulting tree; a clean checkout of the patch's source commit is the same tree and is accepted as prepared; - ../OctoSense-System-Apps at native-apps.lock.json: the system app bundles, the Mail host service and the AppCard assistant. --cargo-manifest also rejects a second octos source. Co-Authored-By: Claude Opus 5.5 (1M context) --- native-apps.lock.json | 9 + patches/runtime/makepad-contained-apps.patch | 4414 ++++++++++++++++++ runtime-patches.lock.json | 17 + tools/setup-native.py | 192 +- 4 files changed, 4597 insertions(+), 35 deletions(-) create mode 100644 native-apps.lock.json create mode 100644 patches/runtime/makepad-contained-apps.patch create mode 100644 runtime-patches.lock.json diff --git a/native-apps.lock.json b/native-apps.lock.json new file mode 100644 index 00000000..25e9df59 --- /dev/null +++ b/native-apps.lock.json @@ -0,0 +1,9 @@ +{ + "schema_version": 1, + "repositories": { + "system-apps": { + "url": "https://github.com/OctoSense-org/OctoSense-System-Apps.git", + "revision": "4d99cb589d00207a9a63e32d46de6685843929d1" + } + } +} diff --git a/patches/runtime/makepad-contained-apps.patch b/patches/runtime/makepad-contained-apps.patch new file mode 100644 index 00000000..f7f1153f --- /dev/null +++ b/patches/runtime/makepad-contained-apps.patch @@ -0,0 +1,4414 @@ +diff --git a/draw/src/text/layouter.rs b/draw/src/text/layouter.rs +index 56f7a4d63..616010173 100644 +--- a/draw/src/text/layouter.rs ++++ b/draw/src/text/layouter.rs +@@ -383,7 +383,7 @@ fn layout_multiline(mut self) -> LaidoutText { + .enumerate() + { + if line_index != 0 { +- self.finish_current_row(true); ++ self.finish_current_row(true, false); + } + if self.is_past_max_rows() { + break; +@@ -394,7 +394,7 @@ fn layout_multiline(mut self) -> LaidoutText { + if has_ellipsis_config { + self.apply_ellipsis_truncation() + } else { +- self.finish_current_row(false); ++ self.finish_current_row(false, false); + self.finish_with(false) + } + } +@@ -457,7 +457,7 @@ fn layout_by_word(&mut self, len: usize) { + } else if self.current_row_is_empty() && !self.current_row_is_continuation() { + self.layout_by_grapheme(fitter.pop()); + } else { +- self.finish_current_row(false); ++ self.finish_current_row(false, true); + } + } + } +@@ -482,7 +482,7 @@ fn layout_by_grapheme(&mut self, len: usize) { + if self.current_row_is_empty() { + self.layout_directly(fitter.pop()); + } else { +- self.finish_current_row(false); ++ self.finish_current_row(false, true); + } + } + } +@@ -518,7 +518,8 @@ fn append_text(&mut self, text: &ShapedText) { + self.current_row_end += text.text.len(); + } + +- fn finish_current_row(&mut self, newline: bool) { ++ /// `soft_wrap`: the row ends because the next word did not fit. ++ fn finish_current_row(&mut self, newline: bool, soft_wrap: bool) { + let font = self.font_family.fonts().first(); + let font_size_in_lpxs = self.style.font_size_in_lpxs(); + let ascender_in_lpxs = font.map_or(0.0, |font| font.ascender_in_ems()) * font_size_in_lpxs; +@@ -531,10 +532,12 @@ fn finish_current_row(&mut self, newline: bool) { + .text + .substr(self.current_row_start..self.current_row_end); + let mut width_in_lpxs = self.current_point_in_lpxs.x; +- if self.options.wrap { ++ if soft_wrap { + // A space consumed at a soft line break still belongs to the text + // (and caret positions), but must not widen or offset the visible + // line. Keep the glyphs and source text intact for hit testing. ++ // Only there: a run's last row keeps its trailing space, which is ++ // the gap before the next run (`to Mail`). + let visible_end = text.trim_end().len(); + for glyph in self.glyphs.iter().rev() { + if glyph.cluster < visible_end { +@@ -722,7 +725,7 @@ fn finish_current_row_if_pending(&mut self) { + let has_pending_content = + self.current_row_start != self.current_row_end || !self.glyphs.is_empty(); + if has_pending_content || self.rows.is_empty() { +- self.finish_current_row(false); ++ self.finish_current_row(false, false); + } + } + } +@@ -1496,6 +1499,16 @@ fn letter_spacing_changes_layout_cache_and_wrapping() { + assert_eq!(centered.rows[0].text.as_str(), "CARD "); + assert!((centered.rows[0].width_in_lpxs - word.size_in_lpxs.width).abs() < 0.01); + assert!((centered.rows[0].origin_in_lpxs.x - 0.05).abs() < 0.01); ++ ++ // A run that ends in a space, laid out where it fits: the space is ++ // the gap before whatever run follows (`to Mail`), not a ++ // soft line break, so it keeps its width. ++ let spaced = layouter.get_or_layout(BorrowedLayoutParams { ++ text: "CARD ", ++ options: LayoutOptions {max_width_in_lpxs: Some(1000.0), wrap: true, ..params.options}, ++ ..params ++ }); ++ assert!(spaced.rows[0].width_in_lpxs > word.size_in_lpxs.width + 1.0); + } + + #[test] +diff --git a/libs/html/src/lib.rs b/libs/html/src/lib.rs +index bac0ce06e..03012b53d 100644 +--- a/libs/html/src/lib.rs ++++ b/libs/html/src/lib.rs +@@ -3417,7 +3417,13 @@ fn text_lookups_skip_parser_artifacts() { + /// Treating U+00A0 and U+3000 as collapsible ate ` ` runs and the + /// full-width spaces in CJK text. + #[test] +- fn only_ascii_whitespace_collapses() { ++ fn a_space_before_an_inline_tag_is_kept() { ++ assert_eq!(text_of("

Welcome to Mail now

"), "Welcome to Mail now"); ++ assert_eq!(text_of("

Welcome to Mail

x

"), "Welcome to Mailx"); ++ } ++ ++ #[test] ++ fn only_ascii_whitespace_collapses() { + assert_eq!(text_of("

a  b

"), "a\u{a0} b"); + assert_eq!(text_of("

  

"), "\u{a0}\u{a0}"); + assert_eq!(text_of("

\u{3000}x\u{3000}

"), "\u{3000}x\u{3000}"); +diff --git a/platform/script/src/lib.rs b/platform/script/src/lib.rs +index f929c8784..69d10c047 100644 +--- a/platform/script/src/lib.rs ++++ b/platform/script/src/lib.rs +@@ -24,6 +24,7 @@ macro_rules! script_eval { + pub mod gen_index; + pub mod heap; + pub mod mod_gc; ++pub mod mod_feed; + pub mod mod_html; + pub mod math_aot; + pub mod mod_math; +diff --git a/platform/script/src/mod_feed.rs b/platform/script/src/mod_feed.rs +new file mode 100644 +index 000000000..497cbc9e0 +--- /dev/null ++++ b/platform/script/src/mod_feed.rs +@@ -0,0 +1,872 @@ ++//! `"".parse_feed(style)`: RSS 2.0 or Atom into an array of rows, ++//! `{title, link, source, published, summary, image}`, for an app that reads ++//! feeds. Parsing in script would spend an app's instruction budget on string ++//! scanning; this is pure computation, so every isolate may call it. ++//! ++//! `style` is `"google"` (titles end in ` - Source`), `"digest"` (TechMeme: ++//! titles end in ` (Outlet)`) or anything else for plain feeds. A document ++//! that is not a feed yields `nil`; a malformed one yields fewer rows. ++//! ++//! Items are split on ` bool { ++ ["http://", "https://"].iter().any(|scheme| s.get(..scheme.len()).is_some_and(|head| head.eq_ignore_ascii_case(scheme))) ++} ++ ++/// One story, whatever the feed. ++#[derive(Clone, Debug, Default, PartialEq)] ++struct Headline { ++ title: String, ++ link: String, ++ source: String, ++ source_id: String, ++ published: Option, ++ points: Option, ++ comments: Option, ++ summary: String, ++ discussion: Option, ++ image: Option, ++} ++ ++fn json_string(out: &mut String, s: &str) { ++ out.push('"'); ++ for c in s.chars() { ++ match c { ++ '"' => out.push_str("\\\""), ++ '\\' => out.push_str("\\\\"), ++ '\n' => out.push_str("\\n"), ++ '\r' => out.push_str("\\r"), ++ '\t' => out.push_str("\\t"), ++ c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)), ++ c => out.push(c), ++ } ++ } ++ out.push('"'); ++} ++ ++fn rows_json(rows: &[Headline]) -> String { ++ let mut out = String::from("["); ++ for (i, row) in rows.iter().enumerate() { ++ if i > 0 { ++ out.push(','); ++ } ++ out.push_str("{\"title\":"); ++ json_string(&mut out, &row.title); ++ out.push_str(",\"link\":"); ++ json_string(&mut out, &row.link); ++ out.push_str(",\"source\":"); ++ json_string(&mut out, &row.source); ++ out.push_str(",\"summary\":"); ++ json_string(&mut out, &row.summary); ++ out.push_str(",\"image\":"); ++ json_string(&mut out, row.image.as_deref().unwrap_or("")); ++ out.push_str(",\"published\":"); ++ match row.published { ++ Some(secs) => out.push_str(&secs.to_string()), ++ None => out.push('0'), ++ } ++ out.push('}'); ++ } ++ out.push(']'); ++ out ++} ++ ++pub fn define_feed_module(heap: &mut ScriptHeap, native: &mut ScriptNative) { ++ native.add_type_method( ++ heap, ++ ScriptValueType::REDUX_STRING, ++ id!(parse_feed), ++ &[(id!(style), NIL)], ++ |vm, args| { ++ let sself = script_value!(vm, args.self); ++ let style_value = script_value!(vm, args.style); ++ let style = vm.bx.heap.string_with(style_value, |_, s| s.to_string()).unwrap_or_default(); ++ let style = match style.as_str() { ++ "google" => Style { split_source: true, digest: false }, ++ "digest" => Style { split_source: false, digest: true }, ++ _ => Style::default(), ++ }; ++ let Some(parsed) = vm.bx.heap.string_with(sself, |_, s| parse_styled(s, style)) else { ++ return script_err_unexpected!(vm.bx.threads.cur_ref().trap, "parse_feed called on non-string value"); ++ }; ++ let Ok(rows) = parsed else { return NIL }; ++ let json = rows_json(&rows); ++ let mut json_parser = std::mem::take(&mut vm.bx.threads.cur().json_parser); ++ let result = json_parser.read_json(&json, &mut vm.bx.heap); ++ vm.bx.threads.cur().json_parser = json_parser; ++ result ++ }, ++ ); ++} ++ ++ ++ ++/// How a feed spells its stories, beyond plain RSS or Atom. ++#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] ++struct Style { ++ /// Google News: titles end in ` - Source`. ++ split_source: bool, ++ /// A digest (TechMeme): titles end in ` (Author/Outlet)` and the ++ /// description is `Outlet: headline` again. ++ digest: bool, ++} ++ ++fn parse_styled(xml: &str, style: Style) -> Result, String> { ++ // A feed that prefixes every Atom element (``) reads like ++ // a bare one once the prefix is dropped. ++ let unprefixed; ++ let xml = if !xml.contains("`. ++ let body = &chunk[chunk.find('>')? + 1..]; ++ let body = body.split_once(close).map_or(body, |(b, _)| b); ++ parse_item(body, is_atom, style) ++ }) ++ .collect(); ++ Ok(rows) ++} ++ ++const RSS: (&str, &str, bool) = ("", false); ++const ATOM: (&str, &str, bool) = ("", true); ++ ++/// The item element to split on and whether the feed is Atom: the root ++/// element decides (``; `` or ``), else the first item ++/// element found. ++fn feed_kind(xml: &str) -> Option<(&'static str, &'static str, bool)> { ++ match root_element(xml) { ++ Some("feed") => Some(ATOM), ++ Some("rss" | "rdf:RDF") => Some(RSS), ++ _ if has_element(xml, "item") => Some(RSS), ++ _ if has_element(xml, "entry") => Some(ATOM), ++ _ => None, ++ } ++} ++ ++/// The name of the root element: the first element after the prolog (a ++/// byte-order mark, whitespace, ``, ``, ``). ++/// Looking only there keeps a `` inside a description from naming ++/// the kind. ++fn root_element(xml: &str) -> Option<&str> { ++ let prolog_space = |c: char| c.is_whitespace() || c == '\u{feff}'; ++ let mut rest = xml.trim_start_matches(prolog_space); ++ loop { ++ let after_prolog = if let Some(after) = rest.strip_prefix("")?.1 ++ } else if let Some(after) = rest.strip_prefix("")?.1 ++ } else if let Some(after) = rest.strip_prefix("`; what follows is not an element, so the caller falls ++ // back to the item heuristic. Feeds do not carry one. ++ after.split_once('>')?.1 ++ } else { ++ break; ++ }; ++ rest = after_prolog.trim_start_matches(prolog_space); ++ } ++ let name = rest.strip_prefix('<')?; ++ let end = name.find(|c: char| c.is_whitespace() || c == '>' || c == '/').unwrap_or(name.len()); ++ Some(&name[..end]).filter(|n| !n.is_empty()) ++} ++ ++/// Whether `xml` opens an element named exactly `name`. ++fn has_element(xml: &str, name: &str) -> bool { ++ let open = format!("<{name}"); ++ let mut from = 0; ++ while let Some(pos) = xml[from..].find(&open) { ++ from += pos + open.len(); ++ if xml[from..].chars().next().is_some_and(|c| c == '>' || c == '/' || c.is_whitespace()) { ++ return true; ++ } ++ } ++ false ++} ++ ++/// One `` / `` body as a row; None without a title or an http ++/// link. ++fn parse_item(body: &str, is_atom: bool, style: Style) -> Option { ++ let mut title = clean_text(element_text(body, "title")?); ++ if title.is_empty() { ++ return None; ++ } ++ let link = if is_atom { atom_link(body) } else { rss_link(body) }?; ++ let mut source = if is_atom { ++ String::new() ++ } else { ++ element_text(body, "source").map(clean_text).unwrap_or_default() ++ }; ++ if style.split_source { ++ if let Some((head, tail)) = split_google_source(&title) { ++ title = head; ++ source = tail; ++ } ++ } ++ if style.digest { ++ if let Some((head, outlet)) = trailing_outlet(&title) { ++ title = head; ++ source = outlet; ++ } ++ } ++ let mut summary = element_text(body, "description") ++ .or_else(|| element_text(body, "summary")) ++ .or_else(|| element_text(body, "content")) ++ .or_else(|| element_text(body, "content:encoded")) ++ .map(|d| truncate(&clean_text(d), SUMMARY_CHARS)) ++ .unwrap_or_default(); ++ // A description of `Outlet: headline` (a digest's): the outlet is the ++ // row's publisher when nothing named one, and a description that is ++ // only the headline again is no summary. ++ if let Some((outlet, rest)) = outlet_prefix(&title, &summary) { ++ if source.is_empty() { ++ source = outlet; ++ } ++ summary = rest; ++ } ++ let published = element_text(body, "pubDate") ++ .and_then(|d| parse_rfc822(d.trim())) ++ .or_else(|| element_text(body, "published").and_then(|d| parse_rfc3339(d.trim()))) ++ .or_else(|| element_text(body, "updated").and_then(|d| parse_rfc3339(d.trim()))) ++ .or_else(|| element_text(body, "dc:date").and_then(|d| parse_rfc3339(d.trim()))); ++ // The model stamps `source_id` when the rows land: one reader serves ++ // every feed. ++ Some(Headline { ++ title, ++ link, ++ source, ++ source_id: String::new(), ++ published, ++ points: None, ++ comments: None, ++ summary, ++ discussion: None, ++ image: image_url(body), ++ }) ++} ++ ++/// The story's picture, when the item carries one: a `` ++/// that is an image (its `medium` or `type` says so, or neither is given), ++/// a ``, an `` with an image type, else the ++/// first `` in the description or content. Only an http(s) URL ++/// counts, and it is asked for over https whatever the feed says: feeds ++/// still carry plain http picture links (TechMeme's), the app's own ++/// connections are held to transport security on Apple platforms, and a ++/// picture host without https costs the story its picture, not its row. ++fn image_url(body: &str) -> Option { ++ media_image(body, "media:content", true) ++ .or_else(|| media_image(body, "media:thumbnail", false)) ++ .or_else(|| media_image(body, "enclosure", true)) ++ .or_else(|| inline_image(body)) ++ .map(|url| match url.strip_prefix("http://") { ++ Some(rest) => format!("https://{rest}"), ++ None => url, ++ }) ++} ++ ++/// The `url` of the first `` element that is an image. `typed`: the ++/// element carries a `medium` or `type`, and it must say image when it ++/// does (an enclosure must say so; a media:content with neither is taken ++/// as one). Every such element is looked at, not just the first: a feed ++/// may list a video before its still. ++fn media_image(body: &str, tag: &str, typed: bool) -> Option { ++ let open = format!("<{tag}"); ++ let mut from = 0; ++ while let Some(pos) = body[from..].find(&open) { ++ let at = from + pos; ++ let after = &body[at + open.len()..]; ++ let next = after.chars().next()?; ++ from = at + open.len(); ++ if next != '>' && next != '/' && !next.is_whitespace() { ++ continue; ++ } ++ let tag_end = after.find('>')?; ++ let attrs = &after[..tag_end]; ++ if typed { ++ let medium = attribute(attrs, "medium"); ++ let kind = attribute(attrs, "type"); ++ let says_image = medium.as_deref() == Some("image") || kind.as_deref().is_some_and(|t| t.starts_with("image/")); ++ let says_nothing = medium.is_none() && kind.is_none() && tag == "media:content"; ++ if !says_image && !says_nothing { ++ continue; ++ } ++ } ++ if let Some(url) = attribute(attrs, "url").map(|u| decode_entities(&u)) { ++ if is_http_url(&url) { ++ return Some(url); ++ } ++ } ++ } ++ None ++} ++ ++/// The first `` in the item's HTML description or content that is ++/// not a tiny one (a permalink icon, a tracking pixel: anything whose ++/// `width` or `height` says under `MIN_IMAGE_PX`). Tags and attributes in ++/// any case, values quoted or not. ++fn inline_image(body: &str) -> Option { ++ let html = element_text(body, "description") ++ .or_else(|| element_text(body, "content:encoded")) ++ .or_else(|| element_text(body, "content"))?; ++ // CDATA markers dropped and entities decoded: feeds double-encode HTML. ++ let html = decode_entities(&html.replace("", "")); ++ // ASCII lowercasing keeps every byte offset, so positions found in the ++ // lowered copy index the original. ++ let lower = html.to_ascii_lowercase(); ++ let mut from = 0; ++ while let Some(pos) = lower[from..].find("') else { break }; ++ let attrs = &html[at..at + tag_end]; ++ from = at + tag_end; ++ if is_tiny(attrs) { ++ continue; ++ } ++ if let Some(src) = attribute_ci(attrs, "src").map(|u| decode_entities(&u)) { ++ if is_http_url(&src) { ++ return Some(src); ++ } ++ } ++ } ++ None ++} ++ ++/// Under this many pixels on a side an image is an icon, not a picture. ++const MIN_IMAGE_PX: f64 = 50.0; ++ ++/// Whether an ``'s `width` or `height` attribute says it is an icon. ++fn is_tiny(attrs: &str) -> bool { ++ ["width", "height"].iter().any(|key| { ++ attribute_ci(attrs, key) ++ .and_then(|v| v.trim().trim_end_matches("px").trim().parse::().ok()) ++ .is_some_and(|px| px < MIN_IMAGE_PX) ++ }) ++} ++ ++/// `attribute` for HTML: the name in any case, the value quoted or bare ++/// (ending at whitespace). ++fn attribute_ci(attrs: &str, name: &str) -> Option { ++ let lower = attrs.to_ascii_lowercase(); ++ let key = format!("{}=", name.to_ascii_lowercase()); ++ let mut from = 0; ++ while let Some(pos) = lower[from..].find(&key) { ++ let at = from + pos; ++ let before_ok = at == 0 || lower[..at].ends_with(|c: char| c.is_whitespace()); ++ from = at + key.len(); ++ if !before_ok { ++ continue; ++ } ++ let rest = &attrs[at + key.len()..]; ++ let value = match rest.chars().next()? { ++ quote @ ('"' | '\'') => { ++ let value = &rest[1..]; ++ &value[..value.find(quote)?] ++ } ++ _ => rest.split(|c: char| c.is_whitespace()).next().unwrap_or(""), ++ }; ++ return Some(value.to_string()); ++ } ++ None ++} ++ ++/// `Outlet: headline …` in a description whose remainder opens with the ++/// item's own title: the outlet, and what is left after the headline ++/// (empty when the description was the headline alone). None when the ++/// description does not have that shape. ++fn outlet_prefix(title: &str, summary: &str) -> Option<(String, String)> { ++ // The colon alone: a stripped `
` may have left no space after it. ++ let (outlet, rest) = summary.split_once(':')?; ++ let outlet = outlet.trim(); ++ let rest = rest.trim_start(); ++ if !looks_like_name(outlet) { ++ return None; ++ } ++ let title = title.trim().trim_end_matches('…').trim(); ++ let head: String = title.chars().take(TITLE_MATCH_CHARS).collect(); ++ if head.is_empty() || !rest.to_lowercase().starts_with(&head.to_lowercase()) { ++ return None; ++ } ++ let rest = rest.trim(); ++ // What follows the headline, compared character by character (a ++ // lowercase form can differ in length from its original, so byte ++ // offsets of one never index the other). ++ let remainder = strip_prefix_ci(rest, title).unwrap_or("").trim(); ++ let remainder = remainder.trim_start_matches(['—', '-', '·', ':', ',']).trim(); ++ Some((outlet.to_string(), remainder.to_string())) ++} ++ ++/// `text` after `prefix`, matched case-insensitively character by ++/// character; None when `text` does not start with it. ++fn strip_prefix_ci<'a>(text: &'a str, prefix: &str) -> Option<&'a str> { ++ let mut rest = text; ++ for want in prefix.chars() { ++ let got = rest.chars().next()?; ++ if !got.to_lowercase().eq(want.to_lowercase()) { ++ return None; ++ } ++ rest = &rest[got.len_utf8()..]; ++ } ++ Some(rest) ++} ++ ++/// A digest title's trailing ` (Author/Outlet)` or ` (Outlet)`: the title ++/// without it, and the outlet (the last `/`-separated part). None when the ++/// title does not end that way, or what is in the parentheses is too long ++/// to be a name. ++fn trailing_outlet(title: &str) -> Option<(String, String)> { ++ let title = title.trim(); ++ let inner_end = title.strip_suffix(')')?; ++ let open = inner_end.rfind('(')?; ++ let inner = inner_end[open + 1..].trim(); ++ let head = inner_end[..open].trim_end(); ++ if inner.is_empty() || head.is_empty() || inner.chars().count() > OUTLET_CHARS || inner.contains('(') { ++ return None; ++ } ++ let outlet = inner.rsplit('/').next().unwrap_or(inner).trim(); ++ if outlet.is_empty() { ++ return None; ++ } ++ Some((head.to_string(), outlet.to_string())) ++} ++ ++/// Whether `text` reads as an outlet's name rather than the start of a ++/// sentence: a few words, each capitalised or a number (`Financial Times`, ++/// `9to5Mac`, `The Verge`), connectors aside; no sentence punctuation. ++fn looks_like_name(text: &str) -> bool { ++ let words: Vec<&str> = text.split_whitespace().collect(); ++ if words.is_empty() || text.chars().count() > OUTLET_CHARS || text.contains(['.', '!', '?']) { ++ return false; ++ } ++ words.iter().all(|word| { ++ matches!(*word, "of" | "the" | "and" | "de" | "for" | "&") ++ || word.chars().next().is_some_and(|c| c.is_uppercase() || c.is_ascii_digit()) ++ }) ++} ++ ++/// An outlet's name is a few words: anything longer is a sentence with a ++/// colon in it. ++const OUTLET_CHARS: usize = 40; ++/// How much of the headline the description must open with. ++const TITLE_MATCH_CHARS: usize = 24; ++ ++/// Google News titles end in ` - Source`; the last dash is the separator. ++fn split_google_source(title: &str) -> Option<(String, String)> { ++ let (head, tail) = title.rsplit_once(" - ")?; ++ let (head, tail) = (head.trim(), tail.trim()); ++ (!head.is_empty() && !tail.is_empty()).then(|| (head.to_string(), tail.to_string())) ++} ++ ++/// The first `...` in `xml` as its attribute text and raw ++/// inner text. A tag name must end at `>`, whitespace or `/`, so `title` ++/// never matches `titleX`. A self-closing element has empty inner text. ++fn element<'a>(xml: &'a str, tag: &str) -> Option<(&'a str, &'a str)> { ++ let open = format!("<{tag}"); ++ let close = format!(""); ++ let mut from = 0; ++ while let Some(pos) = xml[from..].find(&open) { ++ let at = from + pos; ++ let after = &xml[at + open.len()..]; ++ let next = after.chars().next()?; ++ if next != '>' && next != '/' && !next.is_whitespace() { ++ from = at + 1; ++ continue; ++ } ++ let tag_end = after.find('>')?; ++ let attrs = after[..tag_end].trim_end(); ++ if let Some(attrs) = attrs.strip_suffix('/') { ++ return Some((attrs, "")); ++ } ++ let content = &after[tag_end + 1..]; ++ let end = content.find(&close)?; ++ return Some((attrs, &content[..end])); ++ } ++ None ++} ++ ++/// The raw inner text of the first `` in `xml`. ++fn element_text<'a>(xml: &'a str, tag: &str) -> Option<&'a str> { ++ element(xml, tag).map(|(_, text)| text) ++} ++ ++/// An RSS item's `` when it is an http(s) URL, else its `` when ++/// that is one and not marked `isPermaLink="false"`. ++fn rss_link(body: &str) -> Option { ++ element_text(body, "link") ++ .map(clean_text) ++ .filter(|l| is_http_url(l)) ++ .or_else(|| permalink_guid(body)) ++} ++ ++fn permalink_guid(body: &str) -> Option { ++ let (attrs, text) = element(body, "guid")?; ++ if attribute(attrs, "isPermaLink").is_some_and(|v| v.eq_ignore_ascii_case("false")) { ++ return None; ++ } ++ Some(clean_text(text)).filter(|g| is_http_url(g)) ++} ++ ++/// Atom links are ``; the article is the one ++/// without `rel` or with `rel="alternate"`. ++fn atom_link(xml: &str) -> Option { ++ let mut from = 0; ++ while let Some(pos) = xml[from..].find("')?; ++ let attrs = &after[..tag_end]; ++ // Past the whole tag: restarting inside it would rescan to its `>` ++ // once per ` Option { ++ let key = format!("{name}="); ++ let mut from = 0; ++ while let Some(pos) = attrs[from..].find(&key) { ++ let at = from + pos; ++ let before_ok = at == 0 || attrs[..at].ends_with(|c: char| c.is_whitespace()); ++ let rest = &attrs[at + key.len()..]; ++ from = at + key.len(); ++ if !before_ok { ++ continue; ++ } ++ let quote = rest.chars().next()?; ++ if quote != '"' && quote != '\'' { ++ continue; ++ } ++ let value = &rest[1..]; ++ let end = value.find(quote)?; ++ return Some(value[..end].to_string()); ++ } ++ None ++} ++ ++/// CDATA markers dropped (they can sit anywhere in a description), entities ++/// decoded, HTML tags stripped, entities decoded again (feeds double-encode ++/// HTML in descriptions), whitespace collapsed. ++pub(crate) fn clean_text(raw: &str) -> String { ++ let text = raw.replace("", ""); ++ let text = decode_entities(text.trim()); ++ let text = strip_tags(&text); ++ let text = decode_entities(&text); ++ text.split_whitespace().collect::>().join(" ") ++} ++ ++/// What an angle-bracketed run turns into once stripped. ++enum TagKind { ++ /// Wraps words inside a sentence: dropped, so `Example:` keeps ++ /// its colon attached. ++ Inline, ++ /// Separates words or lines: a space, so `line
break` never fuses. ++ Block, ++ /// Not an HTML tag at all: a title may well say `<3` or `x < y`. ++ Text, ++} ++ ++const INLINE_TAGS: &[&str] = &[ ++ "a", "abbr", "b", "big", "cite", "code", "del", "em", "font", "i", "ins", "mark", "q", "s", "small", "span", ++ "strike", "strong", "sub", "sup", "tt", "u", ++]; ++ ++const BLOCK_TAGS: &[&str] = &[ ++ "article", "aside", "audio", "blockquote", "br", "center", "dd", "div", "dl", "dt", "figcaption", "figure", ++ "footer", "h1", "h2", "h3", "h4", "h5", "h6", "header", "hr", "iframe", "img", "li", "nav", "ol", "p", "picture", ++ "pre", "section", "source", "table", "tbody", "td", "th", "thead", "tr", "ul", "video", ++]; ++ ++fn tag_kind(tag: &str) -> TagKind { ++ // Comments and processing instructions are markup too. ++ if tag.starts_with('!') || tag.starts_with('?') { ++ return TagKind::Block; ++ } ++ let name = tag ++ .trim_start_matches('/') ++ .split(|c: char| c.is_whitespace() || c == '/') ++ .next() ++ .unwrap_or("") ++ .to_ascii_lowercase(); ++ if INLINE_TAGS.contains(&name.as_str()) { ++ TagKind::Inline ++ } else if BLOCK_TAGS.contains(&name.as_str()) { ++ TagKind::Block ++ } else { ++ TagKind::Text ++ } ++} ++ ++/// Remove known HTML tags (see `TagKind`); an unclosed `<` is text. ++fn strip_tags(text: &str) -> String { ++ let mut out = String::with_capacity(text.len()); ++ let mut rest = text; ++ while let Some(open) = rest.find('<') { ++ out.push_str(&rest[..open]); ++ let after = &rest[open..]; ++ let Some(close) = after.find('>') else { ++ rest = after; ++ break; ++ }; ++ match tag_kind(&after[1..close]) { ++ TagKind::Inline => {} ++ TagKind::Block => out.push(' '), ++ TagKind::Text => out.push_str(&after[..=close]), ++ } ++ rest = &after[close + 1..]; ++ } ++ out.push_str(rest); ++ out ++} ++ ++/// The scan for an entity's `;` looks at `&` and the ten bytes after it, ++/// so an entity is at most nine characters between `&` and `;`; the ++/// longest wanted, `􏿿`, has eight. ++const MAX_ENTITY_LEN: usize = 10; ++ ++/// The named entities feeds actually use, plus numeric ones. A numeric ++/// entity for a control character (other than tab, newline and carriage ++/// return) stays as written, so no feed can smuggle one into a label. ++pub(crate) fn decode_entities(text: &str) -> String { ++ let mut out = String::with_capacity(text.len()); ++ let mut rest = text; ++ while let Some(pos) = rest.find('&') { ++ out.push_str(&rest[..pos]); ++ let after = &rest[pos..]; ++ // Only look as far as an entity can reach, so a wall of `&` stays linear. ++ let Some(end) = after.bytes().take(MAX_ENTITY_LEN + 1).position(|b| b == b';') else { ++ out.push('&'); ++ rest = &after[1..]; ++ continue; ++ }; ++ let entity = &after[1..end]; ++ let decoded = match entity { ++ "amp" => Some('&'), ++ "lt" => Some('<'), ++ "gt" => Some('>'), ++ "quot" => Some('"'), ++ "apos" => Some('\''), ++ "nbsp" => Some(' '), ++ "mdash" => Some('—'), ++ "ndash" => Some('–'), ++ "hellip" => Some('…'), ++ "lsquo" => Some('‘'), ++ "rsquo" => Some('’'), ++ "ldquo" => Some('“'), ++ "rdquo" => Some('”'), ++ _ => numeric_entity(entity), ++ }; ++ match decoded { ++ Some(c) => { ++ out.push(c); ++ rest = &after[end + 1..]; ++ } ++ None => { ++ out.push('&'); ++ rest = &after[1..]; ++ } ++ } ++ } ++ out.push_str(rest); ++ out ++} ++ ++/// `#65` or `#x41`, digits only (no sign), and never a control character ++/// other than tab, newline or carriage return. ++fn numeric_entity(entity: &str) -> Option { ++ let number = entity.strip_prefix('#')?; ++ let code = match number.strip_prefix(['x', 'X']) { ++ Some(hex) if !hex.is_empty() && hex.bytes().all(|b| b.is_ascii_hexdigit()) => u32::from_str_radix(hex, 16).ok()?, ++ Some(_) => return None, ++ None if !number.is_empty() && number.bytes().all(|b| b.is_ascii_digit()) => number.parse().ok()?, ++ None => return None, ++ }; ++ char::from_u32(code).filter(|c| !c.is_control() || matches!(c, '\n' | '\t' | '\r')) ++} ++ ++/// At most `max` characters: cut to `max - 1`, a trailing space dropped, an ++/// ellipsis appended. ++pub(crate) fn truncate(text: &str, max: usize) -> String { ++ if text.chars().count() <= max { ++ return text.to_string(); ++ } ++ let cut: String = text.chars().take(max.saturating_sub(1)).collect(); ++ format!("{}…", cut.trim_end()) ++} ++ ++const MONTHS: [&str; 12] = ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"]; ++ ++/// `Wed, 16 Sep 2026 08:15:01 -0400`, the weekday and seconds optional, ++/// the zone `GMT`/`UT`/`UTC`/`Z`, `±HHMM`, or a US abbreviation. ++pub(crate) fn parse_rfc822(text: &str) -> Option { ++ let mut parts: Vec<&str> = text.split_whitespace().collect(); ++ if parts.first().is_some_and(|p| p.ends_with(',')) { ++ parts.remove(0); ++ } ++ if parts.len() < 4 { ++ return None; ++ } ++ let day: i64 = parts[0].parse().ok()?; ++ let month_name = parts[1].to_lowercase(); ++ let month = MONTHS.iter().position(|m| month_name.starts_with(m))? as i64 + 1; ++ let year: i64 = parts[2].parse().ok()?; ++ let year = if year < 100 { year + 2000 } else { year }; ++ let clock = hms(parts[3])?; ++ let offset = parts.get(4).map(|z| zone_offset(z)).unwrap_or(0); ++ unix_seconds(year, month, day, clock, offset) ++} ++ ++/// `2026-09-16T08:30:00+02:00`, `...Z`, fractional seconds allowed, a ++/// missing zone read as UTC. ++pub(crate) fn parse_rfc3339(text: &str) -> Option { ++ let (date, time) = text.split_once(['T', 't', ' '])?; ++ let mut d = date.split('-'); ++ let year: i64 = d.next()?.parse().ok()?; ++ let month: i64 = d.next()?.parse().ok()?; ++ let day: i64 = d.next()?.parse().ok()?; ++ let zone_at = time.find(['Z', 'z', '+']).or_else(|| time.rfind('-')).unwrap_or(time.len()); ++ let (clock, zone) = time.split_at(zone_at); ++ let clock = hms(clock.split('.').next()?)?; ++ unix_seconds(year, month, day, clock, zone_offset(zone)) ++} ++ ++/// Unix seconds for a civil date and clock in a zone `offset` seconds east ++/// of UTC; None when a date field is out of range. A day past its month's ++/// end is let through, as a feed's own clock would have it. ++fn unix_seconds(year: i64, month: i64, day: i64, (h, m, s): (i64, i64, i64), offset: i64) -> Option { ++ if !(1..=9999).contains(&year) || !(1..=12).contains(&month) || !(1..=31).contains(&day) { ++ return None; ++ } ++ Some(days_from_civil(year, month, day) * 86_400 + h * 3600 + m * 60 + s - offset) ++} ++ ++/// `HH:MM` or `HH:MM:SS`, each field in range (a leap second allowed). ++fn hms(text: &str) -> Option<(i64, i64, i64)> { ++ let mut p = text.split(':'); ++ let h: i64 = p.next()?.parse().ok()?; ++ let m: i64 = p.next()?.parse().ok()?; ++ let s: i64 = match p.next() { ++ Some(s) => s.parse().ok()?, ++ None => 0, ++ }; ++ ((0..=23).contains(&h) && (0..=59).contains(&m) && (0..=60).contains(&s)).then_some((h, m, s)) ++} ++ ++/// Seconds east of UTC for a zone token. Zone names other than the UTC ++/// spellings and the US abbreviations (CET, BST, AEST, ...) are read as ++/// UTC: a time off by a few hours beats no time at all. ++fn zone_offset(zone: &str) -> i64 { ++ match zone.to_uppercase().as_str() { ++ "GMT" | "UT" | "UTC" | "Z" | "" => 0, ++ "EST" => -5 * 3600, ++ "EDT" => -4 * 3600, ++ "CST" => -6 * 3600, ++ "CDT" => -5 * 3600, ++ "MST" => -7 * 3600, ++ "MDT" => -6 * 3600, ++ "PST" => -8 * 3600, ++ "PDT" => -7 * 3600, ++ z => { ++ let sign = if z.starts_with('-') { -1 } else { 1 }; ++ let digits: String = z.chars().filter(|c| c.is_ascii_digit()).collect(); ++ if digits.len() < 4 { ++ return 0; ++ } ++ let hh: i64 = digits[..2].parse().unwrap_or(0); ++ let mm: i64 = digits[2..4].parse().unwrap_or(0); ++ sign * (hh * 3600 + mm * 60) ++ } ++ } ++} ++ ++/// Days since 1970-01-01 for a proleptic Gregorian date. ++pub(crate) fn days_from_civil(y: i64, m: i64, d: i64) -> i64 { ++ let y = if m <= 2 { y - 1 } else { y }; ++ let era = (if y >= 0 { y } else { y - 399 }) / 400; ++ let yoe = y - era * 400; ++ let mp = if m > 2 { m - 3 } else { m + 9 }; ++ let doy = (153 * mp + 2) / 5 + d - 1; ++ let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; ++ era * 146_097 + doe - 719_468 ++} ++ ++ ++#[cfg(test)] ++mod tests { ++ use super::*; ++ ++ const RSS: &str = r#"T ++ <![CDATA[Rust 2.0 & you]]>https://example.com/a ++ <p>Big <b>news</b></p> ++ Tue, 10 Jun 2025 12:00:00 GMT ++ ++ Second - The Outlethttps://example.com/b ++ https://example.com/c ++ "#; ++ ++ const ATOM: &str = r#"Atom post ++ 2025-06-10T12:00:00Z ++ Short"#; ++ ++ #[test] ++ fn rss_items_become_rows_with_cleaned_text_times_and_https_images() { ++ let rows = parse_styled(RSS, Style::default()).unwrap(); ++ assert_eq!(rows.len(), 2, "an untitled item is skipped"); ++ assert_eq!(rows[0].title, "Rust 2.0 & you"); ++ assert_eq!(rows[0].summary, "Big news"); ++ assert_eq!(rows[0].published, Some(1_749_556_800)); ++ assert_eq!(rows[0].image.as_deref(), Some("https://img.example.com/a.jpg")); ++ assert_eq!(rows[1].title, "Second - The Outlet", "plain style keeps the title whole"); ++ } ++ ++ #[test] ++ fn google_style_takes_the_outlet_off_the_title() { ++ let rows = parse_styled(RSS, Style { split_source: true, digest: false }).unwrap(); ++ assert_eq!(rows[1].title, "Second"); ++ assert_eq!(rows[1].source, "The Outlet"); ++ } ++ ++ #[test] ++ fn atom_entries_parse_and_non_feeds_are_refused() { ++ let rows = parse_styled(ATOM, Style::default()).unwrap(); ++ assert_eq!(rows[0].link, "https://example.org/p"); ++ assert_eq!(rows[0].published, Some(1_749_556_800)); ++ assert!(parse_styled("nope", Style::default()).is_err()); ++ } ++ ++ #[test] ++ fn rows_serialize_to_json_the_script_reader_accepts() { ++ let rows = parse_styled(RSS, Style::default()).unwrap(); ++ let json = rows_json(&rows); ++ assert!(json.starts_with("[{\"title\":\"Rust 2.0 & you\""), "{json}"); ++ let quoted = rows_json(&[Headline { title: "a \"q\"\n".into(), ..Default::default() }]); ++ assert!(quoted.contains("\"a \\\"q\\\"\\n\""), "{quoted}"); ++ } ++} +diff --git a/platform/script/src/vm.rs b/platform/script/src/vm.rs +index c19d2512c..c579c89c3 100644 +--- a/platform/script/src/vm.rs ++++ b/platform/script/src/vm.rs +@@ -3,6 +3,7 @@ + use crate::makepad_error_log::*; + use crate::makepad_live_id::*; + use crate::mod_gc::*; ++use crate::mod_feed::*; + use crate::mod_html::*; + use crate::mod_math::*; + use crate::mod_pod::*; +@@ -1787,6 +1788,7 @@ pub fn new() -> Self { + define_std_module(&mut heap, &mut native); + define_regex_module(&mut heap, &mut native); + define_html_module(&mut heap, &mut native); ++ define_feed_module(&mut heap, &mut native); + define_shader_module(&mut heap, &mut native); + define_gc_module(&mut heap, &mut native); + let pod_builtins = define_pod_module(&mut heap, &mut native); +diff --git a/platform/script/std/src/gate.rs b/platform/script/std/src/gate.rs +index 9625a12cc..f74915c23 100644 +--- a/platform/script/std/src/gate.rs ++++ b/platform/script/std/src/gate.rs +@@ -1,10 +1,11 @@ + //! The one question every way out of an isolate asks: may THIS heap reach + //! THIS URL? + //! +-//! Three paths issue requests on a script's behalf — `net.http_request`, +-//! `res.http_resource` (artwork) and the data fetches behind `sys.*` — and +-//! a network grant that covers only the first is not a grant. All three ask +-//! here. The answer comes from a gate the embedding host installs; with no ++//! Four paths issue requests on a script's behalf — `net.http_request`, ++//! `net.web_socket`, `res.http_resource` (artwork) and the data fetches ++//! behind `sys.*` — and a network grant that covers only the first is not a ++//! grant. All of them ask here. Listening servers (`net.http_server`) and raw ++//! streams (`net.socket_stream`) name no URL, so they ask the socket gate. The answer comes from a gate the embedding host installs; with no + //! gate installed, everything is allowed, which is the behaviour every + //! existing host had. A host that installs a gate answers per heap, so an + //! app's allowlist follows its isolate and nothing else. +@@ -16,6 +17,45 @@ + + thread_local! { + static URL_GATE: Cell> = const { Cell::new(None) }; ++ static MEDIA_GATE: Cell> = const { Cell::new(None) }; ++ static SOCKET_GATE: Cell> = const { Cell::new(None) }; ++} ++ ++/// `heap_key -> allowed`: may this heap open what a host list cannot ++/// describe — a listening server (`net.http_server`) or a raw TCP/TLS stream ++/// (`net.socket_stream`)? A server answers whoever connects, and a raw ++/// stream speaks any protocol to any port, so neither fits a per-URL gate. ++pub type ScriptSocketGate = fn(usize) -> bool; ++ ++/// Install (or clear) the gate for listening servers and raw sockets. With ++/// no socket gate, both are allowed, as they were for every existing host. ++pub fn set_script_socket_gate(gate: Option) { ++ SOCKET_GATE.with(|g| g.set(gate)); ++} ++ ++/// Whether `heap_key` may listen or open a raw socket. True when no gate is ++/// installed. ++pub fn script_sockets_allowed(heap_key: usize) -> bool { ++ SOCKET_GATE.with(|g| match g.get() { ++ Some(gate) => gate(heap_key), ++ None => true, ++ }) ++} ++ ++/// Install (or clear) the gate for media loads (`http_resource`: images and ++/// other artwork). A host can grant an app artwork from anywhere without ++/// granting it requests to anywhere. With no media gate, media answers to ++/// the URL gate like every other path. ++pub fn set_script_media_gate(gate: Option) { ++ MEDIA_GATE.with(|g| g.set(gate)); ++} ++ ++/// Whether `heap_key` may load media from `url`. ++pub fn script_media_url_allowed(heap_key: usize, url: &str) -> bool { ++ match MEDIA_GATE.with(|g| g.get()) { ++ Some(gate) => gate(heap_key, url), ++ None => script_url_allowed(heap_key, url), ++ } + } + + /// Install (or clear) the gate for this thread. Isolates are UI-thread-owned, +@@ -102,4 +142,34 @@ fn only_heap_seven(heap: usize, _url: &str) -> bool { + assert!(!script_url_allowed(8, "https://anything.example")); + set_script_url_gate(None); + } ++ ++ #[test] ++ fn media_falls_back_to_the_url_gate_until_it_has_its_own() { ++ fn only_heap_seven(heap: usize, _url: &str) -> bool { ++ heap == 7 ++ } ++ fn every_heap(_heap: usize, _url: &str) -> bool { ++ true ++ } ++ set_script_url_gate(Some(only_heap_seven)); ++ assert!(!script_media_url_allowed(8, "https://cdn.example/a.jpg")); ++ set_script_media_gate(Some(every_heap)); ++ assert!(script_media_url_allowed(8, "https://cdn.example/a.jpg")); ++ assert!(!script_url_allowed(8, "https://cdn.example/a.jpg"), "requests keep their own gate"); ++ set_script_media_gate(None); ++ set_script_url_gate(None); ++ } ++ ++ #[test] ++ fn no_socket_gate_allows_sockets_and_a_gate_decides() { ++ set_script_socket_gate(None); ++ assert!(script_sockets_allowed(8)); ++ fn only_heap_seven(heap: usize) -> bool { ++ heap == 7 ++ } ++ set_script_socket_gate(Some(only_heap_seven)); ++ assert!(script_sockets_allowed(7)); ++ assert!(!script_sockets_allowed(8)); ++ set_script_socket_gate(None); ++ } + } +diff --git a/platform/script/std/src/net.rs b/platform/script/std/src/net.rs +index ba16994ca..7814d6f8e 100644 +--- a/platform/script/std/src/net.rs ++++ b/platform/script/std/src/net.rs +@@ -14,6 +14,11 @@ + use std::sync::mpsc::channel; + use std::time::Duration; + ++ ++/// The response body cap for a script request that names none: an isolate ++/// may not buffer an unbounded body, and 16 MiB is past any page or feed. ++pub const SCRIPT_DEFAULT_BODY_LIMIT: u64 = 16 * 1024 * 1024; ++ + pub struct ScriptWebSocket { + #[allow(unused)] + pub id: LiveId, +@@ -786,7 +791,19 @@ pub fn script_mod(vm: &mut ScriptVm) { + return script_err_type_mismatch!(vm.trap(), "invalid net arg type"); + } + ++ // A server answers whoever connects; no host list describes that. ++ let heap_key = vm.bx.heap.heap_key(); ++ if !crate::gate::script_sockets_allowed(heap_key) { ++ return script_err_io!(vm.trap(), "this app may not open a listening server"); ++ } ++ if vm.std_mut::().net.is_none() { ++ return script_err_io!(vm.trap(), "script net runtime is not configured"); ++ } ++ + let options = HttpServerOptions::script_from_value(vm, options); ++ let Ok(listen_address) = options.listen.parse() else { ++ return script_err_invalid_args!(vm.trap(), "invalid listen address {}", options.listen); ++ }; + let events = HttpServerEvents::script_from_value(vm, events); + + let (server_tx, server_rx) = channel(); +@@ -800,7 +817,7 @@ pub fn script_mod(vm: &mut ScriptVm) { + }); + + let server = HttpServer { +- listen_address: options.listen.parse().unwrap(), ++ listen_address, + post_max_size: 1024 * 1024 * 10, + post_max_size_overrides: Vec::new(), + pre_admit_posts: false, +@@ -838,7 +855,12 @@ pub fn script_mod(vm: &mut ScriptVm) { + { + return script_err_type_mismatch!(vm.trap(), "invalid net arg type"); + } +- let request = HttpRequest::script_from_value(vm, request); ++ let mut request = HttpRequest::script_from_value(vm, request); ++ // A script that never names a limit reads back 0, which refused ++ // every body with "response body exceeds configured limit". ++ if request.max_response_body_bytes == 0 { ++ request.max_response_body_bytes = SCRIPT_DEFAULT_BODY_LIMIT; ++ } + let events = HttpEvents::script_from_value(vm, events); + // The host's per-isolate allowlist, when one is installed: a + // network grant that only opened this module would otherwise +@@ -893,12 +915,12 @@ pub fn script_mod(vm: &mut ScriptVm) { + } + if prop == id!(error) { + if let Some(error) = error { +- return vm.new_string_with(|_vm, out| out.push_str(&error)).into(); ++ return vm.bx.heap.new_string_from_str(&error).into(); + } + return NIL; + } + if prop == id!(host) { +- return vm.new_string_with(|_vm, out| out.push_str(&host)).into(); ++ return vm.bx.heap.new_string_from_str(&host).into(); + } + script_err_not_found!(vm.trap(), "invalid socket_stream prop") + }); +@@ -1071,7 +1093,7 @@ pub fn script_mod(vm: &mut ScriptVm) { + match socket_stream_poll(vm, handle) { + SocketStreamPoll::Data(data) => { + let string = String::from_utf8_lossy(&data); +- vm.new_string_with(|_vm, out| out.push_str(&string)).into() ++ vm.bx.heap.new_string_from_str(&string).into() + } + SocketStreamPoll::Closed(Some(err)) => script_err_io!(vm.trap(), "{err}"), + SocketStreamPoll::Closed(None) => NIL, +@@ -1116,6 +1138,12 @@ pub fn script_mod(vm: &mut ScriptVm) { + return script_err_type_mismatch!(vm.trap(), "invalid net arg type"); + } + let events = WebSocketEvents::script_from_value(vm, events); ++ // The same per-isolate allowlist as `http_request`: a socket is ++ // a request that stays open. ++ let heap_key = vm.bx.heap.heap_key(); ++ if !crate::gate::script_url_allowed(heap_key, &request.url) { ++ return script_err_io!(vm.trap(), "this app may not reach {}", request.url); ++ } + + let std = vm.std_mut::(); + let Some(runtime) = std.net.as_ref() else { +@@ -1143,6 +1171,12 @@ pub fn script_mod(vm: &mut ScriptVm) { + if !script_has_proto!(vm, options, net.SocketStreamOptions) { + return script_err_type_mismatch!(vm.trap(), "invalid socket_stream arg type"); + } ++ // A raw stream speaks any protocol to any port, so a host list ++ // cannot bound it: a policed isolate is refused outright. ++ let heap_key = vm.bx.heap.heap_key(); ++ if !crate::gate::script_sockets_allowed(heap_key) { ++ return script_err_io!(vm.trap(), "this app may not open a raw socket"); ++ } + // Raw sockets obey the same gate as http_request/web_socket, so a sandboxed + // VM without a net runtime can't open TCP connections. + if vm.std_mut::().net.is_none() { +diff --git a/platform/src/event/finger.rs b/platform/src/event/finger.rs +index 0fb1eabaf..bc7c319cd 100644 +--- a/platform/src/event/finger.rs ++++ b/platform/src/event/finger.rs +@@ -493,6 +493,13 @@ pub fn is_digit_captured_elsewhere(&self, digit_id: DigitId, area: Area) -> bool + self.captures.iter().any(|v| v.digit_id == digit_id && v.area != area) + } + ++ /// Every area that captured this digit. A container that only wants to ++ /// yield to its own children (a Button inside it), not to the scroll view ++ /// around it, compares these rects with its own. ++ pub fn digit_capture_areas(&self, digit_id: DigitId) -> Vec { ++ self.captures.iter().filter(|v| v.digit_id == digit_id).map(|v| v.area).collect() ++ } ++ + /// The area that captured the touch with the given uid, if any. + /// Lets a raw `Event::LongPress` handler check which widget owns the press. + pub fn touch_capture_area(&self, uid: u64) -> Option { +diff --git a/platform/src/script/res.rs b/platform/src/script/res.rs +index f94c296b2..42b9f4cfe 100644 +--- a/platform/src/script/res.rs ++++ b/platform/src/script/res.rs +@@ -1189,11 +1189,9 @@ pub fn script_mod(vm: &mut ScriptVm) { + _ if prop == id!(path) => { + let path = res.abs_path.clone(); + drop(resources); +- return vm +- .new_string_with(|_vm, s| { +- s.push_str(&path); +- }) +- .into(); ++ // Charged like any string: an isolate under a heap ++ // cap refuses the unbounded builder outright. ++ return vm.bx.heap.new_string_from_str(&path).into(); + } + _ if prop == id!(is_loaded) => { + return matches!(res.data, CxScriptResourceData::Loaded(_)).into() +@@ -1205,11 +1203,7 @@ pub fn script_mod(vm: &mut ScriptVm) { + if let CxScriptResourceData::Error(ref e) = res.data { + let err = e.clone(); + drop(resources); +- return vm +- .new_string_with(|_vm, s| { +- s.push_str(&err); +- }) +- .into(); ++ return vm.bx.heap.new_string_from_str(&err).into(); + } + return NIL; + } +@@ -1340,7 +1334,7 @@ pub fn script_mod(vm: &mut ScriptVm) { + let heap_key = vm.bx.heap.heap_key(); + // Artwork is a way out of the isolate too: a card with no + // network grant was seen fetching nine images through here. +- if !makepad_script_std::script_url_allowed(heap_key, &url_string) { ++ if !makepad_script_std::script_media_url_allowed(heap_key, &url_string) { + // Logged as well as raised: the widget that asked draws + // nothing, and a silent blank is the hardest bug to find. + crate::log!("Script resource refused by the host's allowlist: {url_string}"); +diff --git a/widgets/src/camera_preview.rs b/widgets/src/camera_preview.rs +new file mode 100644 +index 000000000..72bccb9fb +--- /dev/null ++++ b/widgets/src/camera_preview.rs +@@ -0,0 +1,637 @@ ++//! The device camera inside a script app: a live preview drawn natively, and ++//! capture and control from script. ++//! ++//! ```text ++//! cam := CameraPreview{width: Fill height: Fill facing: "back" ++//! on_capture: || show(ui.cam.last()) // app-relative path of the new file ++//! on_error: || say(ui.cam.error())} ++//! ui.cam.start() ui.cam.stop() ui.cam.switch() ui.cam.facing() ++//! ui.cam.capture() // a JPEG into the app's storage ++//! ui.cam.record_start() ui.cam.record_stop() // an MP4 ++//! ui.cam.set_zoom(2.0) ui.cam.set_flash("off"|"on"|"auto"|"torch") ++//! ui.cam.set_exposure(-1.0) ui.cam.focus(x, y) // x, y 0..1 in the preview ++//! ui.cam.set_aspect(16 / 9) // the viewfinder shape; reopens the camera ++//! ``` ++//! ++//! A tap on the preview focuses there and a pinch zooms, natively ++//! (`tap_to_focus`, `pinch_zoom`, both on by default); `on_zoom` hears the ++//! new ratio once a pinch ends. ++//! ++//! Frames never pass through script: the preview is a platform texture, and ++//! script hears one call per finished capture. What an app under a policy ++//! gets: `camera` to open it at all; captures land in its own storage jail; ++//! `microphone` adds sound to a recording; `library` also offers each capture ++//! to the system photo library. ++//! ++//! The camera is a device the person can see is on, so it is released as soon ++//! as it is not needed: when the preview has not drawn for a second (its tile ++//! left the screen), when the app closes ([`release_isolate_devices`], which ++//! the host calls), and when its isolate is collected. ++use crate::{ ++ makepad_derive_widget::*, ++ makepad_draw::*, ++ makepad_script::ScriptFnRef, ++ video::*, ++ view::View, ++ widget::*, ++ widget_async::{CxWidgetToScriptCallExt, ScriptAsyncResult}, ++}; ++use crate::makepad_draw::makepad_platform::permission::{Permission, PermissionStatus}; ++use crate::makepad_draw::makepad_platform::video::{ ++ CameraCaptureEvent, CameraCaptureRequest, CameraCaptureResult, CameraControl, CameraFlashMode, VideoFormat, ++ VideoFormatId, VideoInputId, VideoInputsEvent, VideoPixelFormat, ++}; ++use crate::gesture_view::PinchTracker; ++use std::collections::HashMap; ++ ++script_mod! { ++ use mod.prelude.widgets_internal.* ++ use mod.widgets.* ++ ++ mod.widgets.CameraPreviewBase = #(CameraPreview::register_widget(vm)) ++ mod.widgets.CameraPreview = set_type_default() do mod.widgets.CameraPreviewBase{ ++ width: Fill ++ height: Fill ++ flow: Overlay ++ preview := Video{width: Fill height: Fill autoplay: false show_controls: false} ++ } ++} ++ ++const WATCHDOG_SECONDS: f64 = 0.5; ++/// How long a camera may be without a permission answer before it opens ++/// anyway: a bare desktop binary never gets the prompt's completion, and the ++/// capture backend asks for access itself. ++const PERMISSION_GRACE_SECONDS: f64 = 2.0; ++ ++thread_local! { ++ /// heap key -> the preview videos that heap's cameras opened. ++ static OPEN: std::cell::RefCell>> = Default::default(); ++} ++ ++/// Stop every camera an isolate opened. Called from the isolate GC, and by a ++/// host when the app it runs closes, which can be long before the GC. ++pub fn release_cameras(cx: &mut Cx, heap_key: usize) { ++ let videos = OPEN.with(|o| o.borrow_mut().remove(&heap_key)).unwrap_or_default(); ++ for video in videos { ++ video.stop_and_cleanup_resources(cx); ++ } ++} ++ ++/// Everything a closing app holds that the person can see: its cameras and ++/// its web views. A host calls this when it closes an app. ++pub fn release_isolate_devices(cx: &mut Cx, heap_key: usize) { ++ release_cameras(cx, heap_key); ++ crate::web_reader::gc_web_readers(cx, &[heap_key]); ++} ++ ++#[derive(Clone, Debug)] ++struct CameraChoice { ++ input_id: VideoInputId, ++ front: bool, ++ formats: Vec, ++} ++ ++impl CameraChoice { ++ /// The best preview profile for a viewfinder aspect: nearest aspect, then ++ /// the most pixels within 1080p, then the better pixel format. ++ fn format_for(&self, aspect: f64) -> Option<(VideoFormatId, usize, usize)> { ++ let usable = |f: &&VideoFormat| { ++ matches!(f.pixel_format, VideoPixelFormat::NV12 | VideoPixelFormat::YUY2 | VideoPixelFormat::YUV420) ++ && f.width <= 1920 ++ && f.height <= 1080 ++ && f.height > 0 ++ }; ++ let pixel = |f: &VideoFormat| match f.pixel_format { ++ VideoPixelFormat::NV12 => 3, ++ VideoPixelFormat::YUY2 => 2, ++ VideoPixelFormat::YUV420 => 1, ++ _ => 0, ++ }; ++ let key = |f: &VideoFormat| { ++ let d = ((f.width as f64 / f.height as f64) - aspect).abs(); ++ ((d * 50.0).round() as i64, -((f.width * f.height) as i64), -pixel(f)) ++ }; ++ let best = self.formats.iter().filter(usable).min_by(|a, b| key(a).cmp(&key(b)))?; ++ Some((best.format_id, best.width, best.height)) ++ } ++} ++ ++/// Which camera a platform name describes: front when it says so, or the ++/// second of several that does not say it is the back one. ++fn is_front(name: &str, index: usize, count: usize) -> bool { ++ let name = name.to_lowercase(); ++ name.contains("front") || name.contains("facetime") || name.contains("user") || (count > 1 && index == 1 && !name.contains("back")) ++} ++ ++fn flash_mode(name: &str) -> Option { ++ match name { ++ "off" => Some(CameraFlashMode::Off), ++ "on" => Some(CameraFlashMode::On), ++ "auto" => Some(CameraFlashMode::Auto), ++ "torch" => Some(CameraFlashMode::Torch), ++ _ => None, ++ } ++} ++ ++#[derive(Clone, Copy, Debug, Default, PartialEq)] ++enum State { ++ #[default] ++ Idle, ++ Starting, ++ Running, ++ Stopping, ++} ++ ++#[derive(Script, ScriptHook, Widget)] ++pub struct CameraPreview { ++ #[deref] ++ view: View, ++ /// `"back"` or `"front"`. ++ #[live] ++ facing: String, ++ /// Width over height of the viewfinder: 4:3 for photos, 16:9 for video. ++ #[live(1.3333)] ++ aspect: f64, ++ #[live] ++ on_capture: ScriptFnRef, ++ #[live] ++ on_error: ScriptFnRef, ++ #[live] ++ on_zoom: ScriptFnRef, ++ #[live(true)] ++ tap_to_focus: bool, ++ #[live(true)] ++ pinch_zoom: bool, ++ ++ #[rust] ++ pinch: PinchTracker, ++ #[rust] ++ pinch_base: f32, ++ /// The app asked for a preview (`start()`), and has not stopped it. ++ #[rust] ++ wanted: bool, ++ #[rust] ++ state: State, ++ #[rust] ++ cameras: Vec, ++ #[rust] ++ input: Option, ++ #[rust] ++ open_front: bool, ++ #[rust] ++ open_aspect: f64, ++ #[rust] ++ permission: Option, ++ #[rust] ++ asked_at: f64, ++ #[rust] ++ timer: Option, ++ #[rust] ++ drawn: bool, ++ #[rust] ++ registered: bool, ++ #[rust] ++ recording: Option, ++ #[rust] ++ last: String, ++ #[rust] ++ last_error: String, ++ #[rust] ++ zoom: f32, ++} ++ ++impl CameraPreview { ++ fn heap_key(&self) -> usize { ++ self.view.source.heap_key() ++ } ++ ++ fn granted(&self, grant: &str) -> bool { ++ crate::splash_policy::service_allowed(self.heap_key(), grant).is_ok() ++ } ++ ++ fn fail(&mut self, cx: &mut Cx, why: String) { ++ log!("camera preview: {why}"); ++ self.last_error = why; ++ if self.on_error.as_object() != ScriptObject::ZERO { ++ cx.widget_to_script_call(self.widget_uid(), NIL, self.view.source.clone(), self.on_error.clone(), &[]); ++ } ++ } ++ ++ fn video(&self, cx: &Cx) -> VideoRef { ++ self.view.video(cx, ids!(preview)) ++ } ++ ++ fn start(&mut self, cx: &mut Cx) -> bool { ++ if !self.granted("camera.preview") { ++ self.fail(cx, "this app was not granted the camera".into()); ++ return false; ++ } ++ self.wanted = true; ++ if self.permission.is_none() { ++ self.asked_at = cx.seconds_since_app_start(); ++ cx.request_permission(Permission::Camera); ++ } ++ if self.cameras.is_empty() { ++ // The platform publishes its list once; a preview mounted later ++ // asks again. The frame callback opens the capture session. ++ cx.video_input(0, |_frame| {}); ++ cx.refresh_video_inputs(); ++ } ++ if self.timer.is_none() { ++ self.timer = Some(cx.start_interval(WATCHDOG_SECONDS)); ++ } ++ self.drawn = true; ++ self.drive(cx); ++ true ++ } ++ ++ fn stop(&mut self, cx: &mut Cx) { ++ self.wanted = false; ++ self.halt(cx); ++ if let Some(timer) = self.timer.take() { ++ cx.stop_timer(timer); ++ } ++ } ++ ++ /// Close the camera, keeping whether the app wants it: the watchdog ++ /// reopens it when the preview draws again. ++ fn halt(&mut self, cx: &mut Cx) { ++ if matches!(self.state, State::Starting | State::Running) { ++ if self.recording.take().is_some() { ++ if let Some(input) = self.input { ++ cx.camera_capture(input, CameraCaptureRequest::StopVideo); ++ } ++ } ++ self.state = State::Stopping; ++ self.video(cx).stop_and_cleanup_resources(cx); ++ } ++ } ++ ++ /// One step towards what the app wants, driven by the platform's events. ++ fn drive(&mut self, cx: &mut Cx) { ++ let want_front = self.facing == "front"; ++ match self.state { ++ State::Running if want_front != self.open_front || (self.aspect - self.open_aspect).abs() > 0.05 => { ++ if self.cameras.iter().any(|c| c.front == want_front) { ++ self.halt(cx); ++ } ++ } ++ State::Idle if self.wanted => { ++ match self.permission { ++ Some(PermissionStatus::Granted) => {} ++ Some(status) => { ++ self.wanted = false; ++ self.fail(cx, format!("camera permission {status:?}")); ++ return; ++ } ++ None if cx.seconds_since_app_start() - self.asked_at < PERMISSION_GRACE_SECONDS => return, ++ None => self.permission = Some(PermissionStatus::Granted), ++ } ++ let Some(choice) = self.cameras.iter().find(|c| c.front == want_front).or(self.cameras.first()).cloned() else { ++ return; ++ }; ++ let Some((format_id, _, _)) = choice.format_for(self.aspect) else { return }; ++ let video = self.video(cx); ++ video.set_camera_preview_mode(cx, VideoCameraPreviewMode::Texture); ++ video.set_source_camera(cx, choice.input_id, format_id); ++ video.begin_playback(cx); ++ if !self.registered { ++ self.registered = true; ++ OPEN.with(|o| o.borrow_mut().entry(self.heap_key()).or_default().push(video.clone())); ++ } ++ self.state = State::Starting; ++ self.input = Some(choice.input_id); ++ self.open_front = choice.front; ++ self.open_aspect = self.aspect; ++ } ++ _ => {} ++ } ++ } ++ ++ fn capture_path(&self, cx: &Cx, kind: &str, ext: &str) -> Option<(String, String)> { ++ let now = std::time::SystemTime::now() ++ .duration_since(std::time::UNIX_EPOCH) ++ .map(|d| d.as_millis()) ++ .unwrap_or(0); ++ let relative = format!("DCIM/{kind}_{now}.{ext}"); ++ // A policed app's captures are its own: they land in its jail. The ++ // host's own surfaces write next to the host's data. ++ let real = if crate::splash_policy::is_enforced(self.heap_key()) { ++ if !crate::splash_storage::heap_has_room(self.heap_key()) { ++ return None; ++ } ++ crate::splash_policy::local_path_for_heap(self.heap_key(), &relative)? ++ } else { ++ format!("{}/{relative}", cx.get_data_dir()?) ++ }; ++ if let Some(parent) = std::path::Path::new(&real).parent() { ++ let _ = std::fs::create_dir_all(parent); ++ } ++ Some((relative, real)) ++ } ++ ++ fn capture(&mut self, cx: &mut Cx) -> bool { ++ let (Some(input), State::Running) = (self.input, self.state) else { ++ self.fail(cx, "the camera is not running".into()); ++ return false; ++ }; ++ let Some((relative, real)) = self.capture_path(cx, "IMG", "jpg") else { ++ self.fail(cx, "this app's storage is full or missing".into()); ++ return false; ++ }; ++ self.last = relative; ++ let library = self.granted("library.write"); ++ cx.camera_capture(input, CameraCaptureRequest::Photo { path: real, library }); ++ true ++ } ++ ++ fn record_start(&mut self, cx: &mut Cx) -> bool { ++ let (Some(input), State::Running) = (self.input, self.state) else { ++ self.fail(cx, "the camera is not running".into()); ++ return false; ++ }; ++ if self.recording.is_some() { ++ return true; ++ } ++ let Some((relative, real)) = self.capture_path(cx, "VID", "mp4") else { ++ self.fail(cx, "this app's storage is full or missing".into()); ++ return false; ++ }; ++ let audio = self.granted("microphone.record"); ++ if audio { ++ cx.request_permission(Permission::AudioInput); ++ } ++ self.recording = Some(relative); ++ let library = self.granted("library.write"); ++ cx.camera_capture(input, CameraCaptureRequest::StartVideo { path: real, audio, library }); ++ true ++ } ++ ++ fn record_stop(&mut self, cx: &mut Cx) { ++ if let (Some(input), Some(_)) = (self.input, self.recording.as_ref()) { ++ cx.camera_capture(input, CameraCaptureRequest::StopVideo); ++ } ++ } ++ ++ fn control(&mut self, cx: &mut Cx, control: CameraControl) { ++ if let (Some(input), State::Running) = (self.input, self.state) { ++ cx.camera_control(input, control); ++ } ++ } ++ ++ fn on_capture(&mut self, cx: &mut Cx, result: &CameraCaptureResult) { ++ match result { ++ CameraCaptureResult::Photo { .. } => self.notify_capture(cx), ++ CameraCaptureResult::VideoStopped { .. } => { ++ if let Some(relative) = self.recording.take() { ++ self.last = relative; ++ } ++ self.notify_capture(cx); ++ } ++ CameraCaptureResult::Failed { what, error } => { ++ if what == "video" { ++ self.recording = None; ++ } ++ self.fail(cx, format!("{what} failed: {error}")); ++ } ++ _ => {} ++ } ++ } ++ ++ fn notify_capture(&mut self, cx: &mut Cx) { ++ if self.on_capture.as_object() != ScriptObject::ZERO { ++ cx.widget_to_script_call(self.widget_uid(), NIL, self.view.source.clone(), self.on_capture.clone(), &[]); ++ } ++ } ++} ++ ++impl CameraPreview { ++ fn handle_gestures(&mut self, cx: &mut Cx, event: &Event) { ++ let area = self.view.area(); ++ let rect = area.rect(cx); ++ if let (true, Event::TouchUpdate(touches)) = (self.pinch_zoom, event) { ++ use crate::gesture_view::PinchStep; ++ match self.pinch.update(touches, area.clipped_rect(cx)) { ++ PinchStep::Began(_) => self.pinch_base = if self.zoom > 0.0 { self.zoom } else { 1.0 }, ++ PinchStep::Moved(scale, _) => { ++ let zoom = (self.pinch_base * scale as f32).clamp(1.0, 10.0); ++ if (zoom - self.zoom).abs() >= 0.02 { ++ self.zoom = zoom; ++ self.control(cx, CameraControl::ZoomRatio(zoom)); ++ } ++ } ++ PinchStep::Ended => { ++ if self.on_zoom.as_object() != ScriptObject::ZERO { ++ cx.widget_to_script_call(self.widget_uid(), NIL, self.view.source.clone(), self.on_zoom.clone(), &[(self.zoom as f64).into()]); ++ } ++ } ++ PinchStep::Nothing => {} ++ } ++ } ++ if !self.tap_to_focus || rect.size.x <= 0.0 || rect.size.y <= 0.0 { ++ return; ++ } ++ // A tap the controls drawn over the preview did not claim. ++ if let Hit::FingerUp(e) = event.hits_with_capture_overload(cx, area, true) { ++ let child_claimed = cx.fingers.digit_capture_areas(e.digit_id).into_iter().any(|a| a != area && a.rect(cx).is_inside_of(rect) && a.rect(cx).size != rect.size); ++ if e.is_over && e.was_tap() && !self.pinch.pinching() && !child_claimed { ++ let x = (e.abs.x - rect.pos.x) / rect.size.x; ++ let y = (e.abs.y - rect.pos.y) / rect.size.y; ++ self.control(cx, CameraControl::FocusPoint { x: x.clamp(0.0, 1.0), y: y.clamp(0.0, 1.0) }); ++ } ++ } ++ } ++} ++ ++fn arg_number(vm: &mut ScriptVm, args: ScriptValue, index: usize) -> Option { ++ let args_obj = args.as_object()?; ++ let trap = vm.bx.threads.cur().trap.pass(); ++ vm.bx.heap.vec_value(args_obj, index, trap).as_number() ++} ++ ++fn arg_string(vm: &mut ScriptVm, args: ScriptValue, index: usize) -> Option { ++ let args_obj = args.as_object()?; ++ let trap = vm.bx.threads.cur().trap.pass(); ++ let value = vm.bx.heap.vec_value(args_obj, index, trap); ++ vm.bx.heap.cast_to_owned_string(value, "copying a camera argument") ++} ++ ++impl Widget for CameraPreview { ++ fn script_call(&mut self, vm: &mut ScriptVm, method: LiveId, args: ScriptValue) -> ScriptAsyncResult { ++ let ret = |v: ScriptValue| ScriptAsyncResult::Return(v); ++ match method { ++ m if m == live_id!(start) => ret(vm.with_cx_mut(|cx| self.start(cx)).into()), ++ m if m == live_id!(stop) => { ++ vm.with_cx_mut(|cx| self.stop(cx)); ++ ret(NIL) ++ } ++ m if m == live_id!(switch) => { ++ self.facing = if self.facing == "front" { "back".into() } else { "front".into() }; ++ vm.with_cx_mut(|cx| self.drive(cx)); ++ ret(NIL) ++ } ++ m if m == live_id!(set_facing) => { ++ if let Some(facing) = arg_string(vm, args, 0) { ++ self.facing = facing; ++ vm.with_cx_mut(|cx| self.drive(cx)); ++ } ++ ret(NIL) ++ } ++ m if m == live_id!(set_aspect) => { ++ if let Some(aspect) = arg_number(vm, args, 0).filter(|a| *a > 0.2 && *a < 5.0) { ++ self.aspect = aspect; ++ vm.with_cx_mut(|cx| self.drive(cx)); ++ } ++ ret(NIL) ++ } ++ m if m == live_id!(facing) => ret(vm.bx.heap.new_string_from_str(if self.facing == "front" { "front" } else { "back" })), ++ m if m == live_id!(capture) => ret(vm.with_cx_mut(|cx| self.capture(cx)).into()), ++ m if m == live_id!(record_start) => ret(vm.with_cx_mut(|cx| self.record_start(cx)).into()), ++ m if m == live_id!(record_stop) => { ++ vm.with_cx_mut(|cx| self.record_stop(cx)); ++ ret(NIL) ++ } ++ m if m == live_id!(is_recording) => ret(self.recording.is_some().into()), ++ m if m == live_id!(is_running) => ret((self.state == State::Running).into()), ++ m if m == live_id!(set_zoom) => { ++ if let Some(zoom) = arg_number(vm, args, 0) { ++ self.zoom = zoom.clamp(0.5, 20.0) as f32; ++ let zoom = self.zoom; ++ vm.with_cx_mut(|cx| self.control(cx, CameraControl::ZoomRatio(zoom))); ++ } ++ ret(NIL) ++ } ++ m if m == live_id!(zoom) => ret((if self.zoom > 0.0 { self.zoom as f64 } else { 1.0 }).into()), ++ m if m == live_id!(set_flash) => { ++ if let Some(mode) = arg_string(vm, args, 0).as_deref().and_then(flash_mode) { ++ vm.with_cx_mut(|cx| self.control(cx, CameraControl::Flash(mode))); ++ } ++ ret(NIL) ++ } ++ m if m == live_id!(set_exposure) => { ++ if let Some(ev) = arg_number(vm, args, 0) { ++ vm.with_cx_mut(|cx| self.control(cx, CameraControl::ExposureBias(ev.clamp(-4.0, 4.0) as f32))); ++ } ++ ret(NIL) ++ } ++ m if m == live_id!(focus) => { ++ if let (Some(x), Some(y)) = (arg_number(vm, args, 0), arg_number(vm, args, 1)) { ++ vm.with_cx_mut(|cx| { ++ self.control(cx, CameraControl::FocusPoint { x: x.clamp(0.0, 1.0), y: y.clamp(0.0, 1.0) }) ++ }); ++ } ++ ret(NIL) ++ } ++ m if m == live_id!(last) => ret(vm.bx.heap.new_string_from_str(&self.last)), ++ m if m == live_id!(error) => ret(vm.bx.heap.new_string_from_str(&self.last_error)), ++ _ => self.view.script_call(vm, method, args), ++ } ++ } ++ ++ fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) { ++ self.view.handle_event(cx, event, scope); ++ if self.timer.is_some_and(|t| t.is_event(event).is_some()) { ++ if self.drawn { ++ self.drawn = false; ++ if self.state == State::Idle { ++ self.drive(cx); ++ } ++ self.view.redraw(cx); ++ } else if matches!(self.state, State::Starting | State::Running) { ++ // Not drawn since the last tick: the preview left the screen. ++ self.halt(cx); ++ } ++ } ++ if self.state == State::Running { ++ self.handle_gestures(cx, event); ++ } ++ match event { ++ Event::PermissionResult(result) if result.permission == Permission::Camera => { ++ self.permission = Some(result.status); ++ self.drive(cx); ++ } ++ Event::VideoInputs(VideoInputsEvent { descs }) => { ++ let count = descs.len(); ++ self.cameras = descs ++ .iter() ++ .enumerate() ++ .map(|(i, d)| CameraChoice { input_id: d.input_id, front: is_front(&d.name, i, count), formats: d.formats.clone() }) ++ .filter(|c| c.format_for(4.0 / 3.0).is_some()) ++ .collect(); ++ self.drive(cx); ++ } ++ Event::VideoPlaybackPrepared(_) => { ++ if self.state == State::Starting { ++ self.state = State::Running; ++ } ++ } ++ Event::VideoPlaybackResourcesReleased(_) => { ++ if self.state == State::Stopping { ++ self.state = State::Idle; ++ } ++ } ++ Event::VideoDecodingError(e) => { ++ if matches!(self.state, State::Starting | State::Running) { ++ self.state = State::Idle; ++ let why = format!("the camera stopped: {}", e.error); ++ self.fail(cx, why); ++ } ++ } ++ Event::Actions(actions) => { ++ for action in actions { ++ let Some(capture) = action.downcast_ref::() else { continue }; ++ if Some(capture.input_id) == self.input { ++ let result = capture.result.clone(); ++ self.on_capture(cx, &result); ++ } ++ } ++ } ++ _ => {} ++ } ++ } ++ ++ fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep { ++ self.drawn = true; ++ self.view.draw_walk(cx, scope, walk) ++ } ++} ++ ++#[cfg(test)] ++mod tests { ++ use super::*; ++ ++ fn format(width: usize, height: usize, pixel_format: VideoPixelFormat, id: u64) -> VideoFormat { ++ VideoFormat { format_id: VideoFormatId(LiveId(id)), width, height, frame_rate: Some(30.0), pixel_format } ++ } ++ ++ #[test] ++ fn the_preview_profile_matches_the_viewfinder_then_takes_the_most_pixels() { ++ let choice = CameraChoice { ++ input_id: VideoInputId(LiveId(1)), ++ front: false, ++ formats: vec![ ++ format(640, 480, VideoPixelFormat::NV12, 1), ++ format(1440, 1080, VideoPixelFormat::NV12, 2), ++ format(1920, 1080, VideoPixelFormat::NV12, 3), ++ format(4032, 3024, VideoPixelFormat::NV12, 4), ++ ], ++ }; ++ assert_eq!(choice.format_for(4.0 / 3.0).map(|f| (f.1, f.2)), Some((1440, 1080)), "4:3, and within 1080p"); ++ assert_eq!(choice.format_for(16.0 / 9.0).map(|f| (f.1, f.2)), Some((1920, 1080))); ++ } ++ ++ #[test] ++ fn a_camera_is_front_when_it_says_so_or_is_the_unnamed_second() { ++ assert!(is_front("Front Camera", 0, 2)); ++ assert!(is_front("FaceTime HD Camera", 0, 1)); ++ assert!(!is_front("Back Camera", 1, 2)); ++ assert!(is_front("camera1", 1, 2)); ++ assert!(!is_front("camera0", 0, 2)); ++ } ++ ++ #[test] ++ fn flash_names_map_and_unknown_ones_are_ignored() { ++ assert_eq!(flash_mode("torch"), Some(CameraFlashMode::Torch)); ++ assert_eq!(flash_mode("strobe"), None); ++ } ++} +diff --git a/widgets/src/gesture_view.rs b/widgets/src/gesture_view.rs +new file mode 100644 +index 000000000..35d1a0dd9 +--- /dev/null ++++ b/widgets/src/gesture_view.rs +@@ -0,0 +1,653 @@ ++//! Gestures for script apps: a View whose taps, long presses, swipes, drags ++//! and pinches call the app's script, and a bottom sheet that rests at three ++//! heights and follows the finger between them. ++//! ++//! Recognition stays native. A pinch or a drag is dozens of touch updates a ++//! second; handing each to script would spend an installed app's instruction ++//! budget on arithmetic and stutter on an old phone. `GestureView` calls the ++//! script once per recognized gesture, and for the continuous ones (a drag, a ++//! pinch) at most once a frame and only when the value moved. `SheetView` ++//! needs no script at all while it moves; it tells the app where it came to ++//! rest. ++//! ++//! Neither widget grants anything: they are vocabulary, like a Button, and ++//! every isolate has them. ++//! ++//! ```text ++//! GestureView{ ++//! on_tap: |x, y| open_at(x, y) ++//! on_double_tap: |x, y| toggle_zoom() ++//! on_long_press: |x, y| show_menu(x, y) ++//! on_swipe: |dx, dy| if dx < 0 { next() } else { previous() } ++//! on_pan: |dx, dy, phase| drag(dx, dy, phase) // phase 0 begin, 1 move, 2 end ++//! on_pinch: |scale, x, y, phase| zoom(scale, phase) // scale relative to the pinch start ++//! Image{...} ++//! } ++//! SheetView{ on_detent: |detent| sheet_moved(detent) ...children } // 0 peek, 1 half, 2 full ++//! ``` ++//! Coordinates are relative to the view's top left. ++use crate::{ ++ makepad_derive_widget::*, ++ makepad_draw::*, ++ makepad_script::ScriptFnRef, ++ view::View, ++ widget::*, ++ widget_async::CxWidgetToScriptCallExt, ++}; ++use crate::makepad_draw::makepad_platform::event::{DigitId, TouchState, TouchUpdateEvent}; ++ ++script_mod! { ++ use mod.prelude.widgets_internal.* ++ use mod.widgets.* ++ ++ mod.widgets.GestureViewBase = #(GestureView::register_widget(vm)) ++ mod.widgets.GestureView = set_type_default() do mod.widgets.GestureViewBase{ ++ width: Fill ++ height: Fit ++ } ++ ++ mod.widgets.SheetViewBase = #(SheetView::register_widget(vm)) ++ mod.widgets.SheetView = set_type_default() do mod.widgets.SheetViewBase{ ++ width: Fill ++ height: Fill ++ flow: Down ++ } ++} ++ ++/// Movement under this is still a tap: a finger is never perfectly still. ++const TAP_SLOP: f64 = 10.0; ++/// A second tap this soon after the first, this close to it, is a double tap. ++const DOUBLE_TAP_SECONDS: f64 = 0.3; ++const DOUBLE_TAP_DISTANCE: f64 = 24.0; ++/// A release this fast after this much travel along one axis is a swipe. ++const SWIPE_DISTANCE: f64 = 60.0; ++const SWIPE_SECONDS: f64 = 0.45; ++/// A pinch reports again once its scale has moved this much. ++const PINCH_STEP: f64 = 0.02; ++ ++/// A swipe's direction, when the travel was one: the dominant axis carries ++/// at least 1.5 times the other, and far enough, fast enough. ++fn swipe(dx: f64, dy: f64, seconds: f64) -> Option<(f64, f64)> { ++ if seconds > SWIPE_SECONDS { ++ return None; ++ } ++ if dx.abs() >= SWIPE_DISTANCE && dx.abs() >= dy.abs() * 1.5 { ++ return Some((dx, 0.0)); ++ } ++ if dy.abs() >= SWIPE_DISTANCE && dy.abs() >= dx.abs() * 1.5 { ++ return Some((0.0, dy)); ++ } ++ None ++} ++ ++#[derive(Clone, Copy, Debug)] ++struct Contact { ++ uid: u64, ++ pos: DVec2, ++} ++ ++/// Two fingers down: the spread they started at. ++#[derive(Clone, Copy, Debug)] ++struct Spread { ++ first: u64, ++ second: u64, ++ distance: f64, ++} ++ ++/// What one touch update did to a pinch. ++#[derive(Clone, Copy, Debug, Default, PartialEq)] ++pub(crate) enum PinchStep { ++ #[default] ++ Nothing, ++ Began(DVec2), ++ Moved(f64, DVec2), ++ Ended, ++} ++ ++/// Follows the fingers that started inside the view and reports a pinch. ++#[derive(Clone, Debug, Default)] ++pub(crate) struct PinchTracker { ++ contacts: Vec, ++ spread: Option, ++} ++ ++impl PinchTracker { ++ pub(crate) fn update(&mut self, event: &TouchUpdateEvent, bounds: Rect) -> PinchStep { ++ for touch in &event.touches { ++ match touch.state { ++ TouchState::Start => { ++ if bounds.contains(touch.abs) && !self.contacts.iter().any(|c| c.uid == touch.uid) { ++ self.contacts.push(Contact { uid: touch.uid, pos: touch.abs }); ++ } ++ } ++ TouchState::Stop => self.contacts.retain(|c| c.uid != touch.uid), ++ TouchState::Move | TouchState::Stable => { ++ if let Some(contact) = self.contacts.iter_mut().find(|c| c.uid == touch.uid) { ++ contact.pos = touch.abs; ++ } ++ } ++ } ++ } ++ let contact = |uid: u64| self.contacts.iter().find(|c| c.uid == uid).copied(); ++ match self.spread { ++ None if self.contacts.len() >= 2 => { ++ let (first, second) = (self.contacts[0], self.contacts[1]); ++ self.spread = Some(Spread { ++ first: first.uid, ++ second: second.uid, ++ // A zero spread would make every later ratio infinite. ++ distance: first.pos.distance(&second.pos).max(1.0), ++ }); ++ PinchStep::Began((first.pos + second.pos) * 0.5) ++ } ++ Some(spread) => match (contact(spread.first), contact(spread.second)) { ++ (Some(first), Some(second)) => PinchStep::Moved( ++ first.pos.distance(&second.pos) / spread.distance, ++ (first.pos + second.pos) * 0.5, ++ ), ++ _ => { ++ self.spread = None; ++ PinchStep::Ended ++ } ++ }, ++ None => PinchStep::Nothing, ++ } ++ } ++ ++ pub(crate) fn pinching(&self) -> bool { ++ self.spread.is_some() ++ } ++ ++ /// Whether any finger that started a pinch is still down: its release ++ /// must not read as a tap. ++ fn holding(&self) -> bool { ++ !self.contacts.is_empty() ++ } ++} ++ ++/// One finger followed from the raw touch stream (see GestureView's ++/// handle_event): where and when it went down, and what it has become. ++#[derive(Clone, Copy, Debug)] ++struct TouchPress { ++ uid: u64, ++ start: DVec2, ++ time: f64, ++ panning: bool, ++ /// Spent on a long press: its release is no tap. ++ spent: bool, ++ last_sent: DVec2, ++} ++ ++#[derive(Script, ScriptHook, Widget)] ++pub struct GestureView { ++ #[deref] ++ view: View, ++ #[live] ++ on_tap: ScriptFnRef, ++ #[live] ++ on_double_tap: ScriptFnRef, ++ #[live] ++ on_long_press: ScriptFnRef, ++ #[live] ++ on_swipe: ScriptFnRef, ++ #[live] ++ on_pan: ScriptFnRef, ++ #[live] ++ on_pinch: ScriptFnRef, ++ ++ /// This press is ours: no child had already captured it. ++ #[rust] ++ live: bool, ++ /// The press moved past the tap slop: a drag, not a tap. ++ #[rust] ++ panning: bool, ++ /// Last drag deltas sent, so a still finger sends nothing. ++ #[rust] ++ pan_sent: DVec2, ++ #[rust] ++ last_tap: Option<(f64, DVec2)>, ++ #[rust] ++ pinch: PinchTracker, ++ #[rust] ++ touch: Option, ++ #[rust] ++ pinch_sent: f64, ++ /// A pinch happened during this press: the fingers' release is not a ++ /// tap, a swipe or the end of a drag. ++ #[rust] ++ pinched: bool, ++} ++ ++impl GestureView { ++ fn call(&self, cx: &mut Cx, script_fn: &ScriptFnRef, args: &[ScriptValue]) { ++ if script_fn.as_object() == ScriptObject::ZERO { ++ return; ++ } ++ cx.widget_to_script_call(self.widget_uid(), NIL, self.view.source.clone(), script_fn.clone(), args); ++ } ++ ++ fn local(&self, cx: &Cx, abs: DVec2) -> DVec2 { ++ abs - self.view.area().rect(cx).pos ++ } ++ ++ fn handle_touches(&mut self, cx: &mut Cx, event: &TouchUpdateEvent) { ++ let area = self.view.area(); ++ let bounds = area.clipped_rect(cx); ++ for t in &event.touches { ++ match t.state { ++ TouchState::Start => { ++ if self.touch.is_some() || !bounds.contains(t.abs) { ++ continue; ++ } ++ let claimed = t.handled.get(); ++ if !claimed.is_empty() && claimed != area && is_inside(cx, claimed, area) { ++ continue; ++ } ++ if !self.pinch.holding() || self.pinch.contacts.len() <= 1 { ++ self.pinched = false; ++ } ++ self.touch = Some(TouchPress { uid: t.uid, start: t.abs, time: t.time, panning: false, spent: false, last_sent: DVec2::default() }); ++ } ++ TouchState::Move | TouchState::Stable => { ++ let Some(mut press) = self.touch.filter(|p| p.uid == t.uid) else { continue }; ++ if self.pinch.pinching() || self.pinched { ++ continue; ++ } ++ let delta = t.abs - press.start; ++ if !press.panning && delta.length() > TAP_SLOP { ++ press.panning = true; ++ self.call(cx, &self.on_pan.clone(), &[0.0.into(), 0.0.into(), 0.0.into()]); ++ } ++ if press.panning && (delta - press.last_sent).length() >= 1.0 { ++ press.last_sent = delta; ++ self.call(cx, &self.on_pan.clone(), &[delta.x.into(), delta.y.into(), 1.0.into()]); ++ } ++ self.touch = Some(press); ++ } ++ TouchState::Stop => { ++ let Some(press) = self.touch.filter(|p| p.uid == t.uid) else { continue }; ++ self.touch = None; ++ if self.pinched { ++ continue; ++ } ++ let delta = t.abs - press.start; ++ if press.panning { ++ self.call(cx, &self.on_pan.clone(), &[delta.x.into(), delta.y.into(), 2.0.into()]); ++ if let Some((dx, dy)) = swipe(delta.x, delta.y, t.time - press.time) { ++ self.call(cx, &self.on_swipe.clone(), &[dx.into(), dy.into()]); ++ } ++ } else if !press.spent && delta.length() <= TAP_SLOP && bounds.contains(t.abs) { ++ self.tap(cx, t.abs, t.time); ++ } ++ } ++ } ++ } ++ } ++ ++ fn tap(&mut self, cx: &mut Cx, abs: DVec2, time: f64) { ++ let at = self.local(cx, abs); ++ self.call(cx, &self.on_tap.clone(), &[at.x.into(), at.y.into()]); ++ let double = self ++ .last_tap ++ .is_some_and(|(t, pos)| time - t <= DOUBLE_TAP_SECONDS && pos.distance(&abs) <= DOUBLE_TAP_DISTANCE); ++ if double { ++ self.last_tap = None; ++ self.call(cx, &self.on_double_tap.clone(), &[at.x.into(), at.y.into()]); ++ } else { ++ self.last_tap = Some((time, abs)); ++ } ++ } ++} ++ ++/// Whether `inner` is a widget drawn inside `outer` (not `outer`'s own ++/// rect, and not one around it). ++fn is_inside(cx: &Cx, inner: Area, outer: Area) -> bool { ++ let (rect, own) = (inner.rect(cx), outer.rect(cx)); ++ rect.is_inside_of(own) && rect.size != own.size ++} ++ ++impl Widget for GestureView { ++ fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep { ++ self.view.draw_walk(cx, scope, walk) ++ } ++ ++ fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) { ++ self.view.handle_event(cx, event, scope); ++ if !self.view.visible { ++ return; ++ } ++ ++ if let Event::TouchUpdate(touches) = event { ++ let bounds = self.view.area().clipped_rect(cx); ++ match self.pinch.update(touches, bounds) { ++ PinchStep::Began(center) => { ++ self.pinched = true; ++ self.pinch_sent = 1.0; ++ if self.panning { ++ // The drag a first finger started gives way to the pinch. ++ let at = self.pan_sent; ++ self.call(cx, &self.on_pan.clone(), &[at.x.into(), at.y.into(), 2.0.into()]); ++ self.panning = false; ++ } ++ let at = self.local(cx, center); ++ self.call(cx, &self.on_pinch.clone(), &[1.0.into(), at.x.into(), at.y.into(), 0.0.into()]); ++ } ++ PinchStep::Moved(scale, center) => { ++ if (scale / self.pinch_sent - 1.0).abs() >= PINCH_STEP { ++ self.pinch_sent = scale; ++ let at = self.local(cx, center); ++ self.call(cx, &self.on_pinch.clone(), &[scale.into(), at.x.into(), at.y.into(), 1.0.into()]); ++ } ++ } ++ PinchStep::Ended => { ++ let scale = self.pinch_sent; ++ self.call(cx, &self.on_pinch.clone(), &[scale.into(), 0.0.into(), 0.0.into(), 2.0.into()]); ++ } ++ PinchStep::Nothing => {} ++ } ++ } ++ ++ // Touch is read from the raw stream and never claimed. Claiming it, ++ // even with a capture overload, marks the touch handled, and the ++ // scroll view around this one then never starts its drag: a list of ++ // GestureView rows would not scroll. A child that claimed the touch ++ // (a Button inside) still keeps it. ++ match event { ++ Event::TouchUpdate(touches) => { ++ self.handle_touches(cx, touches); ++ return; ++ } ++ Event::LongPress(press) => { ++ let fire = match self.touch.as_mut() { ++ Some(p) if p.uid == press.uid && !p.panning && !p.spent && !self.pinched => { ++ p.spent = true; ++ true ++ } ++ _ => false, ++ }; ++ if fire { ++ let at = self.local(cx, press.abs); ++ self.call(cx, &self.on_long_press.clone(), &[at.x.into(), at.y.into()]); ++ } ++ return; ++ } ++ _ => {} ++ } ++ ++ match event.hits_with_capture_overload(cx, self.view.area(), true) { ++ Hit::FingerDown(e) => { ++ // A child that already owns this press (a Button) keeps it. A ++ // scroll view around us captures every press too; that one is ++ // not a child, and the tap is still ours. ++ self.live = !child_owns_press(cx, e.digit_id, self.view.area()); ++ self.panning = false; ++ self.pan_sent = DVec2::default(); ++ if !self.pinch.holding() { ++ self.pinched = false; ++ } ++ } ++ Hit::FingerMove(e) if self.live && !self.pinch.pinching() && !self.pinched => { ++ let delta = e.abs - e.abs_start; ++ if !self.panning && delta.length() > TAP_SLOP { ++ self.panning = true; ++ self.call(cx, &self.on_pan.clone(), &[0.0.into(), 0.0.into(), 0.0.into()]); ++ } ++ if self.panning && (delta - self.pan_sent).length() >= 1.0 { ++ self.pan_sent = delta; ++ self.call(cx, &self.on_pan.clone(), &[delta.x.into(), delta.y.into(), 1.0.into()]); ++ } ++ } ++ Hit::FingerLongPress(e) if self.live && !self.panning && !self.pinched => { ++ let at = self.local(cx, e.abs); ++ // The press is spent on the long press: its release is no tap. ++ self.live = false; ++ self.call(cx, &self.on_long_press.clone(), &[at.x.into(), at.y.into()]); ++ } ++ Hit::FingerUp(e) if self.live => { ++ self.live = false; ++ if self.pinched { ++ return; ++ } ++ let delta = e.abs - e.abs_start; ++ if self.panning { ++ self.panning = false; ++ self.call(cx, &self.on_pan.clone(), &[delta.x.into(), delta.y.into(), 2.0.into()]); ++ if let Some((dx, dy)) = swipe(delta.x, delta.y, e.time - e.capture_time) { ++ self.call(cx, &self.on_swipe.clone(), &[dx.into(), dy.into()]); ++ } ++ return; ++ } ++ if e.is_over && delta.length() <= TAP_SLOP && !e.has_long_press_occurred { ++ self.tap(cx, e.abs, e.time); ++ } ++ } ++ _ => {} ++ } ++ } ++} ++ ++/// Whether a widget inside `area` (not `area` itself, not one around it) ++/// captured this press. ++fn child_owns_press(cx: &Cx, digit_id: DigitId, area: Area) -> bool { ++ cx.fingers ++ .digit_capture_areas(digit_id) ++ .into_iter() ++ .any(|captured| captured != area && is_inside(cx, captured, area)) ++} ++ ++/// Of the sheet's own height: the peek, half and full resting heights. ++const PEEK_HEIGHT: f64 = 132.0; ++const HALF_FRACTION: f64 = 0.45; ++/// What full height leaves above the sheet, so a search bar stays reachable. ++const FULL_TOP_GAP: f64 = 96.0; ++/// Pixels a second: faster than this at release is a flick to the next rest. ++const FLICK_VELOCITY: f64 = 600.0; ++ ++/// The three resting heights in a viewport this tall, rising. In a short ++/// viewport they close up rather than cross. ++fn sheet_heights(viewport_h: f64) -> [f64; 3] { ++ let half = viewport_h * HALF_FRACTION; ++ [PEEK_HEIGHT.min(half), half, (viewport_h - FULL_TOP_GAP).max(half)] ++} ++ ++/// Where a released drag comes to rest: the nearest height, or for a flick ++/// the next one the way the finger was going (velocity is downwards, px/s). ++fn sheet_rest(at: f64, velocity: f64, viewport_h: f64) -> usize { ++ let heights = sheet_heights(viewport_h); ++ if velocity <= -FLICK_VELOCITY { ++ heights.iter().position(|h| *h > at + 0.5).unwrap_or(2) ++ } else if velocity >= FLICK_VELOCITY { ++ heights.iter().rposition(|h| *h < at - 0.5).unwrap_or(0) ++ } else { ++ let distance = |i: &usize| (heights[*i] - at).abs(); ++ (0..3).min_by(|a, b| distance(a).total_cmp(&distance(b))).unwrap_or(0) ++ } ++} ++ ++/// A bottom sheet: fills its parent, draws its children in a panel along the ++/// bottom edge at the current detent's height, follows a drag that starts on ++/// the panel, and eases to a detent on release. The app hears only where it ++/// came to rest (`on_detent`), and can move it with `detent: 0|1|2`. ++#[derive(Script, ScriptHook, Widget)] ++pub struct SheetView { ++ #[deref] ++ view: View, ++ /// The detent to rest at: 0 peek, 1 half, 2 full. Script may set it. ++ #[live(0.0)] ++ detent: f64, ++ #[live] ++ on_detent: ScriptFnRef, ++ ++ #[rust] ++ height: f64, ++ /// While a finger is on the panel: the height it started at, and the ++ /// last (time, height) for the release velocity. ++ #[rust] ++ drag: Option<(f64, f64, f64)>, ++ #[rust] ++ prev: Option<(f64, f64)>, ++ #[rust] ++ viewport_h: f64, ++ #[rust] ++ next_frame: NextFrame, ++} ++ ++impl SheetView { ++ fn target(&self) -> f64 { ++ sheet_heights(self.viewport_h)[self.detent.clamp(0.0, 2.0) as usize] ++ } ++} ++ ++impl Widget for SheetView { ++ fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep { ++ let outer = cx.peek_walk_turtle(walk); ++ if outer.size.y > 0.0 { ++ self.viewport_h = outer.size.y; ++ } ++ if self.height <= 0.0 { ++ self.height = self.target(); ++ } ++ let mut inner = walk; ++ inner.height = Size::Fixed(self.height.max(0.0)); ++ inner.abs_pos = Some(dvec2(outer.pos.x, outer.pos.y + outer.size.y - self.height)); ++ cx.walk_turtle(walk); ++ self.view.draw_walk(cx, scope, inner) ++ } ++ ++ fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) { ++ self.view.handle_event(cx, event, scope); ++ if let Some(ne) = self.next_frame.is_event(event) { ++ let target = self.target(); ++ if self.drag.is_none() { ++ // Ease a fixed share of the way each frame: settles in ~200 ms. ++ let step = (target - self.height) * 0.25; ++ if step.abs() < 0.5 { ++ self.height = target; ++ } else { ++ self.height += step; ++ self.next_frame = cx.new_next_frame(); ++ } ++ let _ = ne; ++ self.view.redraw(cx); ++ } ++ } ++ match event.hits_with_capture_overload(cx, self.view.area(), true) { ++ Hit::FingerDown(e) => { ++ if !child_owns_press(cx, e.digit_id, self.view.area()) { ++ self.drag = Some((self.height, e.time, self.height)); ++ self.prev = None; ++ } ++ } ++ Hit::FingerMove(e) => { ++ if let Some((from, _, _)) = self.drag { ++ let [lowest, _, highest] = sheet_heights(self.viewport_h); ++ let now = (from - (e.abs.y - e.abs_start.y)).clamp(lowest, highest); ++ self.prev = self.drag.map(|(_, t, h)| (t, h)); ++ self.drag = Some((from, e.time, now)); ++ self.height = now; ++ self.view.redraw(cx); ++ } ++ } ++ Hit::FingerUp(e) => { ++ if let Some((_, time, at)) = self.drag.take() { ++ // Downwards velocity: the sheet shrinks as the finger descends. ++ let velocity = match self.prev { ++ Some((t0, h0)) if time > t0 => -(at - h0) / (time - t0), ++ _ => 0.0, ++ }; ++ let _ = e; ++ let rest = sheet_rest(at, velocity, self.viewport_h) as f64; ++ let moved = rest != self.detent.clamp(0.0, 2.0).floor(); ++ self.detent = rest; ++ self.next_frame = cx.new_next_frame(); ++ if moved && self.on_detent.as_object() != ScriptObject::ZERO { ++ cx.widget_to_script_call( ++ self.widget_uid(), ++ NIL, ++ self.view.source.clone(), ++ self.on_detent.clone(), ++ &[rest.into()], ++ ); ++ } ++ } ++ } ++ _ => {} ++ } ++ } ++} ++ ++#[cfg(test)] ++mod tests { ++ use super::*; ++ use crate::makepad_draw::makepad_platform::event::TouchPoint; ++ use std::cell::Cell; ++ ++ fn touches(points: &[(u64, TouchState, f64, f64)]) -> TouchUpdateEvent { ++ TouchUpdateEvent { ++ time: 1.0, ++ window_id: WindowId(0, 0), ++ modifiers: KeyModifiers::default(), ++ touches: points ++ .iter() ++ .map(|&(uid, state, x, y)| TouchPoint { ++ uid, ++ state, ++ abs: dvec2(x, y), ++ time: 1.0, ++ rotation_angle: 0.0, ++ force: 1.0, ++ radius: dvec2(1.0, 1.0), ++ handled: Cell::new(Area::Empty), ++ sweep_lock: Cell::new(Area::Empty), ++ }) ++ .collect(), ++ } ++ } ++ ++ #[test] ++ fn a_swipe_is_fast_and_along_one_axis() { ++ assert_eq!(swipe(-120.0, 10.0, 0.2), Some((-120.0, 0.0))); ++ assert_eq!(swipe(5.0, 90.0, 0.2), Some((0.0, 90.0))); ++ assert_eq!(swipe(-120.0, 100.0, 0.2), None, "a diagonal is no swipe"); ++ assert_eq!(swipe(-120.0, 0.0, 1.0), None, "a slow drag is no swipe"); ++ assert_eq!(swipe(-30.0, 0.0, 0.1), None, "nor is a short one"); ++ } ++ ++ #[test] ++ fn two_fingers_inside_the_view_pinch_relative_to_their_start() { ++ let bounds = Rect { pos: dvec2(0.0, 0.0), size: dvec2(400.0, 400.0) }; ++ let mut pinch = PinchTracker::default(); ++ assert_eq!(pinch.update(&touches(&[(1, TouchState::Start, 100.0, 200.0)]), bounds), PinchStep::Nothing); ++ assert_eq!( ++ pinch.update(&touches(&[(2, TouchState::Start, 300.0, 200.0)]), bounds), ++ PinchStep::Began(dvec2(200.0, 200.0)) ++ ); ++ assert_eq!( ++ pinch.update(&touches(&[(2, TouchState::Move, 500.0, 200.0)]), bounds), ++ PinchStep::Moved(2.0, dvec2(300.0, 200.0)) ++ ); ++ assert_eq!(pinch.update(&touches(&[(1, TouchState::Stop, 100.0, 200.0)]), bounds), PinchStep::Ended); ++ assert!(pinch.holding(), "a finger is still down: its release is not a tap"); ++ pinch.update(&touches(&[(2, TouchState::Stop, 500.0, 200.0)]), bounds); ++ assert!(!pinch.holding()); ++ } ++ ++ #[test] ++ fn a_finger_that_starts_outside_the_view_is_not_part_of_its_pinch() { ++ let bounds = Rect { pos: dvec2(0.0, 0.0), size: dvec2(100.0, 100.0) }; ++ let mut pinch = PinchTracker::default(); ++ pinch.update(&touches(&[(1, TouchState::Start, 50.0, 50.0)]), bounds); ++ assert_eq!(pinch.update(&touches(&[(2, TouchState::Start, 500.0, 50.0)]), bounds), PinchStep::Nothing); ++ } ++ ++ #[test] ++ fn the_sheet_rests_at_the_nearest_detent_or_flicks_to_the_next() { ++ assert_eq!(sheet_heights(800.0), [132.0, 360.0, 704.0]); ++ assert_eq!(sheet_rest(200.0, 0.0, 800.0), 0); ++ assert_eq!(sheet_rest(300.0, 0.0, 800.0), 1); ++ assert_eq!(sheet_rest(200.0, -900.0, 800.0), 1, "an upward flick goes to the next detent up"); ++ assert_eq!(sheet_rest(650.0, 900.0, 800.0), 1, "a downward flick to the next one down"); ++ let [peek, half, full] = sheet_heights(200.0); ++ assert!(peek <= half && half <= full && full <= 200.0); ++ } ++} +diff --git a/widgets/src/lib.rs b/widgets/src/lib.rs +index 4c6418776..211cf4086 100644 +--- a/widgets/src/lib.rs ++++ b/widgets/src/lib.rs +@@ -33,6 +33,9 @@ + + // Modules ordered to match script_mod calls + pub mod rubber_view; ++pub mod gesture_view; ++pub mod web_reader; ++pub mod camera_preview; + pub mod scroll_bar; + pub mod scroll_bars; + pub mod scroll_motion; +@@ -363,6 +366,8 @@ pub fn widgets_mod(vm: &mut ScriptVm) { + crate::view_ui::script_mod(vm); + crate::grid::script_mod(vm); + crate::rubber_view::script_mod(vm); ++ crate::gesture_view::script_mod(vm); ++ crate::web_reader::script_mod(vm); + + crate::label::script_mod(vm); + crate::link_label::script_mod(vm); +@@ -444,6 +449,7 @@ pub fn widgets_mod(vm: &mut ScriptVm) { + crate::callout_tooltip::script_mod(vm); + crate::popup_notification::script_mod(vm); + crate::video::script_mod(vm); ++ crate::camera_preview::script_mod(vm); + crate::page_flip::script_mod(vm); + crate::hosted_view::script_mod(vm); + crate::file_tree::script_mod(vm); +diff --git a/widgets/src/map/archive.rs b/widgets/src/map/archive.rs +index baef27a1f..3d9b67fef 100644 +--- a/widgets/src/map/archive.rs ++++ b/widgets/src/map/archive.rs +@@ -468,6 +468,10 @@ struct CachedHttpRange { + /// HTTP `.mkmap` source using one whole-root GET and strict shard ranges. + pub struct HttpRangeByteSource { + root_url: String, ++ /// The script heap this source fetches for (0 = the host's own): every ++ /// request answers to that heap's allowlist, like the rest of the network ++ /// paths a card can reach. ++ heap_key: usize, + disk_cache: Option, + requests: HashMap, + queued: Vec, +@@ -496,6 +500,7 @@ fn new_with_disk_cache( + ) -> Self { + Self { + root_url: root_url.into().trim_end_matches('/').to_string(), ++ heap_key: 0, + disk_cache, + requests: HashMap::new(), + queued: Vec::new(), +@@ -575,6 +580,17 @@ fn issue(&mut self, cx: &mut Cx, mut pending: HttpNetworkRead) { + (url, Some(format!("bytes={offset}-{end}")), len) + } + }; ++ if !crate::splash_policy::url_allowed(self.heap_key, &url) { ++ crate::log!("map archive fetch refused by the host's allowlist: {url}"); ++ for waiter in &pending.waiters { ++ self.ready.push_back(ReadCompletion { ++ token: waiter.token, ++ result: Err("refused by the host's allowlist".to_string()), ++ }); ++ } ++ cx.redraw_all(); ++ return; ++ } + #[cfg(target_arch = "wasm32")] + let url = format!( + "{url}#makepad-http=archive&tiles={tile_label}&priority={priority}&bytes={max_body}" +@@ -2023,6 +2039,14 @@ pub fn http(root_url: impl Into, workers: ArchiveWorkerPool) -> Self { + )) + } + ++ /// [`Self::http`] on behalf of a script heap, whose allowlist every ++ /// request then answers to. ++ pub fn http_for_heap(root_url: impl Into, workers: ArchiveWorkerPool, heap_key: usize) -> Self { ++ let mut source = HttpRangeByteSource::new(root_url); ++ source.heap_key = heap_key; ++ Self::Http(TileArchive::new(source, workers)) ++ } ++ + pub fn request_tile( + &mut self, + cx: &mut Cx, +diff --git a/widgets/src/map/view.rs b/widgets/src/map/view.rs +index 4581c70af..7bc1d7167 100644 +--- a/widgets/src/map/view.rs ++++ b/widgets/src/map/view.rs +@@ -4312,6 +4312,10 @@ struct LabelLists { + + // --- MapView widget --- + ++/// How many Overpass mirrors a tile request tries before it gives up on the ++/// ones an app's policy does not list. ++const OVERPASS_MIRROR_TRIES: u8 = 8; ++ + #[derive(Script, Widget)] + pub struct MapView { + /// The @cam readout in the corner: the exact command to recreate this +@@ -7542,7 +7546,8 @@ fn install_overlay_sources(&mut self, cx: &mut Cx) { + .overlays + .iter() + .map(|overlay| { +- let (archive, local_mbtiles_path, filter) = match &overlay.source { ++ let source = self.jailed_tile_source(overlay.source.clone()); ++ let (archive, local_mbtiles_path, filter) = match &source { + TileSourceConfig::LocalArchive { mbtiles_path, .. } => { + let (path, filter) = split_overlay_option(mbtiles_path); + if is_mkmap_path_shape(path) { +@@ -7558,7 +7563,7 @@ fn install_overlay_sources(&mut self, cx: &mut Cx) { + TileSourceConfig::HttpArchive { root_url, .. } => { + let (root_url, filter) = split_overlay_option(root_url); + ( +- Some(MapTileArchive::http(root_url, workers.clone())), ++ Some(MapTileArchive::http_for_heap(root_url, workers.clone(), self.source.heap_key())), + None, + filter, + ) +@@ -7575,7 +7580,33 @@ fn install_overlay_sources(&mut self, cx: &mut Cx) { + self.apply_archive_cache_budgets(cx); + } + ++ /// A local archive config as this map's script heap may read it: the ++ /// paths of a card under a policy resolve inside its storage jail, and a ++ /// path that cannot (no jail, or one that climbs out) reads nothing. ++ fn jailed_tile_source(&self, config: TileSourceConfig) -> TileSourceConfig { ++ let heap_key = self.source.heap_key(); ++ let jail = |path: &str| { ++ if path.is_empty() { ++ return String::new(); ++ } ++ crate::splash_policy::local_path_for_heap(heap_key, path).unwrap_or_default() ++ }; ++ match config { ++ TileSourceConfig::LocalArchive { mbtiles_path, detail_mbtiles_path, bridge_dz_path } => { ++ TileSourceConfig::LocalArchive { ++ mbtiles_path: jail(&mbtiles_path), ++ detail_mbtiles_path: jail(&detail_mbtiles_path), ++ bridge_dz_path: jail(&bridge_dz_path), ++ } ++ } ++ TileSourceConfig::HttpArchive { root_url, detail_root_url, bridge_dz_path } => { ++ TileSourceConfig::HttpArchive { root_url, detail_root_url, bridge_dz_path: jail(&bridge_dz_path) } ++ } ++ } ++ } ++ + fn install_archive_source(&mut self, cx: &mut Cx, config: TileSourceConfig) { ++ let config = self.jailed_tile_source(config); + self.archive_generation = self.archive_generation.wrapping_add(1).max(1); + self.style_epoch = self.style_epoch.wrapping_add(1).max(1); + if let Some(archive) = self.base_archive.as_mut() { +@@ -7610,9 +7641,9 @@ fn install_archive_source(&mut self, cx: &mut Cx, config: TileSourceConfig) { + self.mbtiles_path.clear(); + self.detail_mbtiles_path = detail_root_url.clone(); + self.bridge_dz_mbtiles_path = bridge_dz_path.clone(); +- self.base_archive = Some(MapTileArchive::http(root_url, workers.clone())); ++ self.base_archive = Some(MapTileArchive::http_for_heap(root_url, workers.clone(), self.source.heap_key())); + self.detail_archive = needs_separate_detail_archive(&config) +- .then(|| MapTileArchive::http(detail_root_url, workers)); ++ .then(|| MapTileArchive::http_for_heap(detail_root_url, workers, self.source.heap_key())); + self.use_local_mbtiles = true; + self.use_network = false; + } +@@ -9389,7 +9420,18 @@ fn request_tile( + } + + let query = overpass_query(tile_key); +- let endpoint = overpass_endpoint(tile_key, attempts, cx.seconds_since_app_start() as u64); ++ // The endpoint rotates across mirrors per tile and attempt. An app ++ // under a policy may list only some of them: use the next one it may ++ // reach rather than failing the tile on the mirror it may not. ++ let now = cx.seconds_since_app_start() as u64; ++ let heap_key = self.source.heap_key(); ++ let Some(endpoint) = (0..OVERPASS_MIRROR_TRIES) ++ .map(|k| overpass_endpoint(tile_key, attempts.wrapping_add(k), now)) ++ .find(|endpoint| crate::splash_policy::url_allowed(heap_key, endpoint)) ++ else { ++ self.mark_tile_failed(tile_key, "refused by the host's allowlist"); ++ return false; ++ }; + let mut request = HttpRequest::new(endpoint.to_string(), HttpMethod::POST); + request.set_header("Content-Type".to_string(), "text/plain".to_string()); + request.set_header("Accept".to_string(), "application/json".to_string()); +@@ -13557,7 +13599,7 @@ fn nav_update(&mut self, cx: &mut Cx, rect: Rect) { + // Where the DEVICE is: the last fix, else the card's declared + // centre (a fact it observed), else nothing — the camera does not + // move on its own. +- let fix = crate::makepad_draw::makepad_platform::gps::last_gps_fix() ++ let fix = crate::splash_policy::gps_fix_for_heap(self.source.heap_key()) + .map(|f| (f.lat, f.lon)) + .filter(|(lat, lon)| is_a_place(*lat, *lon)) + .or(Some((self.center_lat, self.center_lon))) +diff --git a/widgets/src/splash.rs b/widgets/src/splash.rs +index 2262d0f2b..53db34c04 100644 +--- a/widgets/src/splash.rs ++++ b/widgets/src/splash.rs +@@ -281,9 +281,13 @@ fn eval_styled_body(&mut self,cx:&mut Cx,preserve:bool) { + crate::desktop_style::apply_widgets(vm); + crate::script_eval!(vm, {mod.res = nil}); + crate::widget_async::apply_splash_isolate_mods(vm); ++ crate::widget_async::forget_granted_isolate_mods(heap_key); + }); + } + } ++ // Vocabulary this app's grants entitle it to, now that its policy ++ // is seated (see `register_splash_isolate_mod_for`). ++ crate::widget_async::apply_granted_isolate_mods(vm); + // Everything on `mod` that is not the body's own; whatever the run + // adds beyond this is the body's. + let mut known = module_keys(vm); +@@ -742,7 +746,7 @@ pub fn call_script_fn_with_strings(&mut self, cx: &mut Cx, name: LiveId, args: & + } + let vals: Vec = args + .iter() +- .map(|s| vm.new_string_with(|_vm, out| out.push_str(s))) ++ .map(|s| vm.bx.heap.new_string_from_str(s)) + .collect(); + vm.with_instruction_limit(WIDGET_SCRIPT_INSTRUCTION_LIMIT, |vm| { + vm.call(fnval, &vals); +@@ -1234,6 +1238,14 @@ pub fn register_agent_module(vm: &mut ScriptVm) { + let mut payload = String::new(); + vm.bx.heap.to_json_inner(payload_value, &mut payload); + ++ // A notify carries no sender, so the host acts on it as if its own ++ // card sent it. An app under a policy may only post one when it ++ // was granted `agent`; anything else would let an installed app ++ // drive the host's agent. ++ if let Err(reason) = crate::splash_policy::service_allowed(vm.bx.heap.heap_key(), "agent.notify") { ++ log!("splash: refused agent.notify {event_id:?}: {reason}"); ++ return NIL; ++ } + Cx::post_action(SplashAction::Notify { event_id, payload }); + NIL + }, +@@ -1565,7 +1577,7 @@ pub fn register_agent_module(vm: &mut ScriptVm) { + let field_v = script_value!(vm, args.field); + let mut field = String::new(); + vm.bx.heap.cast_to_string(field_v, &mut field); +- let url = geocode_url(&name); ++ let url = geocode_url(&name, vm.bx.heap.heap_key()); + let out = match vm.host.cx_mut().script_data_fetch(&url) { + Some(bytes) => geocode_pluck(&bytes, field.trim()) + .unwrap_or_else(|| "—".to_string()), +@@ -1617,7 +1629,7 @@ pub fn register_agent_module(vm: &mut ScriptVm) { + let field_v = script_value!(vm, args.field); + let mut field = String::new(); + vm.bx.heap.cast_to_string(field_v, &mut field); +- let url = geocode_url(&name); ++ let url = geocode_url(&name, vm.bx.heap.heap_key()); + let key = if field.trim() == "lon" { "lon" } else { "lat" }; + let n = vm + .host +@@ -3054,7 +3066,7 @@ pub fn register_agent_module(vm: &mut ScriptVm) { + let field_v = script_value!(vm, args.field); + let mut field = String::new(); + vm.bx.heap.cast_to_string(field_v, &mut field); +- let out = pref_at(field.trim()).unwrap_or_else(|| "\u{2014}".to_string()); ++ let out = pref_at(vm.bx.heap.heap_key(), field.trim()).unwrap_or_else(|| "\u{2014}".to_string()); + vm.bx.heap.new_string_from_str(&out) + }, + ); +@@ -3079,7 +3091,7 @@ pub fn register_agent_module(vm: &mut ScriptVm) { + let field_v = script_value!(vm, args.field); + let mut field = String::new(); + vm.bx.heap.cast_to_string(field_v, &mut field); +- let Some(id) = collection_at("reading", index) else { ++ let Some(id) = collection_at(vm.bx.heap.heap_key(), "reading", index) else { + return vm.bx.heap.new_string_from_str("—"); + }; + let key = match field.trim().to_ascii_lowercase().as_str() { +@@ -3335,8 +3347,8 @@ pub fn register_agent_module(vm: &mut ScriptVm) { + let mut ticker = String::new(); + vm.bx.heap.cast_to_string(ticker_v, &mut ticker); + let ticker = ticker.trim(); +- let held = (0..collection_len("watchlist")) +- .filter_map(|i| collection_at("watchlist", i)) ++ let held = (0..collection_len_for(vm.bx.heap.heap_key(), "watchlist")) ++ .filter_map(|i| collection_at(vm.bx.heap.heap_key(), "watchlist", i)) + .any(|t| t == ticker); + vm.bx.heap.new_string_from_str(if held { "1" } else { "0" }) + }, +@@ -3360,7 +3372,7 @@ pub fn register_agent_module(vm: &mut ScriptVm) { + let field_v = script_value!(vm, args.field); + let mut field = String::new(); + vm.bx.heap.cast_to_string(field_v, &mut field); +- let Some(name) = collection_at("topics", index) else { ++ let Some(name) = collection_at(vm.bx.heap.heap_key(), "topics", index) else { + return vm.bx.heap.new_string_from_str("—"); + }; + // json_pluck walks a dot-path from the ROOT (it is not a text +@@ -3411,7 +3423,11 @@ pub fn register_agent_module(vm: &mut ScriptVm) { + id_lut!(link), + script_args_def!(field = NIL), + |vm, _args| { +- let url = LINK.read().map(|s| s.clone()).unwrap_or_default(); ++ let url = if crate::splash_policy::profile_allowed(vm.bx.heap.heap_key()) { ++ LINK.read().map(|s| s.clone()).unwrap_or_default() ++ } else { ++ String::new() ++ }; + vm.bx.heap.new_string_from_str(&url) + }, + ); +@@ -3725,7 +3741,9 @@ pub fn register_agent_module(vm: &mut ScriptVm) { + let field_v = script_value!(vm, args.field); + let mut field = String::new(); + vm.bx.heap.cast_to_string(field_v, &mut field); +- let fix = crate::makepad_draw::makepad_platform::gps::last_gps_fix(); ++ // Without the `location` grant this reads as "no fix yet", the ++ // state every card already handles. ++ let fix = crate::splash_policy::gps_fix_for_heap(vm.bx.heap.heap_key()); + match field.trim().to_ascii_lowercase().as_str() { + "ok" => ScriptValue::from_f64(if fix.is_some() { 1.0 } else { 0.0 }), + "lon" => ScriptValue::from_f64(fix.map(|f| f.lon).unwrap_or(-9999.0)), +@@ -3756,7 +3774,7 @@ pub fn register_agent_module(vm: &mut ScriptVm) { + let field_v = script_value!(vm, args.field); + let mut field = String::new(); + vm.bx.heap.cast_to_string(field_v, &mut field); +- let Some(ticker) = collection_at("watchlist", index) else { ++ let Some(ticker) = collection_at(vm.bx.heap.heap_key(), "watchlist", index) else { + // Past the end of the list. Empty rather than an em dash: a row + // that does not exist is not a row whose value failed to load. + return vm.bx.heap.new_string_from_str(""); +@@ -3819,14 +3837,14 @@ pub fn register_agent_module(vm: &mut ScriptVm) { + } else { + vm.bx.heap.cast_to_string(unit_value, &mut unit); + } +- let Some(name) = collection_at("cities", index) else { ++ let Some(name) = collection_at(vm.bx.heap.heap_key(), "cities", index) else { + return vm.bx.heap.new_string_from_str(""); + }; + // The one field that needs no network: it IS what was stored. + if field.trim() == "name" { + return vm.bx.heap.new_string_from_str(&name); + } +- let geo = geocode_url(&name); ++ let geo = geocode_url(&name, vm.bx.heap.heap_key()); + let (lat, lon) = match vm.host.cx_mut().script_data_fetch(&geo) { + None => { + let placeholder = vm.host.cx_mut().script_data_placeholder(&geo); +@@ -3879,7 +3897,7 @@ pub fn register_agent_module(vm: &mut ScriptVm) { + sys, + id_lut!(citiesnum), + script_args_def!(), +- |_vm, _args| ScriptValue::from_f64(collection_len("cities") as f64), ++ |vm, _args| ScriptValue::from_f64(collection_len_for(vm.bx.heap.heap_key(), "cities") as f64), + ); + + vm.add_method( +@@ -3959,7 +3977,7 @@ pub fn register_agent_module(vm: &mut ScriptVm) { + sys, + id_lut!(watchlistnum), + script_args_def!(), +- |_vm, _args| ScriptValue::from_f64(collection_len("watchlist") as f64), ++ |vm, _args| ScriptValue::from_f64(collection_len_for(vm.bx.heap.heap_key(), "watchlist") as f64), + ); + + vm.set_injected_global(id!(sys), sys.into()); +@@ -4017,7 +4035,10 @@ fn locale_field(field: &str) -> String { + } + } + +-fn pref_at(field: &str) -> Option { ++fn pref_at(heap_key: usize, field: &str) -> Option { ++ if !crate::splash_policy::profile_allowed(heap_key) { ++ return None; ++ } + PREFS.read().ok()?.as_ref()?.get(field).cloned() + } + +@@ -4048,7 +4069,20 @@ pub fn collection_len(name: &str) -> usize { + .unwrap_or(0) + } + +-fn collection_at(name: &str, index: usize) -> Option { ++/// [`collection_len`] as a given isolate may see it: empty without the ++/// `profile` grant, like a user who saved nothing. ++fn collection_len_for(heap_key: usize, name: &str) -> usize { ++ if crate::splash_policy::profile_allowed(heap_key) { ++ collection_len(name) ++ } else { ++ 0 ++ } ++} ++ ++fn collection_at(heap_key: usize, name: &str, index: usize) -> Option { ++ if !crate::splash_policy::profile_allowed(heap_key) { ++ return None; ++ } + COLLECTIONS + .read() + .ok()? +@@ -5502,7 +5536,7 @@ fn nav_step_field(route: &ParsedNavRoute, d: f64, field: &str) -> String { + /// card's. Detecting CJK by codepoint range is enough: the alternative is asking + /// the generating model to romanise names, which is another thing for it to get + /// silently wrong. +-fn geocode_url(name: &str) -> String { ++fn geocode_url(name: &str, heap_key: usize) -> String { + let name = name.trim(); + // An EMPTY name means "where the device is" — the weather exemplar's + // `state city { shape: text, initial: "" }` is documented as exactly +@@ -5516,7 +5550,9 @@ fn geocode_url(name: &str) -> String { + // search, which resolves to nothing and keeps the placeholder path: + // never invent a place. + if name.is_empty() { +- if let Some(fix) = crate::makepad_draw::makepad_platform::gps::last_gps_fix() { ++ // The fix only for an app granted `location`: a blank name must not ++ // become a way to learn where the device is without asking. ++ if let Some(fix) = crate::splash_policy::gps_fix_for_heap(heap_key) { + return format!( + "https://photon.komoot.io/reverse?lat={:.2}&lon={:.2}&lang=en", + fix.lat, fix.lon +@@ -5894,3 +5930,45 @@ pub(crate) fn sim_clock_secs() -> f64 { + static START: OnceLock = OnceLock::new(); + START.get_or_init(std::time::Instant::now).elapsed().as_secs_f64() + } ++ ++#[cfg(test)] ++mod policy_gate_tests { ++ use super::*; ++ ++ /// `sys.gps(...)`, `sys.watchlistnum()` and `agent.notify` run inside a ++ /// real isolate: first unpoliced (the host's own card), then under a ++ /// policy that lacks each grant, then with the grants. ++ #[test] ++ fn a_policed_isolate_needs_grants_for_location_the_users_lists_and_the_agent() { ++ let mut cx = Cx::new(Box::new(|_, _| {})); ++ cx.with_vm(crate::script_mod); ++ crate::makepad_draw::makepad_platform::gps::set_gps_fix(52.37, 4.89, 10.0); ++ set_collections(std::collections::BTreeMap::from([( ++ "watchlist".to_string(), ++ vec!["NVDA".to_string(), "AAPL".to_string()], ++ )])); ++ let vm_id = cx.alloc_splash_vm(); ++ let read = |cx: &mut Cx| { ++ cx.with_script_vm_id(vm_id, |vm| { ++ register_agent_module(vm); ++ let ok = vm.eval(crate::makepad_script::script! { sys.gps("ok") }).as_number(); ++ let lists = vm.eval(crate::makepad_script::script! { sys.watchlistnum() }).as_number(); ++ (ok, lists) ++ }) ++ }; ++ let heap = cx.with_script_vm_id(vm_id, |vm| vm.bx.heap.heap_key()); ++ ++ assert_eq!(read(&mut cx), (Some(1.0), Some(2.0)), "the host's own card sees both"); ++ ++ crate::splash_policy::set_policy_for_heap(heap, vec!["net".into()], vec![], None); ++ assert_eq!(read(&mut cx), (Some(0.0), Some(0.0)), "no grant reads as no fix and no saved lists"); ++ assert!(crate::splash_policy::service_allowed(heap, "agent.notify").is_err()); ++ ++ crate::splash_policy::set_policy_for_heap(heap, vec!["location".into(), "profile".into()], vec![], None); ++ assert_eq!(read(&mut cx), (Some(1.0), Some(2.0))); ++ ++ crate::splash_policy::gc_policies(&[heap]); ++ set_collections(Default::default()); ++ cx.free_splash_vm(vm_id); ++ } ++} +diff --git a/widgets/src/splash_host.rs b/widgets/src/splash_host.rs +index d1c7a0c25..81529af28 100644 +--- a/widgets/src/splash_host.rs ++++ b/widgets/src/splash_host.rs +@@ -136,6 +136,18 @@ pub fn take_splash_host_requests() -> Vec { + BRIDGE.with(|b| std::mem::take(&mut b.borrow_mut().queue)) + } + ++/// Drains only the requests from these isolates, leaving every other host's ++/// queued: a host that runs some isolates (an app runner) must not swallow ++/// the requests of isolates another host answers. ++pub fn take_splash_host_requests_for(heap_keys: &[usize]) -> Vec { ++ BRIDGE.with(|b| { ++ let mut b = b.borrow_mut(); ++ let (mine, rest): (Vec<_>, Vec<_>) = std::mem::take(&mut b.queue).into_iter().partition(|r| heap_keys.contains(&r.heap_key)); ++ b.queue = rest; ++ mine ++ }) ++} ++ + /// Answers one request: `Ok(json)` becomes `{ok: true, data: }`, + /// `Err(msg)` becomes `{ok: false, error: msg}`. Returns what happened, so a + /// host can log undeliverable answers instead of wondering why an app hangs. +@@ -171,7 +183,7 @@ pub fn splash_host_respond( + let data = parser.read_json(json, &mut vm.bx.heap); + (true, data, NIL) + } +- Err(msg) => (false, NIL, vm.new_string_with(|_vm, s| s.push_str(msg))), ++ Err(msg) => (false, NIL, vm.bx.heap.new_string_from_str(msg)), + }; + let obj = vm.bx.heap.new_object(); + let trap = vm.bx.threads.cur().trap.pass(); +@@ -198,6 +210,11 @@ pub fn splash_host_respond( + } + }); + }); ++ // What the callback asked of its widgets (`ui.list.render()`) is queued, ++ // and the queue is worked on the next event. An answer delivered after ++ // the host handled its event would otherwise wait for the person to ++ // touch the screen. ++ SignalToUI::set_ui_signal(); + if !contained { + // The callback panicked; the panic was contained and that isolate is + // suspect, but the host app carries on. +@@ -262,7 +279,7 @@ pub fn script_mod(vm: &mut ScriptVm) { + return match callback { + Some(callback) => { + let obj = vm.bx.heap.new_object(); +- let error = vm.new_string_with(|_vm, s| s.push_str(&reason)); ++ let error = vm.bx.heap.new_string_from_str(&reason); + let trap = vm.bx.threads.cur().trap.pass(); + vm.bx.heap.set_value(obj, id!(is_ok).into(), false.into(), trap); + vm.bx.heap.set_value(obj, id!(data).into(), NIL, trap); +@@ -371,6 +388,19 @@ fn take_drains_and_gc_drops_dead_state() { + }); + } + ++ #[test] ++ fn a_host_takes_only_its_own_isolates_requests() { ++ let _ = take_splash_host_requests(); ++ queue_raw(31, 1, "mail.list"); ++ queue_raw(32, 2, "weather.get"); ++ let mine = take_splash_host_requests_for(&[31]); ++ assert_eq!(mine.len(), 1); ++ assert_eq!(mine[0].service, "mail.list"); ++ let rest = take_splash_host_requests(); ++ assert_eq!(rest.len(), 1, "the other host's request stays queued"); ++ assert_eq!(rest[0].heap_key, 32); ++ } ++ + #[test] + fn tags_and_caps_are_per_heap() { + set_tag_for_heap(1, Some("one".into())); +diff --git a/widgets/src/splash_policy.rs b/widgets/src/splash_policy.rs +index 9bb895860..04cf78b36 100644 +--- a/widgets/src/splash_policy.rs ++++ b/widgets/src/splash_policy.rs +@@ -8,7 +8,13 @@ + //! + //! - [`service_allowed`] — before a `host.request` is queued (ADR 0002 §2); + //! - [`url_allowed`] — from every network path, through the gate installed +-//! in `makepad-script-std` (ADR 0002 §3); ++//! in `makepad-script-std` (ADR 0002 §3): `net.http_request`, ++//! `net.web_socket`, `sys.*` data fetches, artwork, map tiles, web cards ++//! and a `Video`'s network source; ++//! - [`sockets_allowed`] — before `net.http_server` listens or ++//! `net.socket_stream` connects. Neither names a URL a host list could ++//! judge, and no capability covers them (`net` is requests to the listed ++//! hosts), so a policed isolate gets neither; + //! - [`charge`] — after every evaluation and callback, against a cumulative + //! instruction budget (ADR 0002 §4). + //! +@@ -42,6 +48,9 @@ pub struct HeapPolicy { + /// Set (or replace) the policy for a heap. Enforcement starts here: a heap + /// that never had this called is not enforced. + pub fn set_policy_for_heap(heap_key: usize, capabilities: Vec, hosts: Vec, instruction_budget: Option) { ++ // A policy nothing consults is not a policy: whoever sets one, the gates ++ // that read it are in place from here on. ++ install_url_gate(); + POLICIES.with(|p| { + let mut p = p.borrow_mut(); + let used = p.get(&heap_key).map(|old| old.instructions_used).unwrap_or(0); +@@ -110,6 +119,139 @@ pub fn url_allowed(heap_key: usize, url: &str) -> bool { + }) + } + ++/// May this heap open a listening server or a raw socket? Only when it is ++/// not policed. A server answers whoever connects and a raw stream speaks any ++/// protocol to any port; a contained app's way out is a request to a listed ++/// host, and nothing wider. ++pub fn sockets_allowed(heap_key: usize) -> bool { ++ !is_enforced(heap_key) ++} ++ ++/// May this heap know where the device is? Location is a service family ++/// like any other, granted as `location`; an unpoliced heap keeps the old ++/// behaviour. Every reader of the platform fix on a script's behalf asks ++/// here: `sys.gps`, the blank-name geocode fallback, the map's follow camera. ++pub fn location_allowed(heap_key: usize) -> bool { ++ service_allowed(heap_key, "location.get").is_ok() ++} ++ ++/// The device's last GPS fix as this heap may see it: `None` without the ++/// `location` grant, exactly as if the device had no fix yet, so a card's ++/// no-fix path is also its no-permission path. ++pub fn gps_fix_for_heap(heap_key: usize) -> Option { ++ if location_allowed(heap_key) { ++ crate::makepad_draw::makepad_platform::gps::last_gps_fix() ++ } else { ++ None ++ } ++} ++ ++/// May this heap read what the user keeps in the host: saved lists ++/// (`sys.watchlist`, cities, reading, topics), stored preferences and the page ++/// open in the reader. These are published process-wide for the host's own ++/// cards; an app under a policy needs the `profile` grant to see them. ++pub fn profile_allowed(heap_key: usize) -> bool { ++ service_allowed(heap_key, "profile.read").is_ok() ++} ++ ++/// A file a widget was told to read, as this heap may read it. An unpoliced ++/// heap reads the path as given. A policed heap's paths are app-visible paths ++/// inside its storage jail, so a card cannot point a widget (a map archive, ++/// say) at a file of the host's; with no jail, it reads nothing. ++pub fn local_path_for_heap(heap_key: usize, path: &str) -> Option { ++ if !is_enforced(heap_key) { ++ return Some(path.to_string()); ++ } ++ let root = crate::splash_storage::root_for_heap(heap_key)?; ++ let real = crate::splash_storage::resolve_jailed(&root, path).ok()?; ++ crate::splash_storage::verify_no_symlinks(&root, &real).ok()?; ++ Some(real.to_string_lossy().into_owned()) ++} ++ ++/// The hosts a policed heap may reach, or `None` for an unpoliced heap. For ++/// a surface the gate cannot stand in front of — a system WebView fetches on ++/// its own — so it can be told the same list. ++pub fn hosts_for_heap(heap_key: usize) -> Option> { ++ POLICIES.with(|p| p.borrow().get(&heap_key).map(|policy| policy.hosts.clone())) ++} ++ ++/// The host of `url` when it is https and names a public host: not loopback, ++/// private, link-local, shared or unspecified, not a single-label or ++/// `.local`/`.internal`/`.localhost` name, and not an IP written in a form ++/// only some resolvers read as one (`0x7f.1`). What a grant to reach "any ++/// public page" may reach, and nothing on the device's own network. ++pub fn public_https_host(url: &str) -> Result { ++ if !url.get(..8).is_some_and(|scheme| scheme.eq_ignore_ascii_case("https://")) { ++ return Err("only https:// URLs are allowed".into()); ++ } ++ let host = makepad_script_std::url_host(url).ok_or("missing host")?; ++ let bare = host.trim_start_matches('[').trim_end_matches(']'); ++ if let Ok(ip) = bare.parse::() { ++ return if is_public_ip(ip) { Ok(host) } else { Err(format!("host not permitted (private/internal): {host}")) }; ++ } ++ let last = host.rsplit('.').next().unwrap_or(""); ++ let numeric = last.bytes().all(|b| b.is_ascii_digit()) || last.starts_with("0x"); ++ if numeric || !host.contains('.') || host.starts_with('[') { ++ return Err(format!("host not permitted (not a public name): {host}")); ++ } ++ if host == "localhost" || [".localhost", ".internal", ".local", ".lan", ".home.arpa"].iter().any(|s| host.ends_with(s)) { ++ return Err(format!("host not permitted (private/internal): {host}")); ++ } ++ Ok(host) ++} ++ ++fn is_public_ip(ip: std::net::IpAddr) -> bool { ++ use std::net::IpAddr; ++ match ip { ++ IpAddr::V4(v4) => { ++ let [a, b, ..] = v4.octets(); ++ !(v4.is_loopback() ++ || v4.is_private() ++ || v4.is_link_local() ++ || v4.is_unspecified() ++ || v4.is_broadcast() ++ || v4.is_documentation() ++ || v4.is_multicast() ++ || a == 0 ++ || (a == 100 && (64..128).contains(&b))) ++ } ++ IpAddr::V6(v6) => { ++ if let Some(v4) = v6.to_ipv4_mapped() { ++ return is_public_ip(IpAddr::V4(v4)); ++ } ++ let first = v6.segments()[0]; ++ !(v6.is_loopback() ++ || v6.is_unspecified() ++ || v6.is_multicast() ++ || (first & 0xfe00) == 0xfc00 ++ || (first & 0xffc0) == 0xfe80) ++ } ++ } ++} ++ ++/// May this heap load `url` as media (an image, artwork)? Its host list ++/// answers first; the `images` grant adds any public https host, for an app ++/// that shows pictures from wherever its content links (a feed reader's ++/// thumbnails). A load is still a request, so the grant is its own consent. ++pub fn media_allowed(heap_key: usize, url: &str) -> bool { ++ url_allowed(heap_key, url) ++ || (is_enforced(heap_key) ++ && may_run(heap_key) ++ && service_allowed(heap_key, "images.any").is_ok() ++ && public_https_host(url).is_ok()) ++} ++ ++/// May this heap open `url` as a page in the system WebView? Its host list ++/// answers first; the `web` grant adds any public https page, for a reader. ++/// Such a page gets no bridge into the app. ++pub fn page_allowed(heap_key: usize, url: &str) -> bool { ++ url_allowed(heap_key, url) ++ || (is_enforced(heap_key) ++ && may_run(heap_key) ++ && service_allowed(heap_key, "web.open").is_ok() ++ && public_https_host(url).is_ok()) ++} ++ + /// Record `instructions` run by this heap. Returns false once the budget is + /// spent, and stays false: the heap is exhausted from then on. + pub fn charge(heap_key: usize, instructions: u64) -> bool { +@@ -143,6 +285,8 @@ pub(crate) fn install_url_gate() { + INSTALLED.with(|installed| { + if !installed.get() { + makepad_script_std::set_script_url_gate(Some(url_allowed)); ++ makepad_script_std::set_script_media_gate(Some(media_allowed)); ++ makepad_script_std::set_script_socket_gate(Some(sockets_allowed)); + installed.set(true); + } + }); +@@ -226,6 +370,95 @@ fn replacing_a_policy_keeps_what_was_already_spent() { + gc_policies(&[905]); + } + ++ #[test] ++ fn location_needs_the_location_grant() { ++ gc_policies(&[908]); ++ assert!(location_allowed(908), "an unpoliced heap reads the fix as before"); ++ set_policy_for_heap(908, vec!["net".into()], vec![], None); ++ assert!(!location_allowed(908)); ++ assert!(gps_fix_for_heap(908).is_none(), "no grant reads as no fix"); ++ set_policy_for_heap(908, vec!["location".into()], vec![], None); ++ assert!(location_allowed(908)); ++ assert!(!profile_allowed(908), "location does not cover the user's saved lists"); ++ set_policy_for_heap(908, vec!["profile".into()], vec![], None); ++ assert!(profile_allowed(908)); ++ gc_policies(&[908]); ++ } ++ ++ #[test] ++ fn a_policed_heap_reads_local_files_only_inside_its_jail() { ++ gc_policies(&[909]); ++ assert_eq!(local_path_for_heap(909, "/etc/hosts").as_deref(), Some("/etc/hosts")); ++ set_policy_for_heap(909, vec![], vec![], None); ++ assert!(local_path_for_heap(909, "maps/world.mkmap").is_none(), "no jail, no files"); ++ crate::splash_storage::set_root_for_heap(909, Some("/jail/app".into())); ++ assert_eq!( ++ local_path_for_heap(909, "maps/world.mkmap").as_deref(), ++ Some("/jail/app/maps/world.mkmap") ++ ); ++ assert!(local_path_for_heap(909, "../../etc/hosts").is_none(), "no climbing out"); ++ crate::splash_storage::set_root_for_heap(909, None); ++ gc_policies(&[909]); ++ } ++ ++ #[test] ++ fn a_public_host_is_https_and_off_the_devices_network() { ++ for ok in ["https://news.ycombinator.com/", "https://CDN.Example.org:8443/a.jpg", "https://8.8.8.8/", "https://[2606:4700::1111]/"] { ++ assert!(public_https_host(ok).is_ok(), "{ok}"); ++ } ++ for bad in [ ++ "http://example.com/", ++ "https://localhost/", ++ "https://127.0.0.1/", ++ "https://10.0.0.8/", ++ "https://172.20.1.1/", ++ "https://192.168.1.1/", ++ "https://169.254.169.254/latest/meta-data", ++ "https://100.64.0.1/", ++ "https://0.0.0.0/", ++ "https://[::1]/", ++ "https://[fd00::1]/", ++ "https://[fe80::1]/", ++ "https://[::ffff:127.0.0.1]/", ++ "https://0x7f.1/", ++ "https://2130706433/", ++ "https://router/", ++ "https://printer.local/", ++ "https://nas.home.arpa/", ++ "https://evil.com@127.0.0.1/", ++ ] { ++ assert!(public_https_host(bad).is_err(), "{bad}"); ++ } ++ } ++ ++ #[test] ++ fn images_and_web_grants_open_public_https_only() { ++ set_policy_for_heap(910, vec!["net".into()], vec!["hn.algolia.com".into()], None); ++ assert!(media_allowed(910, "https://hn.algolia.com/logo.png"), "its own hosts, as before"); ++ assert!(!media_allowed(910, "https://cdn.example.org/a.jpg")); ++ assert!(!page_allowed(910, "https://example.org/story")); ++ set_policy_for_heap(910, vec!["net".into(), "images".into(), "web".into()], vec!["hn.algolia.com".into()], None); ++ assert!(media_allowed(910, "https://cdn.example.org/a.jpg")); ++ assert!(page_allowed(910, "https://example.org/story")); ++ assert!(!media_allowed(910, "https://192.168.1.1/a.jpg"), "never the device's network"); ++ assert!(!page_allowed(910, "http://example.org/story"), "never plain http"); ++ assert!(!url_allowed(910, "https://cdn.example.org/a.jpg"), "neither grant widens requests"); ++ gc_policies(&[910]); ++ assert!(media_allowed(910, "https://anything.example/"), "an unpoliced heap, as before"); ++ } ++ ++ #[test] ++ fn a_policed_heap_opens_no_server_and_no_raw_socket_whatever_it_holds() { ++ gc_policies(&[911]); ++ assert!(sockets_allowed(911), "an unpoliced heap, as before"); ++ set_policy_for_heap(911, vec!["net".into(), "storage".into(), "web".into()], vec!["127.0.0.1".into()], None); ++ assert!(!sockets_allowed(911), "no grant and no listed host covers a socket"); ++ assert!(!makepad_script_std::script_sockets_allowed(911), "setting a policy installs the gate"); ++ assert!(makepad_script_std::script_url_allowed(911, "ws://127.0.0.1:9/"), "the url gate is in too"); ++ gc_policies(&[911]); ++ assert!(makepad_script_std::script_sockets_allowed(911)); ++ } ++ + #[test] + fn gc_forgets_a_heap_entirely() { + set_policy_for_heap(906, vec![], vec![], Some(1)); +diff --git a/widgets/src/splash_storage.rs b/widgets/src/splash_storage.rs +index 4b91cb6bf..778e6e745 100644 +--- a/widgets/src/splash_storage.rs ++++ b/widgets/src/splash_storage.rs +@@ -111,6 +111,19 @@ pub(crate) fn gc_roots(dead_heaps: &[usize]) { + }); + } + ++/// Whether a heap's jail is under its byte quota: what a native writer ++/// into the jail (the camera) checks before it adds a file. ++pub(crate) fn heap_has_room(heap_key: usize) -> bool { ++ let Some(root) = root_for_heap(heap_key) else { return false }; ++ let quota = JAIL_QUOTAS.with(|q| q.borrow().get(&heap_key).copied()).unwrap_or(MAX_TOTAL_BYTES); ++ jail_usage(&root).0 < quota ++} ++ ++/// The sandbox root assigned to a heap, if any. ++pub(crate) fn root_for_heap(heap_key: usize) -> Option { ++ SANDBOX_ROOTS.with(|r| r.borrow().get(&heap_key).cloned()) ++} ++ + fn root_for_vm(vm: &ScriptVm) -> Option { + let heap_key = vm.bx.heap.heap_key(); + SANDBOX_ROOTS.with(|r| r.borrow().get(&heap_key).cloned()) +@@ -155,7 +168,7 @@ pub fn resolve_jailed(root: &Path, virtual_path: &str) -> Result Result<(), String> { ++pub(crate) fn verify_no_symlinks(root: &Path, real: &Path) -> Result<(), String> { + let mut cursor = root.to_path_buf(); + let rel = real.strip_prefix(root).map_err(|_| "path escapes the app's storage")?; + for comp in rel.components() { +@@ -233,7 +246,21 @@ pub fn script_mod(vm: &mut ScriptVm) { + let path = script_value!(vm, args.path); + match target(vm, path) { + Ok((_root, real)) => match std::fs::read_to_string(&real) { +- Ok(text) => vm.new_string_with(|_vm, s| s.push_str(&text)).into(), ++ Ok(text) => vm.bx.heap.new_string_from_str(&text).into(), ++ Err(_) => script_err_io!(vm.trap(), "file not found"), ++ }, ++ Err(e) => script_err_io!(vm.trap(), "{}", e), ++ } ++ }); ++ ++ // `fs.read_bytes(path)`: a file as a byte array, for what is not text (a ++ // photo the camera took, to hand to an Image). Charged to the heap like ++ // any allocation. ++ vm.add_method(fs, id_lut!(read_bytes), script_args_def!(path = NIL), |vm, args| { ++ let path = script_value!(vm, args.path); ++ match target(vm, path) { ++ Ok((_root, real)) => match std::fs::read(&real) { ++ Ok(bytes) => vm.bx.heap.new_array_from_vec_u8(bytes).into(), + Err(_) => script_err_io!(vm.trap(), "file not found"), + }, + Err(e) => script_err_io!(vm.trap(), "{}", e), +diff --git a/widgets/src/text_input.rs b/widgets/src/text_input.rs +index dc0829f7f..5b60547f9 100644 +--- a/widgets/src/text_input.rs ++++ b/widgets/src/text_input.rs +@@ -525,6 +525,9 @@ + } + } + ++/// What a refused secret field shows instead of its placeholder. ++const SECRET_REFUSED: &str = "Apps can't ask for passwords"; ++ + #[derive(Script, Widget, Animator)] + pub struct TextInput { + #[uid] +@@ -890,6 +893,21 @@ pub fn empty_text(&self) -> &str { + &self.empty_text + } + ++ /// A contained app may not collect a secret. A password field (or one ++ /// that asks the keyboard for a password or a one-time code, which also ++ /// invites autofill) inside a policed isolate takes no input: secrets are ++ /// typed on the host's own sheet and go to the service that needs them, ++ /// never through the app. Host-owned isolates (a service's sheet) and the ++ /// shell's own UI are not policed and keep working. ++ pub(crate) fn refuses_secret(&self) -> bool { ++ let secret = self.is_password ++ || matches!( ++ self.content_type, ++ TextInputContentType::Password | TextInputContentType::NewPassword | TextInputContentType::OneTimeCode ++ ); ++ secret && crate::splash_policy::is_enforced(self.source.heap_key()) ++ } ++ + /// Text presented by the input, including its placeholder and masking. + pub fn display_text(&self) -> String { + if self.text.is_empty() { +@@ -1084,7 +1102,10 @@ fn layout_text(&mut self, cx: &mut Cx2d) { + + fn draw_text(&mut self, cx: &mut Cx2d) -> Rect { + let inner_walk = self.inner_walk(); +- let text_rect = if self.text.is_empty() { ++ let text_rect = if self.refuses_secret() { ++ self.draw_text ++ .draw_walk(cx, inner_walk, self.label_align, SECRET_REFUSED) ++ } else if self.text.is_empty() { + self.draw_text + .draw_walk(cx, inner_walk, self.label_align, &self.empty_text) + } else { +@@ -2484,6 +2505,14 @@ fn disabled(&self, cx: &Cx) -> bool { + } + + fn handle_event(&mut self, cx: &mut Cx, event: &Event, _scope: &mut Scope) { ++ if self.refuses_secret() { ++ // Never focused, so no keyboard opens and nothing is typed. ++ if !self.text.is_empty() { ++ self.text.clear(); ++ self.draw_bg.redraw(cx); ++ } ++ return; ++ } + if self.animator_handle_event(cx, event).must_redraw() { + self.draw_bg.redraw(cx); + self.draw_cursor.redraw(cx); +diff --git a/widgets/src/video.rs b/widgets/src/video.rs +index 1fee14b5e..55574072b 100644 +--- a/widgets/src/video.rs ++++ b/widgets/src/video.rs +@@ -1375,9 +1375,27 @@ fn maybe_prepare_playback(&mut self, cx: &mut Cx) { + return; + } + } +- VideoDataSource::Network { url } => VideoSource::Network(url.to_string()), ++ // The platform player fetches and reads on its own, so the ++ // isolate's policy is asked here, before it is handed a ++ // source: a network URL answers to the host list like any ++ // request, a path to the app's storage jail. ++ VideoDataSource::Network { url } => { ++ let heap_key = self.source_ref.heap_key(); ++ if !crate::splash_policy::url_allowed(heap_key, url) { ++ error!("Video source refused by the host's allowlist: {url}"); ++ self.should_prepare_playback = false; ++ return; ++ } ++ VideoSource::Network(url.to_string()) ++ } + VideoDataSource::Filesystem { path } => { +- VideoSource::Filesystem(path.to_string()) ++ let heap_key = self.source_ref.heap_key(); ++ let Some(path) = crate::splash_policy::local_path_for_heap(heap_key, path) else { ++ error!("Video source refused: {path} is outside this app's storage"); ++ self.should_prepare_playback = false; ++ return; ++ }; ++ VideoSource::Filesystem(path) + } + VideoDataSource::Camera { + input_id, +diff --git a/widgets/src/view.rs b/widgets/src/view.rs +index d02cf4902..ad333ec3e 100644 +--- a/widgets/src/view.rs ++++ b/widgets/src/view.rs +@@ -139,6 +139,10 @@ pub struct View { + script_async: ScriptAsyncCalls, + #[rust] + applying_style_render: bool, ++ /// An `on_render` result is being applied: its children are built fresh ++ /// rather than applied over the previous render's (see `on_after_apply`). ++ #[rust] ++ applying_render: bool, + #[rust] + item_tap_live: bool, + +@@ -214,6 +218,7 @@ fn on_after_apply( + if !apply.is_eval() { + if let Some(obj) = value.as_object() { + let mut anon_index = 0usize; ++ let rendering = self.applying_render; + vm.vec_with(obj, |vm, vec| { + for kv in vec { + // Determine the id: use prefixed id if available, otherwise use numbered id for anonymous children +@@ -240,7 +245,19 @@ fn on_after_apply( + if let Some((_, node)) = + self.children.iter_mut().find(|(id2, _)| *id2 == id) + { +- node.script_apply(vm, apply, scope, kv.value); ++ // An `on_render` result is built fresh, as the first ++ // render was. Applied over the previous render's ++ // instance, a child keeps what the new object does not ++ // restate: a slot that held a card with a picture keeps ++ // the picture's named child, and a label re-laid-out ++ // with longer text lost its wrap width. A render is a ++ // function of the app's state, not an edit of the ++ // last one. ++ if rendering { ++ *node = WidgetRef::script_from_value_scoped(vm, scope, kv.value); ++ } else { ++ node.script_apply(vm, apply, scope, kv.value); ++ } + } else { + let widget = + WidgetRef::script_from_value_scoped(vm, scope, kv.value); +@@ -854,6 +871,28 @@ fn script_call( + method: LiveId, + args: ScriptValue, + ) -> ScriptAsyncResult { ++ // `ui.panel.set_visible(false)`: show and hide from script, so an app ++ // can keep a stateful child (a reader's web view) instead of ++ // re-rendering it away. ++ if method == live_id!(set_visible) { ++ let mut visible = None; ++ if let Some(args_obj) = args.as_object() { ++ let trap = vm.bx.threads.cur().trap.pass(); ++ visible = vm.bx.heap.vec_value(args_obj, 0, trap).as_bool(); ++ } ++ if let Some(visible) = visible { ++ if visible != self.visible { ++ self.visible = visible; ++ // A view that was hidden has no drawn area to invalidate, ++ // so its own redraw would repaint nothing. ++ vm.with_cx_mut(|cx| cx.redraw_all()); ++ } ++ } ++ return ScriptAsyncResult::Return(NIL); ++ } ++ if method == live_id!(is_visible) { ++ return ScriptAsyncResult::Return(self.visible.into()); ++ } + if method == live_id!(render) || method == live_id!(render_style) { + // `me` protos off `self.source`, and the caller's `args` object + // travels into the VM that owns `on_render` — both are heap values, +@@ -920,8 +959,10 @@ fn script_result(&mut self, vm: &mut ScriptVm, id: ScriptAsyncId, result: Script + let declaration = self.source.clone(); + let style=call.method()==id!(render_style); + self.applying_style_render=style; ++ self.applying_render=!style; + self.script_apply(vm, &if style {Apply::ScriptReapply}else{Apply::Reload}, &mut Scope::empty(), me_obj.into()); + self.applying_style_render=false; ++ self.applying_render=false; + self.source = declaration; + self.redraw(vm.cx_mut()); + } +diff --git a/widgets/src/web_card.rs b/widgets/src/web_card.rs +index 952bbfa3f..7a1dd6b16 100644 +--- a/widgets/src/web_card.rs ++++ b/widgets/src/web_card.rs +@@ -95,42 +95,7 @@ fn inject_widget_kit(html: &str) -> String { + /// adds the allowlist) and `download` (which doesn't — its bytes land in the + /// sandbox, so the SSRF block is the protection that matters). + fn check_https_public(url: &str) -> Result { +- let rest = url +- .strip_prefix("https://") +- .ok_or_else(|| "only https:// URLs are allowed".to_string())?; +- let host = rest +- .split(|c| c == '/' || c == '?' || c == '#') +- .next() +- .unwrap_or("") +- .split('@') +- .next_back() +- .unwrap_or("") +- .split(':') +- .next() +- .unwrap_or("") +- .to_ascii_lowercase(); +- if host.is_empty() { +- return Err("missing host".into()); +- } +- let blocked_prefix = ["127.", "10.", "192.168.", "169.254.", "0."]; +- let private_172 = host.starts_with("172.") +- && host +- .split('.') +- .nth(1) +- .and_then(|o| o.parse::().ok()) +- .map(|o| (16..=31).contains(&o)) +- .unwrap_or(false); +- if host == "localhost" +- || host == "::1" +- || host.ends_with(".localhost") +- || host.ends_with(".internal") +- || host.ends_with(".local") +- || blocked_prefix.iter().any(|p| host.starts_with(p)) +- || private_172 +- { +- return Err(format!("host not permitted (private/internal): {}", host)); +- } +- Ok(host) ++ crate::splash_policy::public_https_host(url) + } + + /// Enforce the `http.fetch` capability gate: SSRF guard + the host must be on the +@@ -148,6 +113,54 @@ fn fetch_host_allowed(url: &str) -> Result<(), String> { + } + } + ++/// The bridge tool a card under a policy may call, and the grant it needs. ++/// `None` = never, under a policy: the WebView bridge's files and downloads ++/// live outside the app's jail, and its share sheet and notices are the host's ++/// to raise. An unpoliced card (the host's own) keeps every tool. ++fn policed_tool_grant(tool: &str) -> Option<&'static str> { ++ match tool { ++ "ping" => Some(""), ++ "http.fetch" => Some("net.fetch"), ++ "clipboard.write" => Some("clipboard.write"), ++ _ => None, ++ } ++} ++ ++/// A Content-Security-Policy that holds a policed card's own document to its ++/// host list. The WebView fetches for itself, out of reach of the script ++/// gate; this is how the same allowlist follows it there. Inline script and ++/// style stay allowed (the kit and the card are inline); nothing may frame, ++/// and nothing loads from a host the manifest does not name. ++fn policy_csp(hosts: &[String]) -> String { ++ let mut sources = String::new(); ++ for host in hosts { ++ // A host list entry is a bare host (any port) or host:port. ++ if host.chars().all(|c| c.is_ascii_alphanumeric() || matches!(c, '.' | '-' | ':' | '[' | ']')) { ++ sources.push_str(" https://"); ++ sources.push_str(host); ++ } ++ } ++ format!( ++ "default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'{sources}; \ ++ img-src data: blob:{sources}; media-src blob:{sources}; font-src data:{sources}; \ ++ connect-src{none}{sources}; frame-src 'none'; form-action 'none'; base-uri 'none'", ++ none = if sources.is_empty() { " 'none'" } else { "" }, ++ ) ++} ++ ++/// Put `csp` at the very start of the head, ahead of anything that could ++/// fetch, so it governs the whole document. ++fn inject_csp(html: &str, csp: &str) -> String { ++ let tag = format!(""); ++ match html.find("") { ++ Some(i) => { ++ let at = i + "".len(); ++ format!("{}{}{}", &html[..at], tag, &html[at..]) ++ } ++ None => format!("{tag}{html}"), ++ } ++} ++ + script_mod! { + use mod.prelude.widgets_internal.* + +@@ -350,6 +363,11 @@ fn load_settled(&mut self, cx: &mut Cx) { + // inline HTML — used to isolate loadHTMLString from the overlay path. + if let Some(url) = self.html.trim().strip_prefix("URLTEST:") { + let url = url.trim().to_string(); ++ if !crate::splash_policy::page_allowed(self.source.heap_key(), &url) { ++ log!("web_card navigation refused by the host's allowlist: {url}"); ++ self.loaded_html = self.html.clone(); ++ return; ++ } + if !self.spawned { + cx.system_browser(id).spawn(&url); + self.spawned = true; +@@ -366,7 +384,10 @@ fn load_settled(&mut self, cx: &mut Cx) { + cx.system_browser(id).spawn("about:blank"); + self.spawned = true; + } +- let html = inject_widget_kit(&self.html); ++ let mut html = inject_widget_kit(&self.html); ++ if let Some(hosts) = crate::splash_policy::hosts_for_heap(self.source.heap_key()) { ++ html = inject_csp(&html, &policy_csp(&hosts)); ++ } + let base = self.base_url_or_default().to_string(); + cx.system_browser(id).set_html(&html, &base); + self.loaded_html = self.html.clone(); +@@ -396,6 +417,19 @@ fn reject(&mut self, cx: &mut Cx, call_id: i64, msg: &str) { + + /// Dispatch one `octos.invoke(tool, args)`. `args` is a JSON string from the card. + fn handle_invoke(&mut self, cx: &mut Cx, call_id: i64, tool: &str, args: &str) { ++ let heap_key = self.source.heap_key(); ++ if crate::splash_policy::is_enforced(heap_key) { ++ let allowed = match policed_tool_grant(tool) { ++ Some("") => Ok(()), ++ Some(grant) => crate::splash_policy::service_allowed(heap_key, grant), ++ None => Err(format!("{tool} is not available to installed apps")), ++ }; ++ if let Err(why) = allowed { ++ log!("web_card {tool} DENIED: {why}"); ++ self.reject(cx, call_id, &format!("{tool} denied: {why}")); ++ return; ++ } ++ } + match tool { + // Round-trip probe: echo the args object back untouched. + "ping" => { +@@ -407,7 +441,19 @@ fn handle_invoke(&mut self, cx: &mut Cx, call_id: i64, tool: &str, args: &str) { + // a host allowlist + SSRF guard before any request leaves the device. + "http.fetch" => match FetchArgs::deserialize_json(args) { + Ok(a) => { +- if let Err(why) = fetch_host_allowed(&a.url) { ++ let allowed = if crate::splash_policy::is_enforced(heap_key) { ++ // The app's own host list, still https and public. ++ check_https_public(&a.url).and_then(|_| { ++ if crate::splash_policy::url_allowed(heap_key, &a.url) { ++ Ok(()) ++ } else { ++ Err("host not on this app's allowlist".to_string()) ++ } ++ }) ++ } else { ++ fetch_host_allowed(&a.url) ++ }; ++ if let Err(why) = allowed { + log!("web_card http.fetch DENIED: {} ({})", a.url, why); + self.reject(cx, call_id, &format!("http.fetch denied: {}", why)); + } else { +@@ -715,6 +761,18 @@ fn draw_walk(&mut self, cx: &mut Cx2d, _scope: &mut Scope, walk: Walk) -> DrawSt + + // A DSL-set `url` (no inline html) navigates once on first draw, so a + // runsplash card can embed a live web pane via `WebCard{ url: "…" }`. ++ if self.html.is_empty() ++ && !self.url.is_empty() ++ && self.loaded_html != self.url ++ && !crate::splash_policy::page_allowed(self.source.heap_key(), &self.url) ++ { ++ // A card under a policy opens only pages on its host list, or ++ // any public https page with the `web` grant. The top-level ++ // document is what the gate can see; the page's own subresources ++ // are the page's, as in any browser, and it gets no bridge. ++ log!("web_card navigation refused by the host's allowlist: {}", self.url); ++ self.loaded_html = self.url.clone(); ++ } + if self.html.is_empty() && !self.url.is_empty() && self.loaded_html != self.url { + let id = self.browser_id(); + if !self.spawned { +@@ -774,3 +832,27 @@ pub fn detach(&self, cx: &mut Cx) { + } + } + } ++ ++#[cfg(test)] ++mod policy_tests { ++ use super::*; ++ ++ #[test] ++ fn a_policed_document_may_reach_only_its_hosts() { ++ let csp = policy_csp(&["api.example.com".into(), "127.0.0.1:5000".into(), "evil.com; script-src *".into()]); ++ assert!(csp.contains("connect-src https://api.example.com https://127.0.0.1:5000;")); ++ assert!(!csp.contains("evil.com"), "a malformed host cannot extend the policy"); ++ assert!(policy_csp(&[]).contains("connect-src 'none';")); ++ let html = inject_csp("x", &policy_csp(&[])); ++ assert!(html.starts_with(" the web views that heap's readers spawned. ++ static OPENED: std::cell::RefCell>> = Default::default(); ++} ++ ++/// Close every web view opened by readers in these (dead) heaps. ++pub(crate) fn gc_web_readers(cx: &mut Cx, dead_heaps: &[usize]) { ++ let ids: Vec = OPENED.with(|o| { ++ let mut o = o.borrow_mut(); ++ dead_heaps.iter().filter_map(|heap| o.remove(heap)).flatten().collect() ++ }); ++ for id in ids { ++ let mut browser = cx.system_browser(id); ++ browser.detach(); ++ browser.close(); ++ } ++} ++ ++#[derive(Script, ScriptHook, Widget)] ++pub struct WebReader { ++ #[deref] ++ view: View, ++ /// Called when a page is refused or fails to load; `error()` says why. ++ #[live] ++ on_error: ScriptFnRef, ++ ++ #[rust] ++ last_error: String, ++ #[rust] ++ url: Option, ++ #[rust] ++ spawned: bool, ++ #[rust] ++ open: bool, ++ #[rust] ++ overlay_visible: bool, ++ #[rust] ++ timer: Option, ++ #[rust] ++ drawn: bool, ++ #[rust] ++ failed: bool, ++} ++ ++impl WebReader { ++ /// Each reader owns its web view: its uid is unique in the process. ++ fn browser_id(&self) -> SystemBrowserId { ++ SystemBrowserId(LiveId(self.widget_uid().0)) ++ } ++ ++ fn heap_key(&self) -> usize { ++ self.view.source.heap_key() ++ } ++ ++ fn report(&mut self, cx: &mut Cx, why: String) { ++ log!("web reader: {why}"); ++ self.last_error = why; ++ if self.on_error.as_object() != ScriptObject::ZERO { ++ cx.widget_to_script_call(self.widget_uid(), NIL, self.view.source.clone(), self.on_error.clone(), &[]); ++ } ++ } ++ ++ /// Open `url`, when this app may. Returns whether it opened. ++ pub fn open(&mut self, cx: &mut Cx, url: &str) -> bool { ++ let heap = self.heap_key(); ++ if !crate::splash_policy::page_allowed(heap, url) { ++ self.report(cx, format!("refused {url}: not on this app's host list, and no `web` grant")); ++ return false; ++ } ++ // Navigable only for an app that may reach any public page: its ++ // links and redirects stay inside what it was granted. ++ let navigable = !crate::splash_policy::is_enforced(heap) ++ || crate::splash_policy::service_allowed(heap, "web.open").is_ok(); ++ let id = self.browser_id(); ++ if navigable { ++ cx.system_browser(id).spawn_navigable(url); ++ } else { ++ cx.system_browser(id).spawn(url); ++ } ++ if !self.spawned { ++ OPENED.with(|o| o.borrow_mut().entry(heap).or_default().push(id)); ++ } ++ self.spawned = true; ++ self.failed = false; ++ self.url = Some(url.to_string()); ++ self.open = true; ++ self.overlay_visible = true; ++ self.drawn = true; ++ if self.timer.is_none() { ++ self.timer = Some(cx.start_interval(WATCHDOG_SECONDS)); ++ } ++ self.view.redraw(cx); ++ true ++ } ++ ++ pub fn close(&mut self, cx: &mut Cx) { ++ if !self.open { ++ return; ++ } ++ self.open = false; ++ self.hide_overlay(cx); ++ if let Some(timer) = self.timer.take() { ++ cx.stop_timer(timer); ++ } ++ self.view.redraw(cx); ++ } ++ ++ fn hide_overlay(&mut self, cx: &mut Cx) { ++ if self.spawned && self.overlay_visible { ++ let area = self.view.area(); ++ cx.system_browser(self.browser_id()).update(area, false); ++ } ++ self.overlay_visible = false; ++ self.drawn = false; ++ } ++} ++ ++impl Widget for WebReader { ++ fn script_call(&mut self, vm: &mut ScriptVm, method: LiveId, args: ScriptValue) -> ScriptAsyncResult { ++ if method == live_id!(open) { ++ let mut url = None; ++ if let Some(args_obj) = args.as_object() { ++ let trap = vm.bx.threads.cur().trap.pass(); ++ let value = vm.bx.heap.vec_value(args_obj, 0, trap); ++ url = vm.bx.heap.cast_to_owned_string(value, "copying a reader url"); ++ } ++ let Some(url) = url else { return ScriptAsyncResult::Return(FALSE) }; ++ let opened = vm.with_cx_mut(|cx| self.open(cx, url.trim())); ++ return ScriptAsyncResult::Return(opened.into()); ++ } ++ if method == live_id!(close) { ++ vm.with_cx_mut(|cx| self.close(cx)); ++ return ScriptAsyncResult::Return(NIL); ++ } ++ if method == live_id!(is_open) { ++ return ScriptAsyncResult::Return(self.open.into()); ++ } ++ if method == live_id!(error) { ++ return ScriptAsyncResult::Return(vm.bx.heap.new_string_from_str(&self.last_error)); ++ } ++ if method == live_id!(url) { ++ let url = vm.bx.heap.new_string_from_str(self.url.as_deref().unwrap_or("")); ++ return ScriptAsyncResult::Return(url); ++ } ++ self.view.script_call(vm, method, args) ++ } ++ ++ fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) { ++ self.view.handle_event(cx, event, scope); ++ if self.timer.is_some_and(|timer| timer.is_event(event).is_some()) { ++ // Drawn since the last tick: ask for the redraw the next one ++ // looks for. Not drawn: the tile is off screen; hide. ++ if self.drawn { ++ self.drawn = false; ++ self.view.redraw(cx); ++ } else { ++ self.hide_overlay(cx); ++ } ++ } ++ if let Event::Actions(actions) = event { ++ for action in actions { ++ if let Some(err) = action.downcast_ref::() { ++ if err.browser_id == self.browser_id().0.get_value() && self.open { ++ self.failed = true; ++ self.hide_overlay(cx); ++ let why = format!("could not load {}: {}", self.url.as_deref().unwrap_or(""), err.description); ++ self.report(cx, why); ++ } ++ } ++ } ++ } ++ } ++ ++ fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep { ++ let step = self.view.draw_walk(cx, scope, walk); ++ if step.is_done() && self.spawned && (self.open || self.overlay_visible) { ++ let show = self.open && !self.failed; ++ let area = self.view.area(); ++ cx.system_browser(self.browser_id()).update(area, show); ++ if show { ++ self.overlay_visible = true; ++ self.drawn = true; ++ } ++ } ++ step ++ } ++} +diff --git a/widgets/src/widget.rs b/widgets/src/widget.rs +index 09209b1cb..b6e496a1b 100644 +--- a/widgets/src/widget.rs ++++ b/widgets/src/widget.rs +@@ -787,6 +787,28 @@ pub fn script_call( + if method == live_id!(visible) { + return ScriptAsyncResult::Return(self.visible().into()); + } ++ // Text likewise: a widget with `Widget::set_text` (an `Html` view's ++ // body, say) takes it from script without a `script_call` of its own. ++ if method == live_id!(set_text) { ++ let Some(args_obj) = args.as_object() else { ++ return ScriptAsyncResult::Return(NIL); ++ }; ++ let trap = vm.bx.threads.cur().trap.pass(); ++ let value = vm.bx.heap.vec_value(args_obj, 0, trap); ++ let Some(text) = vm.bx.heap.cast_to_owned_string(value, "copying widget text") else { ++ return ScriptAsyncResult::Return(script_err_wrong_value!( ++ vm.trap(), ++ "set_text expects a string, got {:?}", ++ value.value_type() ++ )); ++ }; ++ vm.with_cx_mut(|cx| self.set_text(cx, &text)); ++ return ScriptAsyncResult::Return(NIL); ++ } ++ if method == live_id!(text) { ++ let text = self.text(); ++ return ScriptAsyncResult::Return(vm.bx.heap.new_string_from_str(&text)); ++ } + ScriptAsyncResult::MethodNotFound + } + +diff --git a/widgets/src/widget_async.rs b/widgets/src/widget_async.rs +index 63acffe8c..ffa296b59 100644 +--- a/widgets/src/widget_async.rs ++++ b/widgets/src/widget_async.rs +@@ -95,6 +95,58 @@ pub fn register_splash_isolate_mod(f: fn(&mut ScriptVm)) { + HOST_ISOLATE_MODS.with(|g| g.borrow_mut().push(f)); + } + ++thread_local! { ++ /// Host mods that only isolates holding a grant receive, with the grant. ++ static GRANTED_ISOLATE_MODS: RefCell> = const { RefCell::new(Vec::new()) }; ++ /// heap key -> indices into `GRANTED_ISOLATE_MODS` already installed there. ++ static GRANTED_INSTALLED: RefCell>> = RefCell::new(HashMap::new()); ++} ++ ++/// Install a script mod only into the isolates granted `grant`. ++/// ++/// [`register_splash_isolate_mod`] reaches every isolate, so vocabulary a host ++/// registers for its own cards also lands in every installed app. A mod ++/// registered here is installed into an isolate under a policy only when that ++/// policy grants `grant` (a capability family such as `agent`, or an exact ++/// service name), and into an unpoliced isolate (the host's own surfaces) ++/// always. ++/// ++/// The grant is known only once the host has seated the policy, which is ++/// after allocation, so these mods install when a Splash first evaluates its ++/// body, before the body runs. A later policy that drops the grant does not ++/// remove a mod already installed: set the policy before the body loads. ++pub fn register_splash_isolate_mod_for(grant: &'static str, f: fn(&mut ScriptVm)) { ++ GRANTED_ISOLATE_MODS.with(|g| g.borrow_mut().push((grant, f))); ++} ++ ++/// Install the granted mods this isolate is entitled to and does not yet ++/// have. Cheap when there is nothing new to install. ++pub(crate) fn apply_granted_isolate_mods(vm: &mut ScriptVm) { ++ let heap_key = vm.bx.heap.heap_key(); ++ let mods = GRANTED_ISOLATE_MODS.with(|mods| mods.borrow().clone()); ++ for (index, (grant, install)) in mods.into_iter().enumerate() { ++ let installed = GRANTED_INSTALLED.with(|i| i.borrow().get(&heap_key).is_some_and(|done| done.contains(&index))); ++ if installed { ++ continue; ++ } ++ let entitled = !crate::splash_policy::is_enforced(heap_key) ++ || crate::splash_policy::service_allowed(heap_key, grant).is_ok(); ++ if entitled { ++ install(vm); ++ GRANTED_INSTALLED.with(|i| i.borrow_mut().entry(heap_key).or_default().push(index)); ++ } ++ } ++} ++ ++/// Forget which granted mods a heap has, so the next ++/// [`apply_granted_isolate_mods`] installs them again: a theme rebuild ++/// replaces the modules they bound into. ++pub(crate) fn forget_granted_isolate_mods(heap_key: usize) { ++ GRANTED_INSTALLED.with(|i| { ++ i.borrow_mut().remove(&heap_key); ++ }); ++} ++ + /// Reinstall trusted host vocabulary after a theme rebuild replaces mod.widgets. + /// Clone first so an installer can register another module without borrowing the list. + pub(crate) fn apply_splash_isolate_mods(vm: &mut ScriptVm) { +@@ -156,6 +208,13 @@ pub fn gc_dead_splash_isolates(cx: &mut Cx) { + crate::splash_storage::gc_roots(&dead_heaps); + crate::splash_host::gc_bridge(&dead_heaps); + crate::splash_policy::gc_policies(&dead_heaps); ++ for heap in &dead_heaps { ++ forget_granted_isolate_mods(*heap); ++ } ++ // Web views and cameras an app opened do not outlive it. ++ for heap in &dead_heaps { ++ crate::camera_preview::release_isolate_devices(cx, *heap); ++ } + crate::desktop_style::gc_heaps(cx,&dead_heaps); + // And the resource cache, which is keyed by heap ADDRESS: dropping a heap + // frees that address for the next isolate, and a leftover entry would hand +@@ -494,7 +553,8 @@ fn alloc_splash_vm_with_network(&mut self, network_enabled: bool) -> SplashVmId + // loads) and the network (web_url/http resources) without going + // through the gated net runtime. Raw sockets are gated separately: + // the stdlib's `net.socket_stream` errors when no net runtime is +- // configured, same as `net.http_request`. ++ // configured, same as `net.http_request`, and under a policy it ++ // and `net.http_server` are refused outright (splash_policy). + // `cx.quit` would let any mini-app close the whole host process. + let strip = crate::makepad_script::script! { + mod.fs = nil +@@ -1794,6 +1854,135 @@ fn install_probe(vm: &mut ScriptVm) { + cx.free_splash_vm(third); + } + ++ #[test] ++ fn a_granted_mod_reaches_only_the_isolates_holding_its_grant() { ++ fn install_granted_probe(vm: &mut ScriptVm) { ++ vm.eval(crate::makepad_script::script! { ++ mod.granted_probe = 7 ++ }); ++ } ++ let mut cx = Cx::new(Box::new(|_, _| {})); ++ cx.with_vm(crate::script_mod); ++ register_splash_isolate_mod_for("agent", install_granted_probe); ++ ++ let has_probe = |cx: &mut Cx, vm_id| { ++ cx.with_script_vm_id(vm_id, |vm| { ++ apply_granted_isolate_mods(vm); ++ !vm.eval(crate::makepad_script::script! { mod.granted_probe }).is_err() ++ }) ++ }; ++ let heap = |cx: &mut Cx, vm_id| cx.with_script_vm_id(vm_id, |vm| vm.bx.heap.heap_key()); ++ ++ let host_own = cx.alloc_splash_vm(); ++ let denied = cx.alloc_splash_vm(); ++ let granted = cx.alloc_splash_vm(); ++ let denied_heap = heap(&mut cx, denied); ++ let granted_heap = heap(&mut cx, granted); ++ crate::splash_policy::set_policy_for_heap(denied_heap, vec!["net".into()], vec![], None); ++ crate::splash_policy::set_policy_for_heap(granted_heap, vec!["agent".into()], vec![], None); ++ ++ assert!(has_probe(&mut cx, host_own), "an unpoliced isolate is the host's own"); ++ assert!(!has_probe(&mut cx, denied), "a policy without the grant never sees it"); ++ assert!(has_probe(&mut cx, granted)); ++ ++ crate::splash_policy::gc_policies(&[denied_heap, granted_heap]); ++ cx.free_splash_vm(host_own); ++ cx.free_splash_vm(denied); ++ cx.free_splash_vm(granted); ++ } ++ ++ /// A probe found `net.web_socket`, `net.socket_stream` and ++ /// `net.http_server` skipping the host list that `net.http_request` ++ /// answers to. Each is tried from a host-owned isolate and from a ++ /// contained app holding `net` and a listed host. ++ #[test] ++ fn a_contained_app_opens_sockets_and_servers_only_as_its_policy_allows() { ++ let run = |cx: &mut Cx, vm_id, code: String| { ++ cx.with_script_vm_id(vm_id, |vm| { ++ let script_mod = crate::makepad_script::ScriptMod { ++ cargo_manifest_path: String::new(), ++ module_path: "net_policy_probe".into(), ++ file: "net_policy_probe".into(), ++ line: 0, ++ column: 0, ++ code, ++ values: vec![], ++ }; ++ !vm.eval(script_mod).is_err() ++ }) ++ }; ++ let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); ++ let port = listener.local_addr().unwrap().port(); ++ let web_socket = |host: &str| format!( ++ "use mod.net\nnet.web_socket(\"ws://{host}:{port}/\" net.WebSocketEvents{{}})" ++ ); ++ let raw_socket = format!( ++ "use mod.net\nnet.socket_stream(net.SocketStreamOptions{{host: \"127.0.0.1\" port: \"{port}\"}})" ++ ); ++ let server = "use mod.net\nnet.http_server(net.HttpServerOptions{listen: \"127.0.0.1:0\"} net.HttpServerEvents{})".to_string(); ++ ++ let mut cx = Cx::new(Box::new(|_, _| {})); ++ cx.with_vm(crate::script_mod); ++ let host_own = cx.alloc_splash_vm_with_network(true); ++ let app = cx.alloc_splash_vm_with_network(true); ++ let app_heap = cx.with_script_vm_id(app, |vm| vm.bx.heap.heap_key()); ++ crate::splash_policy::set_policy_for_heap(app_heap, vec!["net".into()], vec!["127.0.0.1".into()], None); ++ ++ assert!(run(&mut cx, host_own, web_socket("127.0.0.1")), "a host-owned isolate opens a web socket"); ++ assert!(run(&mut cx, host_own, web_socket("elsewhere.example")), "to any host, as before"); ++ assert!(run(&mut cx, host_own, raw_socket.clone()), "and a raw socket"); ++ assert!(run(&mut cx, host_own, server.clone()), "and listens"); ++ ++ assert!(run(&mut cx, app, web_socket("127.0.0.1")), "an app reaches its listed host over a web socket"); ++ assert!(!run(&mut cx, app, web_socket("elsewhere.example")), "and no other"); ++ assert!(!run(&mut cx, app, raw_socket.clone()), "a raw socket is refused even to a listed host"); ++ assert!(!run(&mut cx, app, server.clone()), "and so is a listening server"); ++ ++ crate::splash_policy::set_policy_for_heap(app_heap, vec!["net".into()], vec![], None); ++ assert!(!run(&mut cx, app, web_socket("127.0.0.1")), "an empty host list reaches nothing"); ++ ++ crate::splash_policy::gc_policies(&[app_heap]); ++ cx.free_splash_vm(host_own); ++ cx.free_splash_vm(app); ++ drop(listener); ++ } ++ ++ #[test] ++ fn a_contained_app_cannot_draw_a_password_field() { ++ let mut cx = Cx::new(Box::new(|_, _| {})); ++ cx.with_vm(crate::script_mod); ++ let field = |cx: &mut Cx, vm_id| { ++ cx.with_script_vm_id(vm_id, |vm| { ++ let v = vm.eval(crate::makepad_script::script! { ++ use mod.prelude.widgets.* ++ View{ ++ secret := TextInput{is_password: true} ++ plain := TextInput{} ++ code := TextInput{content_type: TextInputContentType.OneTimeCode} ++ } ++ }); ++ WidgetRef::script_from_value(vm, v) ++ }) ++ }; ++ let refuses = |cx: &Cx, view: &WidgetRef, id| view.widget(cx, &[id]).borrow::().unwrap().refuses_secret(); ++ let host_own = cx.alloc_splash_vm(); ++ let app = cx.alloc_splash_vm(); ++ let app_heap = cx.with_script_vm_id(app, |vm| vm.bx.heap.heap_key()); ++ crate::splash_policy::set_policy_for_heap(app_heap, vec!["storage".into()], vec![], None); ++ ++ let sheet = field(&mut cx, host_own); ++ assert!(!refuses(&cx, &sheet, live_id!(secret)), "a host-owned sheet collects the password"); ++ let contained = field(&mut cx, app); ++ assert!(refuses(&cx, &contained, live_id!(secret)), "an app's password field takes nothing"); ++ assert!(refuses(&cx, &contained, live_id!(code)), "nor a one-time-code field, which invites autofill"); ++ assert!(!refuses(&cx, &contained, live_id!(plain))); ++ ++ drop((sheet, contained)); ++ crate::splash_policy::gc_policies(&[app_heap]); ++ cx.free_splash_vm(host_own); ++ cx.free_splash_vm(app); ++ } ++ + #[test] + fn entering_an_isolate_makes_it_the_current_vm_and_leaving_restores_the_outer_one() { + let mut cx = Cx::new(Box::new(|_, _| {})); +@@ -2067,6 +2256,67 @@ fn pane_refresh_drops_inserted_host() { + assert_eq!(n, 2, "render after pane refresh did not commit"); + } + ++ #[test] ++ fn a_rerender_builds_each_child_from_its_new_template() { ++ const RERENDER: &str = r#" ++ let items = [] ++ let WithExtra = View{ height: Fit extra := Label{ text: "x" } } ++ let Plain = View{ height: Fit } ++ fn load(){ ++ host.request("t.items", {}, fn(r){ ++ items = r.data.items ++ ui.item_list.render() ++ }) ++ } ++ item_list := View{ height: Fit, on_render: || { ++ for it in items { ++ if it.extra { WithExtra{} } else { Plain{} } ++ } ++ } } ++"#; ++ let mut cx = Cx::new(Box::new(|_, _| {})); ++ let template = cx.with_vm(|vm| { ++ crate::script_mod(vm); ++ let v = vm.eval(crate::makepad_script::script! { ++ use mod.prelude.widgets.* ++ use mod.widgets.* ++ View{ splash := Splash{} } ++ }); ++ vm.bx.heap.new_object_ref(v.as_object().unwrap()) ++ }); ++ let pane = cx.with_vm(|vm| { ++ let v = vm.eval(crate::makepad_script::script! { ++ use mod.prelude.widgets.* ++ View{ height: Fit } ++ }); ++ WidgetRef::script_from_value(vm, v) ++ }); ++ set_ui_root(&mut cx, &pane); ++ let host = cx.with_vm(|vm| WidgetRef::script_from_value(vm, template.as_object().into())); ++ cx.widget_tree_insert_child_deep(pane.widget_uid(), live_id!(apphost), host.clone()); ++ host.widget(&cx, &[live_id!(splash)]).set_text(&mut cx, RERENDER); ++ let item_list = host.widget(&cx, &[live_id!(item_list)]); ++ assert!(!item_list.is_empty()); ++ let splash = host.widget(&cx, &[live_id!(splash)]); ++ let render = |cx: &mut Cx, json: &str| { ++ assert!(splash.borrow_mut::().unwrap().call_script_fn(cx, live_id!(load), &[])); ++ pump_widget_async(cx); ++ let reqs = crate::splash_host::take_splash_host_requests(); ++ assert_eq!(reqs.len(), 1); ++ crate::splash_host::splash_host_respond(cx, reqs[0].heap_key, reqs[0].req_id, Ok(json)); ++ pump_widget_async(cx); ++ let first = item_list.borrow::().unwrap().children.first().map(|(_, w)| w.clone()).unwrap(); ++ !first.widget(cx, &[live_id!(extra)]).is_empty() ++ }; ++ assert!(render(&mut cx, r#"{"items":[{"extra":true}]}"#), "the first render has the extra child"); ++ assert!( ++ !render(&mut cx, r#"{"items":[{"extra":false}]}"#), ++ "a slot whose template changed keeps nothing of the old one" ++ ); ++ drop(host); ++ gc_dead_splash_isolates(&mut cx); ++ } ++ + #[test] + fn second_isolate_render_commits() { + let mut cx = Cx::new(Box::new(|_, _| {})); diff --git a/runtime-patches.lock.json b/runtime-patches.lock.json new file mode 100644 index 00000000..3c1db06b --- /dev/null +++ b/runtime-patches.lock.json @@ -0,0 +1,17 @@ +{ + "schema_version": 1, + "makepad": { + "base_revision": "1d3d383e84a66dbb18a4a860f505430c9d5b20f4", + "source_commit": "d7a834c74783e2032bc9e2cb9e693eaa2927b1d9", + "patch": "patches/runtime/makepad-contained-apps.patch", + "sha256": "70419d98644af0820816f8bbc480365fa214c71f96eea0dba4b541c7ec9d59a2", + "tree": "3889a3496226def432ecdf8131a9d12eb822e59b", + "product_fixes": [ + "Contained script apps (OctoSense-org/makepad#30): every path out of an isolate answers to its policy; images/web grants; WebReader, GestureView, SheetView, CameraPreview; parse_feed and fs.read_bytes.", + "Host services: a host takes only its own isolates' requests, and an asynchronous answer wakes the UI.", + "A contained app cannot collect a secret: password and one-time-code fields are inert in a policed isolate.", + "Text: a run's trailing space keeps its width unless the row wraps there; every widget takes text/set_text from script.", + "Sockets and servers: net.web_socket answers to the app's host list like net.http_request; net.socket_stream and net.http_server are refused in a policed isolate; a Video's network and file sources answer to the host list and the storage jail." + ] + } +} diff --git a/tools/setup-native.py b/tools/setup-native.py index 8e03b9d7..1afa1b50 100644 --- a/tools/setup-native.py +++ b/tools/setup-native.py @@ -1,59 +1,181 @@ #!/usr/bin/env python3 -"""Bootstrap an application's pinned Octoscript-Makepad runtime (Python 3.9+).""" +"""Prepare OctoSense's pinned sibling sources (Python 3.9+). + +Beside this repository, in one workspace directory: + + octoscript-makepad/ the release native-runtime.lock.json selects + makepad/, octoscript/ at the revisions that release's runtime.json pins; + makepad carries the reviewed product patch + runtime-patches.lock.json names (contained apps) + OctoSense-System-Apps/ at the revision native-apps.lock.json pins: the + system app bundles system-apps.json selects, the + Mail host service and the AppCard assistant + +Local changes are preserved; --update only moves clean checkouts. +""" import argparse +import hashlib import json +import os from pathlib import Path import re import subprocess -import sys CONSUMER = Path(__file__).resolve().parents[1] URL = "https://github.com/OctoSense-org/Octoscript-Makepad.git" +RUNTIME_URLS = { + "makepad": "https://github.com/OctoSense-org/makepad.git", + "octoscript": "https://github.com/OctoSense-org/Octoscript.git", +} +APP_SOURCES = { + # lock key: (sibling directory, URL) + "system-apps": ("OctoSense-System-Apps", "https://github.com/OctoSense-org/OctoSense-System-Apps.git"), +} def git(path, *args, check=True): - return subprocess.run(["git", "-C", str(path), *args], check=check, - capture_output=True, text=True) + result = subprocess.run(["git", "-C", str(path), *args], capture_output=True, text=True) + if check and result.returncode: + raise RuntimeError(result.stderr.strip() or f"git {' '.join(args)} failed in {path}") + return result + + +def fetch(path, name, revision, args): + if not git(path, "cat-file", "-e", revision + "^{commit}", check=False).returncode: + return + cached = args.cache.resolve() / name if args.cache else None + if cached and (cached / ".git").exists() and not git(cached, "cat-file", "-e", revision + "^{commit}", check=False).returncode: + if not git(path, "fetch", "--quiet", "--no-tags", "--depth=1", str(cached), revision, check=False).returncode: + return + git(path, "fetch", "--quiet", "--no-tags", "--depth=1", "origin", revision) + + +def prepare_source(root, name, url, revision, args, overlay=None): + """Check out `name` at `revision` (plus the reviewed `overlay` patch).""" + path = root / name + if not re.fullmatch(r"[0-9a-f]{40}", revision or ""): + raise RuntimeError(f"Unpinned source: {name}") + if not (path / ".git").exists(): + if args.check or path.exists() and any(path.iterdir()): + raise RuntimeError(f"Expected an empty dependency directory: {path}") + path.mkdir(parents=True, exist_ok=True) + git(path, "init", "--quiet") + git(path, "remote", "add", "origin", url) + if Path(git(path, "rev-parse", "--show-toplevel").stdout.strip()).resolve() != path.resolve(): + raise RuntimeError(f"Not a dependency checkout: {path}") + current = git(path, "rev-parse", "--verify", "HEAD", check=False).stdout.strip() + if current and (git(path, "diff", "--quiet", check=False).returncode + or git(path, "ls-files", "--others", "--exclude-standard").stdout): + raise RuntimeError(f"Preserving local changes: {path}") + tree = git(path, "write-tree").stdout.strip() if current else "" + head_tree = git(path, "rev-parse", "HEAD^{tree}", check=False).stdout.strip() + patch = None + if overlay: + patch = (CONSUMER / overlay["patch"]).resolve() + if (not patch.is_relative_to(CONSUMER) + or hashlib.sha256(patch.read_bytes()).hexdigest() != overlay["sha256"] + or overlay["base_revision"] != revision): + raise RuntimeError("Runtime patch does not match its source lock") + # Prepared: the base with the patch staged, or a clean checkout of the + # commit the patch was cut from (the same tree). + if tree == overlay["tree"] and (current == revision or head_tree == overlay["tree"]): + return current + elif current == revision and tree == head_tree: + return current + if current and tree != head_tree: + raise RuntimeError(f"Preserving staged changes: {path}") + if current != revision: + if args.check or current and not args.update: + raise RuntimeError(f"{path} selects another revision; --update only changes clean checkouts") + fetch(path, name, revision, args) + git(path, "checkout", "--quiet", "--detach", revision) + if patch: + if args.check: + raise RuntimeError(f"Reviewed runtime patch is not applied in {path}; run setup without --check") + git(path, "apply", "--check", str(patch)) + git(path, "apply", "--index", str(patch)) + if git(path, "write-tree").stdout.strip() != overlay["tree"]: + raise RuntimeError(f"Runtime patch produced an unexpected tree: {path}") + return revision + + +def verify_consumer(expected): + """Reject manifests that pin a runtime source at another revision.""" + for directory, subdirs, files in os.walk(CONSUMER): + subdirs[:] = [n for n in subdirs if n not in {".git", "target", "node_modules", "build", ".gradle"}] + if "Cargo.toml" not in files: + continue + path = Path(directory) / "Cargo.toml" + for line in path.read_text().splitlines(): + if line.lstrip().startswith("#"): + continue + url = re.search(r'git\s*=\s*"([^"]+)"', line) + if url and url[1] in expected: + rev = re.search(r'rev\s*=\s*"([0-9a-f]{40})"', line) + if not rev or rev[1] != expected[url[1]]: + raise RuntimeError(f"Divergent runtime dependency: {path}: {line}") + + +def verify_cargo(root, cargo_manifest): + """One Makepad in the graph, from the prepared sibling checkout.""" + metadata = json.loads(subprocess.check_output( + ["cargo", "metadata", "--locked", "--format-version", "1", "--features", "mobile-apps", + "--manifest-path", str(cargo_manifest)], cwd=cargo_manifest.parent, text=True)) + seen = set() + critical = {"makepad-script", "makepad-platform", "makepad-draw", "makepad-widgets", "makepad-live-id"} + for package in metadata["packages"]: + if package["name"] in critical: + if package["name"] in seen or not Path(package["manifest_path"]).resolve().is_relative_to(root / "makepad"): + raise RuntimeError(f"Duplicate or foreign runtime crate: {package['name']}") + seen.add(package["name"]) + if seen != critical: + raise RuntimeError("Incomplete Makepad dependency graph") + octos = {package["source"] for package in metadata["packages"] if package["name"] == "octos-core"} + if len(octos) > 1: + raise RuntimeError(f"More than one octos source: {sorted(octos)}") def main(): - parser = argparse.ArgumentParser(description=__doc__) + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument("--root", type=Path, default=CONSUMER.parent) - parser.add_argument("--update", action="store_true", help="Update clean checkouts to the release") - parser.add_argument("--check", action="store_true") - parser.add_argument("--cargo-manifest", type=Path) + parser.add_argument("--update", action="store_true", help="Move clean checkouts to the locked revisions") + parser.add_argument("--check", action="store_true", help="Verify without changing anything") + parser.add_argument("--cache", type=Path, help="Optional directory of Git object caches") + parser.add_argument("--cargo-manifest", type=Path, help="Also check the locked Cargo graph") args = parser.parse_args() root = args.root.resolve() if root == CONSUMER or root.is_relative_to(CONSUMER): - raise RuntimeError("Keep native framework repositories outside the application") + parser.error("Keep the sibling repositories outside the application") lock = json.loads((CONSUMER / "native-runtime.lock.json").read_text()) - if lock.get("schema_version") != 1 or lock.get("url") != URL or not re.fullmatch(r"[0-9a-f]{40}", lock.get("revision", "")): - raise RuntimeError("Expected a pinned OctoSense-org/Octoscript-Makepad release") - runtime = root / "octoscript-makepad" - if not (runtime / ".git").exists(): - if args.check or runtime.exists() and any(runtime.iterdir()): - raise RuntimeError(f"Prepare the runtime in an empty workspace: {runtime}") - runtime.mkdir(parents=True, exist_ok=True) - git(runtime, "init", "--quiet") - git(runtime, "remote", "add", "origin", URL) - current = git(runtime, "rev-parse", "--verify", "HEAD", check=False).stdout.strip() - for name in ("octoscript-makepad", "makepad", "octoscript"): - path = root / name - if (path / ".git").exists() and git(path, "status", "--porcelain", "--untracked-files=normal").stdout: - raise RuntimeError(f"Preserving local changes: {path}") - if current != lock["revision"]: - if args.check or current and not args.update: - raise RuntimeError("Another runtime is checked out; use --update with clean sources") - if git(runtime, "cat-file", "-e", lock["revision"] + "^{commit}", check=False).returncode: - git(runtime, "fetch", "--quiet", "--no-tags", "--depth=1", "origin", lock["revision"]) - git(runtime, "checkout", "--quiet", "--detach", lock["revision"]) - command = [sys.executable, str(runtime / "tools/runtime.py"), - "verify" if args.check else "prepare", "--root", str(root), "--consumer", str(CONSUMER)] - if args.update: - command.append("--update") + if lock.get("schema_version") != 1 or lock.get("url") != URL: + parser.error("Expected a pinned OctoSense-org/Octoscript-Makepad release") + patches = json.loads((CONSUMER / "runtime-patches.lock.json").read_text()) + if patches.get("schema_version") != 1 or not set(patches) <= {"schema_version", "makepad"}: + parser.error("Unsupported runtime patch lock") + apps = json.loads((CONSUMER / "native-apps.lock.json").read_text()) + if apps.get("schema_version") != 1 or set(apps.get("repositories", {})) != set(APP_SOURCES): + parser.error("Unsupported application source lock") + + prepare_source(root, "octoscript-makepad", URL, lock["revision"], args) + manifest = json.loads((root / "octoscript-makepad/runtime.json").read_text()) + if manifest.get("schema_version") != 1 or set(manifest.get("repositories", {})) != set(RUNTIME_URLS): + parser.error("Unsupported runtime source set") + expected = {URL: lock["revision"]} + for name, spec in manifest["repositories"].items(): + if spec.get("url") != RUNTIME_URLS[name]: + parser.error(f"Unexpected runtime source: {name}") + prepare_source(root, name, spec["url"], spec["revision"], args, patches.get(name)) + expected[spec["url"]] = spec["revision"] + for key, spec in apps["repositories"].items(): + directory, url = APP_SOURCES[key] + if spec.get("url") != url: + parser.error(f"Unexpected application source: {key}") + prepare_source(root, directory, url, spec["revision"], args) + verify_consumer(expected) if args.cargo_manifest: - command += ["--cargo-manifest", str(args.cargo_manifest.resolve())] - subprocess.run(command, check=True) + verify_cargo(root, args.cargo_manifest.resolve()) + print(json.dumps({"runtime": lock, "repositories": manifest["repositories"], + "patches": patches, "apps": apps["repositories"]}, indent=2)) if __name__ == "__main__": From 568c4d7a987477f1d053da1399e40ef6a98ebc04 Mon Sep 17 00:00:00 2001 From: ychen Date: Sat, 26 Sep 2026 00:37:13 -0700 Subject: [PATCH 16/42] AppCard: octos-app from OctoSense-System-Apps The AppCard module mounts octos-app from the pinned OctoSense-System-Apps checkout (apps/appcard/app/app, no default features) instead of Octoscript-AppCard@9e8e4898. octos-app takes every octos crate from git octos-org/octos at 18fcd3f1, so the graph has one octos source (`cargo tree -i octos-core --target all`). OCTOSENSE_WORKSPACE (..) already points its asset embedding at the sibling framework checkouts; the desktop does not build for OpenHarmony, so it takes no nix patch. The module test's InstanceHandles gains the `windows` field the module contract has had since the module-windows change. Co-Authored-By: Claude Opus 5.5 (1M context) --- Cargo.lock | 82 ++++++++++++++--------------------- apps/appcard/Cargo.toml | 9 +++- apps/appcard/src/lib.rs | 1 + docs/android-appcard-build.md | 21 +++++---- 4 files changed, 52 insertions(+), 61 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6717f751..69722821 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1366,7 +1366,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2425,7 +2425,7 @@ checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" dependencies = [ "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -6560,7 +6560,7 @@ dependencies = [ [[package]] name = "octos-agent" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d#9e8e4898b8a7c03dbcb62993e7826577411e489d" +source = "git+https://github.com/octos-org/octos.git?rev=18fcd3f16e527d2b601d7ae244f056fb711bb6b8#18fcd3f16e527d2b601d7ae244f056fb711bb6b8" dependencies = [ "async-trait", "base64 0.22.1", @@ -6585,7 +6585,7 @@ dependencies = [ "metrics", "oauth2", "octos-bus", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d)", + "octos-core", "octos-llm", "octos-memory", "octos-plugin", @@ -6621,7 +6621,6 @@ dependencies = [ [[package]] name = "octos-app" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d#9e8e4898b8a7c03dbcb62993e7826577411e489d" dependencies = [ "blake3", "chrono", @@ -6633,7 +6632,7 @@ dependencies = [ "octos-app-render", "octos-app-store", "octos-app-transport", - "octos-core 2.0.3-rc.12 (git+https://github.com/octos-org/octos.git?rev=18fcd3f16e527d2b601d7ae244f056fb711bb6b8)", + "octos-core", "octoscript-node", "octoscript-ui-l0", "reqwest 0.12.28", @@ -6648,7 +6647,6 @@ dependencies = [ [[package]] name = "octos-app-render" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d#9e8e4898b8a7c03dbcb62993e7826577411e489d" dependencies = [ "streaming-markdown-kit", ] @@ -6656,12 +6654,11 @@ dependencies = [ [[package]] name = "octos-app-store" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d#9e8e4898b8a7c03dbcb62993e7826577411e489d" dependencies = [ "chrono", "keyring", "log", - "octos-core 2.0.3-rc.12 (git+https://github.com/octos-org/octos.git?rev=18fcd3f16e527d2b601d7ae244f056fb711bb6b8)", + "octos-core", "serde", "serde_json", "uuid", @@ -6670,14 +6667,13 @@ dependencies = [ [[package]] name = "octos-app-transport" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d#9e8e4898b8a7c03dbcb62993e7826577411e489d" dependencies = [ "android_logger", "chrono", "futures-util", "log", "octos-cli", - "octos-core 2.0.3-rc.12 (git+https://github.com/octos-org/octos.git?rev=18fcd3f16e527d2b601d7ae244f056fb711bb6b8)", + "octos-core", "reqwest 0.12.28", "serde", "serde_json", @@ -6691,7 +6687,7 @@ dependencies = [ [[package]] name = "octos-bus" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d#9e8e4898b8a7c03dbcb62993e7826577411e489d" +source = "git+https://github.com/octos-org/octos.git?rev=18fcd3f16e527d2b601d7ae244f056fb711bb6b8#18fcd3f16e527d2b601d7ae244f056fb711bb6b8" dependencies = [ "aes 0.8.4", "async-trait", @@ -6705,7 +6701,7 @@ dependencies = [ "futures", "lru", "metrics", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d)", + "octos-core", "reqwest 0.12.28", "serde", "serde_json", @@ -6720,7 +6716,7 @@ dependencies = [ [[package]] name = "octos-cli" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d#9e8e4898b8a7c03dbcb62993e7826577411e489d" +source = "git+https://github.com/octos-org/octos.git?rev=18fcd3f16e527d2b601d7ae244f056fb711bb6b8#18fcd3f16e527d2b601d7ae244f056fb711bb6b8" dependencies = [ "agent-client-protocol", "argon2", @@ -6750,7 +6746,7 @@ dependencies = [ "metrics-exporter-prometheus", "octos-agent", "octos-bus", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d)", + "octos-core", "octos-diagnostics", "octos-fleet", "octos-fleet-worker", @@ -6811,27 +6807,13 @@ dependencies = [ "uuid", ] -[[package]] -name = "octos-core" -version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d#9e8e4898b8a7c03dbcb62993e7826577411e489d" -dependencies = [ - "chrono", - "eyre", - "serde", - "serde_json", - "sha2 0.10.9", - "tracing", - "uuid", -] - [[package]] name = "octos-diagnostics" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d#9e8e4898b8a7c03dbcb62993e7826577411e489d" +source = "git+https://github.com/octos-org/octos.git?rev=18fcd3f16e527d2b601d7ae244f056fb711bb6b8#18fcd3f16e527d2b601d7ae244f056fb711bb6b8" dependencies = [ "eyre", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d)", + "octos-core", "reqwest 0.12.28", "serde", "serde_json", @@ -6840,10 +6822,10 @@ dependencies = [ [[package]] name = "octos-fleet" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d#9e8e4898b8a7c03dbcb62993e7826577411e489d" +source = "git+https://github.com/octos-org/octos.git?rev=18fcd3f16e527d2b601d7ae244f056fb711bb6b8#18fcd3f16e527d2b601d7ae244f056fb711bb6b8" dependencies = [ "eyre", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d)", + "octos-core", "redb", "rusqlite", "serde", @@ -6856,12 +6838,12 @@ dependencies = [ [[package]] name = "octos-fleet-worker" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d#9e8e4898b8a7c03dbcb62993e7826577411e489d" +source = "git+https://github.com/octos-org/octos.git?rev=18fcd3f16e527d2b601d7ae244f056fb711bb6b8#18fcd3f16e527d2b601d7ae244f056fb711bb6b8" dependencies = [ "async-trait", "eyre", "octos-agent", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d)", + "octos-core", "octos-fleet", "octos-llm", "octos-memory", @@ -6873,7 +6855,7 @@ dependencies = [ [[package]] name = "octos-llm" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d#9e8e4898b8a7c03dbcb62993e7826577411e489d" +source = "git+https://github.com/octos-org/octos.git?rev=18fcd3f16e527d2b601d7ae244f056fb711bb6b8#18fcd3f16e527d2b601d7ae244f056fb711bb6b8" dependencies = [ "async-trait", "base64 0.22.1", @@ -6883,7 +6865,7 @@ dependencies = [ "jsonwebtoken", "libc", "metrics", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d)", + "octos-core", "redb", "reqwest 0.12.28", "secrecy", @@ -6897,13 +6879,13 @@ dependencies = [ [[package]] name = "octos-memory" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d#9e8e4898b8a7c03dbcb62993e7826577411e489d" +source = "git+https://github.com/octos-org/octos.git?rev=18fcd3f16e527d2b601d7ae244f056fb711bb6b8#18fcd3f16e527d2b601d7ae244f056fb711bb6b8" dependencies = [ "bincode", "chrono", "eyre", "hnsw_rs", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d)", + "octos-core", "redb", "regex", "serde", @@ -6916,7 +6898,7 @@ dependencies = [ [[package]] name = "octos-pipeline" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d#9e8e4898b8a7c03dbcb62993e7826577411e489d" +source = "git+https://github.com/octos-org/octos.git?rev=18fcd3f16e527d2b601d7ae244f056fb711bb6b8#18fcd3f16e527d2b601d7ae244f056fb711bb6b8" dependencies = [ "async-trait", "chrono", @@ -6924,7 +6906,7 @@ dependencies = [ "futures", "glob", "octos-agent", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d)", + "octos-core", "octos-llm", "octos-memory", "octos-plugin", @@ -6938,7 +6920,7 @@ dependencies = [ [[package]] name = "octos-plugin" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d#9e8e4898b8a7c03dbcb62993e7826577411e489d" +source = "git+https://github.com/octos-org/octos.git?rev=18fcd3f16e527d2b601d7ae244f056fb711bb6b8#18fcd3f16e527d2b601d7ae244f056fb711bb6b8" dependencies = [ "eyre", "metrics", @@ -6952,7 +6934,7 @@ dependencies = [ [[package]] name = "octos-sandbox" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d#9e8e4898b8a7c03dbcb62993e7826577411e489d" +source = "git+https://github.com/octos-org/octos.git?rev=18fcd3f16e527d2b601d7ae244f056fb711bb6b8#18fcd3f16e527d2b601d7ae244f056fb711bb6b8" dependencies = [ "clap", "eyre", @@ -6967,7 +6949,7 @@ dependencies = [ [[package]] name = "octos-services" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d#9e8e4898b8a7c03dbcb62993e7826577411e489d" +source = "git+https://github.com/octos-org/octos.git?rev=18fcd3f16e527d2b601d7ae244f056fb711bb6b8#18fcd3f16e527d2b601d7ae244f056fb711bb6b8" dependencies = [ "chrono", "dirs", @@ -6975,7 +6957,7 @@ dependencies = [ "flate2", "futures", "octos-bus", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d)", + "octos-core", "octos-llm", "reqwest 0.12.28", "serde", @@ -6988,14 +6970,14 @@ dependencies = [ [[package]] name = "octos-store" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d#9e8e4898b8a7c03dbcb62993e7826577411e489d" +source = "git+https://github.com/octos-org/octos.git?rev=18fcd3f16e527d2b601d7ae244f056fb711bb6b8#18fcd3f16e527d2b601d7ae244f056fb711bb6b8" dependencies = [ "base64 0.22.1", "chrono", "constant_time_eq 0.3.1", "eyre", "getrandom 0.2.17", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d)", + "octos-core", "redb", "serde", "serde_json", @@ -7008,7 +6990,7 @@ dependencies = [ [[package]] name = "octos-swarm" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d#9e8e4898b8a7c03dbcb62993e7826577411e489d" +source = "git+https://github.com/octos-org/octos.git?rev=18fcd3f16e527d2b601d7ae244f056fb711bb6b8#18fcd3f16e527d2b601d7ae244f056fb711bb6b8" dependencies = [ "async-trait", "chrono", @@ -7027,12 +7009,12 @@ dependencies = [ [[package]] name = "octos-workflows" version = "2.0.3-rc.12" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d#9e8e4898b8a7c03dbcb62993e7826577411e489d" +source = "git+https://github.com/octos-org/octos.git?rev=18fcd3f16e527d2b601d7ae244f056fb711bb6b8#18fcd3f16e527d2b601d7ae244f056fb711bb6b8" dependencies = [ "chrono", "eyre", "octos-agent", - "octos-core 2.0.3-rc.12 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=9e8e4898b8a7c03dbcb62993e7826577411e489d)", + "octos-core", "octos-pipeline", "serde", "serde_json", diff --git a/apps/appcard/Cargo.toml b/apps/appcard/Cargo.toml index 23fe5296..8a4c21de 100644 --- a/apps/appcard/Cargo.toml +++ b/apps/appcard/Cargo.toml @@ -8,10 +8,15 @@ publish = false [dependencies] makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4", features = ["appcard", "maps"] } makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4" } -# The whole Octoscript-AppCard app as a library: routing brain, card store and +# The whole AppCard assistant as a library, from the pinned +# OctoSense-System-Apps checkout beside this repository (apps/appcard/app/app; +# native-apps.lock.json, tools/setup-native.py): routing brain, card store and # transport, L0 lowering, sessions, composer, kernel agent — mounted here as # its `AppShell` widget. No default features: the app's own entry points # (`app_main!`, the dev monitor) stay out; this module is the entry point. -octos-app = { git = "https://github.com/OctoSense-org/Octoscript-AppCard.git", rev = "9e8e4898b8a7c03dbcb62993e7826577411e489d", default-features = false } +# Every octos crate comes from git octos-org/octos at the one rev octos-app +# pins; OCTOSENSE_WORKSPACE (.cargo/config.toml) points its asset embedding +# at the sibling framework checkouts. +octos-app = { path = "../../../OctoSense-System-Apps/apps/appcard/app/app", default-features = false } # Tool arguments (`ask` takes `{"text": ...}`). serde_json = "1.0" diff --git a/apps/appcard/src/lib.rs b/apps/appcard/src/lib.rs index dd478e89..bd6f2a69 100644 --- a/apps/appcard/src/lib.rs +++ b/apps/appcard/src/lib.rs @@ -171,6 +171,7 @@ mod tests { storage: cx_storage_for_test(vm), viewport: makepad_app_module::Viewport { size: dvec2(400.0, 700.0) }, replies, + windows: Default::default(), }; let parts = APPCARD_MODULE.create(vm, open, handles); let errors = vm.take_errors(); diff --git a/docs/android-appcard-build.md b/docs/android-appcard-build.md index 3c0cc602..cb3aa0d2 100644 --- a/docs/android-appcard-build.md +++ b/docs/android-appcard-build.md @@ -1,8 +1,10 @@ # Android build with AppCard's framework, buildtool and bundled kernel -OctoSense on the phone now runs the Octoscript-AppCard module (`apps/appcard`) -on the makepad fork's AppCard framework line and needs three things the stock -`cargo makepad` build does not give it: +OctoSense on the phone now runs the AppCard assistant module (`apps/appcard`, +which mounts `octos-app` from the pinned OctoSense-System-Apps checkout, +`../OctoSense-System-Apps/apps/appcard/app/app`) on the makepad fork's AppCard +framework line and needs three things the stock `cargo makepad` build does not +give it: 1. **The framework pin.** `Cargo.toml`, `apps/reference/Cargo.toml` and `apps/appcard/Cargo.toml` pin every makepad crate at the fork's @@ -28,11 +30,12 @@ on the makepad fork's AppCard framework line and needs three things the stock ## Step by step ```sh -# 0. Paths (adjust): a checkout of the fork and of Octoscript-AppCard (with its -# `octos` submodule initialised), and the Android toolchain dir that -# `cargo makepad android install-toolchain` produced earlier. +# 0. Paths (adjust): a checkout of the fork, a checkout of octos-org/octos at +# the rev octos-app pins for every octos crate (18fcd3f1, see +# OctoSense-System-Apps apps/appcard/app/Cargo.toml), and the Android +# toolchain dir that `cargo makepad android install-toolchain` produced. FORK=/path/to/makepad-fork # https://github.com/OctoSense-org/makepad.git -APPCARD=/path/to/Octoscript-AppCard # octos submodule at deb433e9 or later +OCTOS=/path/to/octos # https://github.com/octos-org/octos.git at 18fcd3f16e527d2b601d7ae244f056fb711bb6b8 TOOLCHAIN=/path/to/android_33_macos_aarch64 # ndk/, platforms/, build-tools/, platform-tools/, openjdk/ # 1. The buildtool cargo-makepad. It looks for the toolchain relative to its @@ -54,9 +57,9 @@ export CC_aarch64_linux_android="$LLVM/aarch64-linux-android33-clang" export CXX_aarch64_linux_android="$LLVM/aarch64-linux-android33-clang++" export AR_aarch64_linux_android="$LLVM/llvm-ar" export RANLIB_aarch64_linux_android="$LLVM/llvm-ranlib" -( cd "$APPCARD/octos" && cargo build --release --target aarch64-linux-android \ +( cd "$OCTOS" && cargo build --release --target aarch64-linux-android \ -p octos-cli --bin octos --features api,git,ast ) -KERNEL="$APPCARD/octos/target/aarch64-linux-android/release/octos" # ~130 MB +KERNEL="$OCTOS/target/aarch64-linux-android/release/octos" # ~130 MB # 3. Build, install and launch OctoSense with the kernel bundled. From this # repository's root, with the phone authorised over adb: From 81406fe3106e3c96064f4dcc79defbf419bd8e3a Mon Sep 17 00:00:00 2001 From: ychen Date: Sat, 26 Sep 2026 00:37:13 -0700 Subject: [PATCH 17/42] Link App Hub: system apps and store apps run in its Card runner The desktop takes its apps the way OctoSense ROM's Home does. The default `app-hub` feature links octosense-app-hub-app (OctoSense-App-Hub crates/app-hub-app @4605128d) and the Mail host service (octosense-mail-service from the pinned OctoSense-System-Apps checkout): - system-apps.json selects News, Photos, Maps, Camera and Mail from ../OctoSense-System-Apps/apps; .cargo/config.toml names it in OCTOSENSE_SYSTEM_APPS. No asset mount: the desktop has no photo library, so Photos runs on its bundle's thumbnails. - apps.rs: the App Hub and Card modules; system_card_apps (short ids, `--system=os.`), installed_card_apps (`hub:`), card_apps, card_manifest_id, matches_running_app, register_host_services (mail, or its demo mailbox with MAKEPAD_APP_CONFIG mail_demo). Card apps and the store have no process form: always module-hosted. - clients::registry merges the catalog, the linked modules, the system apps and the installed apps; a system app replaces a catalog row of the same id in place. - main.rs: the Card runner opens the row's manifest id; installed apps are checked against the catalog, `os.*` ids are not; App Hub's launch and install actions; the data root. - style.rs: installed and system app icons (Camera's own art), App Hub's icon, Maps drawn with the route art. - module_host.rs: nested Card isolates can replay the theme's fonts. - Photos is the system app; `app-photos` (Makepad's native Photos) leaves mobile-apps and the phone targets and stays a comparison switch. Every App Hub library resolves through one source: the Mail service pins App Hub's appstore at an older rev, and two copies would be two host service registries. Co-Authored-By: Claude Opus 5.5 (1M context) --- .cargo/config.toml | 5 + Cargo.lock | 271 ++++++++++++++++++++++++++++++++++++++- Cargo.toml | 36 +++++- src/apps.rs | 230 +++++++++++++++++++++++++++++++-- src/clients.rs | 64 +++++++-- src/desktop.rs | 4 +- src/main.rs | 62 +++++++-- src/mobile_app.rs | 14 +- src/module_host.rs | 21 ++- src/octosense/catalog.rs | 3 +- src/octosense/style.rs | 119 ++++++++++++++++- src/shell/launcher.rs | 4 +- system-apps.json | 6 + 13 files changed, 789 insertions(+), 50 deletions(-) create mode 100644 system-apps.json diff --git a/.cargo/config.toml b/.cargo/config.toml index 912b5003..0a8c0aba 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -6,3 +6,8 @@ MAKEPAD_BUNDLE_IDENTIFIER = { value = "dev.makepad.octosense", force = true } # AppCards embeds assets from the pinned sibling framework, including Git builds. OCTOSENSE_WORKSPACE = { value = "..", relative = true } + +# The system apps App Hub's shell crate packs into this build (ADR 0004): +# which apps, and where their bundles are (the pinned OctoSense-System-Apps +# checkout beside this repository). Cargo passes it as an absolute path. +OCTOSENSE_SYSTEM_APPS = { value = "system-apps.json", relative = true } diff --git a/Cargo.lock b/Cargo.lock index 69722821..6c15b8e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1121,6 +1121,16 @@ dependencies = [ "poly1305 0.9.1", ] +[[package]] +name = "charset" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f927b07c74ba84c7e5fe4db2baeb3e996ab2688992e39ac68ce3220a677c7e" +dependencies = [ + "base64 0.22.1", + "encoding_rs", +] + [[package]] name = "chromiumoxide" version = "0.9.1" @@ -1652,6 +1662,29 @@ dependencies = [ "rand_core 0.10.1", ] +[[package]] +name = "cssparser" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e901edd733a1472f944a45116df3f846f54d37e67e68640ac8bb69689aca2aa" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "phf 0.11.3", + "smallvec 1.16.1", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn 2.0.119", +] + [[package]] name = "ctor" version = "0.2.9" @@ -2060,6 +2093,21 @@ dependencies = [ name = "downcast-rs" version = "1.2.1" +[[package]] +name = "dtoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + [[package]] name = "dunce" version = "1.0.5" @@ -2107,6 +2155,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ego-tree" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2972feb8dffe7bc8c5463b1dacda1b0dfbed3710e50f977d965429692d74cd8" + [[package]] name = "either" version = "1.18.0" @@ -2713,6 +2767,15 @@ dependencies = [ "byteorder 1.5.0", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "g2gen" version = "1.2.2" @@ -4971,6 +5034,17 @@ dependencies = [ "syn 3.0.5", ] +[[package]] +name = "mailparse" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60819a97ddcb831a5614eb3b0174f3620e793e97e09195a395bfa948fd68ed2f" +dependencies = [ + "charset", + "data-encoding", + "quoted_printable", +] + [[package]] name = "makepad-ai" version = "1.0.0" @@ -5067,7 +5141,7 @@ name = "makepad-draw" version = "2.0.0" dependencies = [ "ab_glyph_rasterizer", - "fxhash", + "fxhash 0.2.1", "makepad-gif", "makepad-live-id", "makepad-math", @@ -7022,10 +7096,28 @@ dependencies = [ "tracing", ] +[[package]] +name = "octoscript-makepad" +version = "0.1.0" +dependencies = [ + "octoscript-render", + "octoscript-ui-l0", + "serde_json", +] + [[package]] name = "octoscript-node" version = "0.1.0" +[[package]] +name = "octoscript-render" +version = "0.1.0" +dependencies = [ + "makepad-script", + "octoscript-node", + "serde_json", +] + [[package]] name = "octoscript-ui-l0" version = "0.1.0" @@ -7034,6 +7126,14 @@ dependencies = [ "serde_json", ] +[[package]] +name = "octoscript-widgets" +version = "0.1.0" +dependencies = [ + "makepad-widgets", + "serde_json", +] + [[package]] name = "octosense" version = "0.1.0" @@ -7049,11 +7149,54 @@ dependencies = [ "makepad-widgets", "makepad-wm-api", "makepad-wm-theme", + "octosense-app-hub-app", "octosense-appcard", + "octosense-mail-service", "octosense-reference", "rinx", ] +[[package]] +name = "octosense-app-hub" +version = "0.1.0" +source = "git+https://www.github.com/OctoSense-org/OctoSense-App-Hub?rev=4605128d46fb982828d8198e0d71d62a39c7d6d6#4605128d46fb982828d8198e0d71d62a39c7d6d6" +dependencies = [ + "base64 0.22.1", + "blake3", + "ed25519-dalek", + "hex", + "octosense-app-policy", + "rand_core 0.9.5", + "serde", + "serde_json", + "ureq", +] + +[[package]] +name = "octosense-app-hub-app" +version = "0.1.0" +source = "git+https://github.com/OctoSense-org/OctoSense-App-Hub?rev=4605128d46fb982828d8198e0d71d62a39c7d6d6#4605128d46fb982828d8198e0d71d62a39c7d6d6" +dependencies = [ + "makepad-app-module", + "makepad-widgets", + "makepad-wm-theme", + "octosense-app-hub", + "octosense-app-policy", + "octosense-appstore", + "serde_json", +] + +[[package]] +name = "octosense-app-policy" +version = "0.1.0" +source = "git+https://www.github.com/OctoSense-org/OctoSense-App-Hub?rev=4605128d46fb982828d8198e0d71d62a39c7d6d6#4605128d46fb982828d8198e0d71d62a39c7d6d6" +dependencies = [ + "blake3", + "makepad-widgets", + "serde", + "serde_json", +] + [[package]] name = "octosense-appcard" version = "0.1.0" @@ -7064,6 +7207,39 @@ dependencies = [ "serde_json", ] +[[package]] +name = "octosense-appstore" +version = "0.1.0" +source = "git+https://www.github.com/OctoSense-org/OctoSense-App-Hub?rev=4605128d46fb982828d8198e0d71d62a39c7d6d6#4605128d46fb982828d8198e0d71d62a39c7d6d6" +dependencies = [ + "makepad-app-module", + "makepad-widgets", + "octoscript-makepad", + "octoscript-widgets", + "octosense-app-hub", + "octosense-app-policy", + "serde_json", +] + +[[package]] +name = "octosense-mail-service" +version = "0.1.0" +dependencies = [ + "base64 0.22.1", + "chrono", + "ego-tree", + "keyring", + "mailparse", + "makepad-android-state", + "makepad-jni-sys", + "octosense-appstore", + "rustls", + "scraper", + "serde_json", + "sha2 0.10.9", + "webpki-roots", +] + [[package]] name = "octosense-reference" version = "0.1.0" @@ -7248,6 +7424,7 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ + "phf_macros 0.11.3", "phf_shared 0.11.3", ] @@ -7266,7 +7443,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" dependencies = [ - "phf_macros", + "phf_macros 0.13.1", "phf_shared 0.13.1", "serde", ] @@ -7311,6 +7488,19 @@ dependencies = [ "phf_shared 0.13.1", ] +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "phf_macros" version = "0.13.1" @@ -9001,6 +9191,20 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scraper" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5f3a24d916e78954af99281a455168d4a9515d65eca99a18da1b813689c4ad9" +dependencies = [ + "cssparser", + "ego-tree", + "html5ever 0.35.0", + "precomputed-hash", + "selectors", + "tendril 0.4.3", +] + [[package]] name = "scroll" version = "0.13.0" @@ -9102,6 +9306,25 @@ dependencies = [ "libc", ] +[[package]] +name = "selectors" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5685b6ae43bfcf7d2e7dfcfb5d8e8f61b46442c902531e41a32a9a8bf0ee0fb6" +dependencies = [ + "bitflags 2.13.2", + "cssparser", + "derive_more 2.1.1", + "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "log", + "new_debug_unreachable", + "phf 0.11.3", + "phf_codegen 0.11.3", + "precomputed-hash", + "servo_arc", + "smallvec 1.16.1", +] + [[package]] name = "semver" version = "1.0.28" @@ -9307,6 +9530,15 @@ dependencies = [ "serde", ] +[[package]] +name = "servo_arc" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "170fb83ab34de17dc69aa7c67482b22218ddb85da56546f9bd6b929e32a05930" +dependencies = [ + "stable_deref_trait", +] + [[package]] name = "sha1" version = "0.10.7" @@ -10694,6 +10926,35 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a7ac20be9b7726e0bbdbf974c059676d9acb1cd414961f570a4e8231cacd7fc" +dependencies = [ + "base64 0.23.1", + "flate2", + "log", + "percent-encoding", + "rustls", + "rustls-pki-types", + "ureq-proto", + "utf8-zero", + "webpki-roots", +] + +[[package]] +name = "ureq-proto" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f86fd172ccca569e458f61b6bdd6220965a9ef36e672a6852953b51a0e1583be" +dependencies = [ + "base64 0.23.1", + "http", + "httparse", + "log", +] + [[package]] name = "url" version = "2.5.8" @@ -10746,6 +11007,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf8-zero" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" + [[package]] name = "utf8_iter" version = "1.0.4" diff --git a/Cargo.toml b/Cargo.toml index c1728429..25d3aa8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,6 +57,18 @@ octosense-reference = { path = "apps/reference", optional = true } # The whole Octoscript-AppCard app hosted in a tile, in-process (Phase B of # the AppCard port): octos-app's AppShell — routing brain, cards, kernel agent. octosense-appcard = { path = "apps/appcard", optional = true } +# App Hub, as every OctoSense shell links it (OctoSense-App-Hub +# crates/app-hub-app): the native store module (`apphub`), the Card runner +# module (`card`) that runs contained script apps, the system apps packed at +# build time from the selection `OCTOSENSE_SYSTEM_APPS` names +# (system-apps.json, .cargo/config.toml), installed apps and their icons. +octosense-app-hub-app = { git = "https://github.com/OctoSense-org/OctoSense-App-Hub", rev = "4605128d46fb982828d8198e0d71d62a39c7d6d6", optional = true } +# The `mail` host service the Mail system app calls through `host.request`: +# accounts, message lists and send run here, with the passwords the host +# holds; the app never gets a socket or a secret. From the pinned +# OctoSense-System-Apps checkout beside this repository +# (native-apps.lock.json, tools/setup-native.py). +octosense-mail-service = { path = "../OctoSense-System-Apps/apps/mail/host-service", optional = true } # Rinx (the Matrix client with Moments and articles) as an in-process module. # Built without its standalone window entry point. rinx = { git = "https://github.com/upstreamlabs/Rinx", rev = "5ad2c1739e5b4776588518ab76440a5367c9968e", default-features = false, features = ["octosense-module"], optional = true } @@ -71,12 +83,17 @@ makepad-aichat = { git = "https://github.com/OctoSense-org/makepad.git", rev = " octosense-reference = { path = "apps/reference" } octosense-appcard = { path = "apps/appcard" } makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4", default-features = false } -makepad-photos = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4", default-features = false } [features] -default = [] +# App Hub is part of the standard launcher: the store, the Card runner and +# the system apps (News, Photos, Maps, Camera, Mail) it runs. +default = ["app-hub"] # Also usable on desktop for testing the same embedded apps with --module. -mobile-apps = ["app-reference", "app-sheets", "app-photos", "app-appcard"] +# Photos is the system app here; `app-photos` links Makepad's native Photos +# module instead, for comparison (a linked module of the same id wins). +mobile-apps = ["app-reference", "app-sheets", "app-appcard", "app-hub"] +# App Hub and the host services its system apps call. +app-hub = ["dep:octosense-app-hub-app", "dep:octosense-mail-service"] app-reference = ["dep:octosense-reference"] # Sheets linked in as a module (hosting is still Process unless switched). app-sheets = ["dep:makepad-sheets"] @@ -115,6 +132,7 @@ panic = "unwind" # Resolve the exact framework sources prepared by tools/setup-native.py. [patch."https://github.com/OctoSense-org/makepad.git"] +makepad-script = { path = "../makepad/platform/script" } makepad-widgets = { path = "../makepad/widgets" } makepad-code-editor = { path = "../makepad/code_editor" } makepad-ai = { path = "../makepad/libs/makepad_ai" } @@ -136,6 +154,8 @@ makepad-markdown-widgets = { path = "../makepad/libs/markdown_widgets" } octoscript-ui-l0 = { path = "../octoscript/crates/octoscript-ui-l0" } [patch."https://github.com/OctoSense-org/Octoscript-Makepad.git"] +octoscript-makepad = { path = "../octoscript-makepad/crates/octoscript-makepad" } +octoscript-widgets = { path = "../octoscript-makepad/crates/octoscript-widgets" } octoscript-node = { path = "../octoscript-makepad/crates/octoscript-node" } [patch."https://github.com/ZhangHanDong/makepad.git"] @@ -151,3 +171,13 @@ makepad-code-editor = { path = "../makepad/code_editor" } # Rinx's Matrix patches (see Rinx's Cargo.toml). [patch."https://github.com/ruma/ruma"] ruma = { git = "https://github.com/project-robius/ruma.git", branch = "tsp" } + +# One App Hub. The Mail host service pins App Hub's `octosense-appstore` at +# an older rev than the shell crate above; two copies would be two host +# service registries, and the Card runner would never see Mail's. A patch +# cannot point a source at another rev of itself, so every App Hub library +# resolves through the www host alias of the same repository, at the pin. +[patch."https://github.com/OctoSense-org/OctoSense-App-Hub"] +octosense-app-hub = { git = "https://www.github.com/OctoSense-org/OctoSense-App-Hub", rev = "4605128d46fb982828d8198e0d71d62a39c7d6d6" } +octosense-app-policy = { git = "https://www.github.com/OctoSense-org/OctoSense-App-Hub", rev = "4605128d46fb982828d8198e0d71d62a39c7d6d6" } +octosense-appstore = { git = "https://www.github.com/OctoSense-org/OctoSense-App-Hub", rev = "4605128d46fb982828d8198e0d71d62a39c7d6d6" } diff --git a/src/apps.rs b/src/apps.rs index 7775bfaa..33e90a76 100644 --- a/src/apps.rs +++ b/src/apps.rs @@ -10,6 +10,12 @@ //! `~/.makepad/wm/apps.splash` says otherwise (a settings file, never an //! environment variable) or a dev run passes `--module `. The uber //! builds ignore the switch: everything is a module there. +//! +//! App Hub (feature `app-hub`, on by default) adds the apps its Card runner +//! hosts, the same way OctoSense ROM's Home does: the system apps this build +//! ships as contained script bundles (`os.news`, … ADR 0004) and the apps the +//! person installed from the App Hub catalog (`hub:`). Neither +//! has a process form; the linked `card` module runs every one of them. use makepad_app_module::AppModule; use std::collections::HashMap; @@ -34,7 +40,7 @@ impl Default for AppRegistry { /// A linked module by id, without a registry: what the launcher asks. pub fn is_linked(id: &str) -> bool { - linked_modules().iter().any(|m| m.id() == id) + linked_modules().iter().any(|m| m.id() == id) || is_card_app(id) } /// Mobile includes its bundled modules automatically; desktop opts in with @@ -46,19 +52,167 @@ fn linked_modules() -> Vec<&'static dyn AppModule> { out.push(&octosense_reference::REFERENCE_MODULE); #[cfg(any(feature = "app-sheets", target_os = "android", target_os = "ios"))] out.push(&makepad_sheets::SHEETS_MODULE); - #[cfg(any(feature = "app-photos", target_os = "android", target_os = "ios"))] + // Makepad's native Photos, for comparison only: by default Photos is the + // system app (`os.photos`), and a linked module of the same id wins. + #[cfg(feature = "app-photos")] out.push(&makepad_photos::PHOTOS_MODULE); #[cfg(any(feature = "app-appcard", target_os = "android", target_os = "ios"))] out.push(&octosense_appcard::APPCARD_MODULE); #[cfg(feature = "app-rinx")] out.push(&rinx::module::RINX_MODULE); + // The trust anchor stays native: the store, and the runner every system + // and installed app is hosted by. + #[cfg(feature = "app-hub")] + { + out.push(&octosense_app_hub_app::APP_HUB_MODULE); + out.push(&octosense_app_hub_app::CARD_MODULE); + } out } -/// An installed host has no checkout catalog. Its linked modules carry all -/// the information needed to populate the launcher without filesystem paths. +/// Manifest ids live in a separate namespace from built-ins and catalog rows +/// (catalog ids cannot contain a colon). +pub fn installed_launch_id(manifest_id: &str) -> String { + format!("hub:{manifest_id}") +} + +/// The manifest id the `card` module opens for a launcher row: `os.` +/// for a system app, the installed app's own id for `hub:`. +pub fn card_manifest_id(app: &crate::clients::AppDef) -> Option<&str> { + if app.bin != "card" { + return None; + } + app.id.strip_prefix("hub:").or_else(|| app.args.iter().find_map(|a| a.strip_prefix(SYSTEM_ARG))) +} + +/// A system app's launcher row carries its manifest id as this argument. +const SYSTEM_ARG: &str = "--system="; + +fn card_row(id: String, label: String, args: Vec) -> crate::clients::AppDef { + crate::clients::AppDef { + id, + label, + bin: "card".into(), + package: String::new(), + dir: String::new(), + manifest: None, + args, + policy: crate::clients::LaunchPolicy::OrFocus, + target_dir: None, + } +} + +/// System apps (ADR 0004): first-party apps from OctoSense-System-Apps that +/// App Hub ships as contained script bundles, run by the Card runner. Each +/// keeps its short launcher id (`mail` for `os.mail`), so its icon and dock +/// place are the ones that id always had. They take precedence over catalog +/// rows of the same id (`clients::registry`); a linked native module of the +/// same id wins, for comparison builds (`app-photos`). +pub fn system_card_apps() -> Vec { + #[cfg(feature = "app-hub")] + { + register_host_services(); + let native: Vec<&str> = linked_modules().iter().map(|m| m.id()).collect(); + return octosense_app_hub_app::system_apps() + .into_iter() + .filter_map(|app| { + let short = app.id.strip_prefix("os.")?; + (!native.contains(&short)) + .then(|| card_row(short.into(), app.name.into(), vec![format!("{SYSTEM_ARG}{}", app.id)])) + }) + .collect(); + } + #[allow(unreachable_code)] + Vec::new() +} + +/// The services contained apps call through `host.request` (ADR 0004): `mail` +/// keeps accounts and passwords for the Mail app. `mail_demo` in +/// MAKEPAD_APP_CONFIG serves a demo mailbox from a file vault instead (no +/// keychain, no network): `MAKEPAD_APP_CONFIG='{"mail_demo":true}'`. +#[cfg(feature = "app-hub")] +pub fn register_host_services() { + static ONCE: std::sync::Once = std::sync::Once::new(); + ONCE.call_once(|| { + let demo = std::env::var("MAKEPAD_APP_CONFIG") + .ok() + .and_then(|text| makepad_strict_json::parse(text.as_bytes()).ok()) + .and_then(|config| config.get("mail_demo").and_then(|v| v.as_bool())) + .unwrap_or(false); + if demo { + octosense_mail_service::register_demo() + } else { + octosense_mail_service::register() + } + }); +} + +/// Apps App Hub installed: each is an app of its own in the launcher, hosted +/// by the linked `card` module under its `hub:` identity. Read +/// fresh each time, so an install shows up without a restart. +pub fn installed_card_apps() -> Vec { + #[cfg(feature = "app-hub")] + if let Some(root) = octosense_app_hub_app::data_root_if_set() { + return octosense_app_hub_app::installed_apps(&root) + .into_iter() + .map(|app| card_row(installed_launch_id(&app.id), app.name, Vec::new())) + .collect(); + } + Vec::new() +} + +/// Every launcher row the Card runner opens: system apps, then installed ones. +pub fn card_apps() -> Vec { + let mut apps = system_card_apps(); + apps.extend(installed_card_apps()); + apps +} + +/// Whether the `card` module hosts `id`. Installed ids carry their prefix, +/// so only they read the installed library. +fn is_card_app(id: &str) -> bool { + if id.starts_with("hub:") { + installed_card_apps().iter().any(|app| app.id == id) + } else { + system_card_apps().iter().any(|app| app.id == id) + } +} + +/// Launch-or-focus: a Card app focuses only an instance of that same app, +/// never a built-in (or another installed app) whose name it shares. +pub fn matches_running_app(app: &crate::clients::AppDef, running_id: &str, title: &str) -> bool { + if app.bin == "card" || running_id.starts_with("hub:") { + running_id == app.id + } else { + crate::clients::word_match(running_id, &app.id) || crate::clients::word_match(title, &app.id) + } +} + +/// What a launch of `app` opens `module` with. The Card runner opens the app +/// the launcher row names — a system app by its `os.*` manifest id, an +/// installed one by its own; every other module opens empty. +pub fn module_open(module: &'static dyn AppModule, app: &crate::clients::AppDef) -> Result { + let schema = module.open_schema(); + if module.id() != "card" { + return schema.empty_open(); + } + let manifest_id = card_manifest_id(app).ok_or_else(|| format!("{} names no app for the card runner", app.id))?; + schema.validate(&format!("{{\"app\":{}}}", makepad_strict_json::Value::Str(manifest_id.into()).to_json()), &[]) +} + +/// An installed host has no checkout catalog. Its linked modules, the system +/// apps and the installed apps carry all the information needed to populate +/// the launcher without filesystem paths. pub fn bundled_catalog() -> Vec { - linked_modules().iter().map(|module| crate::clients::AppDef { + let mut catalog = bundled_modules_catalog(); + catalog.extend(card_apps()); + catalog +} + +/// The linked modules as launcher rows. The `card` host module is not an app +/// a person opens; the apps it runs are. +pub fn bundled_modules_catalog() -> Vec { + linked_modules().iter().filter(|module| module.id() != "card").map(|module| crate::clients::AppDef { id: module.id().into(), label: module.label().into(), bin: module.id().into(), @@ -106,9 +260,16 @@ impl AppRegistry { registry } - /// The linked module for an app, if this build has one. + /// The linked module for an app, if this build has one. A system or + /// installed app has none of its own: the `card` module hosts it. pub fn module(&self, id: &str) -> Option<&'static dyn AppModule> { - self.modules.iter().copied().find(|m| m.id() == id) + if let Some(module) = self.modules.iter().copied().find(|m| m.id() == id) { + return Some(module); + } + if is_card_app(id) { + return self.modules.iter().copied().find(|m| m.id() == "card"); + } + None } /// How a launch of `id` is hosted. On a desktop: Module only when a @@ -119,6 +280,14 @@ impl AppRegistry { if !crate::host::processes_available() { return if self.module(id).is_some() { Hosting::Module } else { Hosting::Process }; } + // The store has no process form, and neither has a system or + // installed app: the `card` module hosts them on every platform. + if id == "apphub" && self.module(id).is_some() { + return Hosting::Module; + } + if self.modules.iter().any(|m| m.id() == "card") && !self.modules.iter().any(|m| m.id() == id) && is_card_app(id) { + return Hosting::Module; + } match self.overrides.get(id) { Some(Hosting::Module) if self.module(id).is_some() => Hosting::Module, _ => Hosting::Process, @@ -173,10 +342,19 @@ mod tests { use makepad_widgets::*; let catalog = bundled_catalog(); assert_eq!(catalog.iter().map(|app| app.id.as_str()).collect::>(), - ["reference", "sheets", "photos", "appcard"]); + ["reference", "sheets", "appcard", "apphub", "news", "photos", "maps", "camera", "mail"]); assert!(catalog.iter().all(|app| app.manifest.is_none())); - assert_eq!(catalog[0].policy, crate::clients::LaunchPolicy::AlwaysNew); + // The system apps have no native module: the Card runner hosts them, + // launched by their manifest id (ADR 0004). let registry = AppRegistry::default(); + for id in ["news", "photos", "maps", "camera", "mail"] { + let app = catalog.iter().find(|app| app.id == id).unwrap(); + assert_eq!(card_manifest_id(app), Some(format!("os.{id}").as_str())); + assert_eq!(registry.module(id).map(|m| m.id()), Some("card")); + assert_eq!(registry.hosting(id), Hosting::Module, "{id} has no process form"); + } + let catalog: Vec<_> = catalog.into_iter().filter(|app| app.bin != "card").collect(); + assert_eq!(catalog[0].policy, crate::clients::LaunchPolicy::AlwaysNew); let mut cx = Cx::new(Box::new(|_, _| {})); cx.with_vm(makepad_widgets::script_mod); let mut host = crate::module_host::ModuleHost::default(); @@ -197,6 +375,40 @@ mod tests { } } + #[test] + fn installed_card_identity_never_focuses_a_builtin_with_the_same_name() { + let mut app = card_row("news".into(), "News".into(), Vec::new()); + app.bin = "news".into(); + assert!(matches_running_app(&app, "news", "News")); + assert!(!matches_running_app(&app, "hub:news", "News")); + app.id = installed_launch_id("news"); + app.bin = "card".into(); + assert_eq!(card_manifest_id(&app), Some("news")); + assert!(matches_running_app(&app, "hub:news", "News")); + assert!(!matches_running_app(&app, "news", "News")); + assert!(!matches_running_app(&app, "hub:news-other", "News")); + // A system app is opened by the manifest id its row carries. + let system = card_row("mail".into(), "Mail".into(), vec![format!("{SYSTEM_ARG}os.mail")]); + assert_eq!(card_manifest_id(&system), Some("os.mail")); + assert!(!matches_running_app(&system, "mailer", "Mail"), "a Card app focuses only itself"); + } + + /// The system apps this build packs (system-apps.json): each a Card app + /// under its short id, and each bundle registered with the runner. + #[cfg(feature = "app-hub")] + #[test] + fn the_system_apps_ship_as_card_apps() { + let ids: Vec = system_card_apps().into_iter().map(|app| app.id).collect(); + assert_eq!(ids, ["news", "photos", "maps", "camera", "mail"]); + let registry = AppRegistry::default(); + for id in &ids { + assert_eq!(registry.hosting(id), Hosting::Module); + assert!(is_linked(id)); + } + assert_eq!(registry.hosting("apphub"), Hosting::Module, "the store has no process form"); + assert!(octosense_app_hub_app::system_icon("camera").is_some(), "Camera ships its own icon"); + } + #[test] fn overrides_parse_the_settings_shape_and_ignore_noise() { let text = "// which apps run in-process\n{\n sheets: Module,\n Terminal: process\n files: Sideways\n nonsense\n}\n"; diff --git a/src/clients.rs b/src/clients.rs index 6ed8d46b..c56cb4fa 100644 --- a/src/clients.rs +++ b/src/clients.rs @@ -123,12 +123,37 @@ fn manifest_value(manifest: &str, key: &str) -> Option { /// The app registry: the applications this WM is built around, in menu /// order. Curated on purpose — every row is one we run and verify, not a -/// scan of whatever the workspace happens to contain. -pub fn registry() -> &'static [AppDef] { - match crate::octosense::catalog::loaded() { - Ok(apps) => apps, - Err(_) => &[], - } +/// scan of whatever the workspace happens to contain: the catalog's rows, +/// the linked modules, then the apps App Hub's Card runner hosts (system +/// apps and installed apps, read fresh so an install needs no restart). +pub fn registry() -> Vec { + let base = crate::octosense::catalog::loaded().as_ref().cloned().unwrap_or_default(); + merge_catalog( + base, + crate::apps::bundled_modules_catalog(), + crate::apps::system_card_apps(), + crate::apps::installed_card_apps(), + ) +} + +/// One row per id. A system app replaces a catalog row of the same id in +/// place (it keeps that row's menu position): the system Mail, not +/// Makepad's example Mail, even in a personal catalog. Otherwise the first +/// definition wins — catalog rows, then linked modules. Installed ids live +/// under `hub:`, so a manifest named after a built-in becomes its own row +/// beside it, never a replacement. The internal `card` host is no row. +fn merge_catalog(base: Vec, bundled: Vec, system: Vec, installed: Vec) -> Vec { + let mut system: Vec> = system.into_iter().map(Some).collect(); + let mut take_system = |id: &str| system.iter_mut().find(|a| a.as_ref().is_some_and(|a| a.id == id)).and_then(Option::take); + let mut rows: Vec = Vec::new(); + for app in base.into_iter().chain(bundled) { + rows.push(take_system(&app.id).unwrap_or(app)); + } + rows.extend(system.into_iter().flatten()); + rows.extend(installed); + let mut ids = std::collections::HashSet::new(); + rows.retain(|app| app.id != "card" && ids.insert(app.id.clone())); + rows } /// Registered ids take precedence over binary aliases. A linked module @@ -136,9 +161,9 @@ pub fn registry() -> &'static [AppDef] { /// process form) is still an app: its bundled definition answers, and the /// hosting rules decide whether it may open (`--module ` on a desktop). pub fn find_app(id: &str) -> Option { - registry().iter().find(|a| a.id == id) - .or_else(|| registry().iter().find(|a| a.bin == id)).cloned() - .or_else(|| crate::apps::bundled_catalog().into_iter().find(|a| a.id == id)) + let apps = registry(); + apps.iter().find(|a| a.id == id) + .or_else(|| apps.iter().find(|a| a.bin == id && a.bin != "card")).cloned() } /// `bin/omarchy-launch-or-focus`'s window test, verbatim: @@ -992,6 +1017,22 @@ pub fn spawn_client( mod tests { use super::*; + #[test] + fn system_apps_replace_catalog_rows_and_installed_apps_never_shadow() { + let app = |id: &str, label: &str, bin: &str| AppDef::app(id, label, "", "", bin, LaunchPolicy::OrFocus); + let base = vec![app("browser", "Browser", "browser"), app("mail", "Makepad Mail", "mail"), app("notes", "Notes", "notes")]; + let bundled = vec![app("apphub", "App Hub", "apphub"), app("card", "Internal host", "card")]; + let system = vec![app("news", "News", "card"), app("mail", "Mail", "card")]; + let installed = vec![app("hub:demo", "Demo", "card"), app("hub:mail", "Untrusted Mail", "card")]; + let merged = merge_catalog(base, bundled, system, installed); + assert_eq!( + merged.iter().map(|a| a.id.as_str()).collect::>(), + ["browser", "mail", "notes", "apphub", "news", "hub:demo", "hub:mail"] + ); + // The system Mail took the example's place, not its name only. + assert_eq!((merged[1].label.as_str(), merged[1].bin.as_str()), ("Mail", "card")); + } + /// Cargo's checkout is Cargo's to manage: a build there is invisible to /// `cargo clean`, survives no refetch, and quietly grows the shared /// cache. Apps from the pinned revision build into OctoSense's own tree. @@ -1131,6 +1172,11 @@ mod tests { }; let mut metadata = std::collections::HashMap::new(); for app in registry() { + // Linked modules and the apps the Card runner hosts are no + // process: nothing to build. + if app.package.is_empty() { + continue; + } let manifest = app .manifest .clone() diff --git a/src/desktop.rs b/src/desktop.rs index 98197585..ab5e8a49 100644 --- a/src/desktop.rs +++ b/src/desktop.rs @@ -892,7 +892,7 @@ impl DesktopShelf { pub fn app_icon(id: &str) -> Ico { match id { "browser" => Ico::Globe, - "photos" | "image" => Ico::Photo, + "photos" | "image" | "camera" => Ico::Photo, "terminal" => Ico::Keyboard, "files" => Ico::Menu, "sheets" => Ico::Calendar, @@ -902,7 +902,7 @@ pub fn app_icon(id: &str) -> Ico { "mixer" => Ico::Speaker, "vj" => Ico::Headphone, "score" => Ico::Bell, - "route" => Ico::Globe, + "route" | "maps" => Ico::Globe, "fabric" => Ico::Shirt, "fab" => Ico::Refresh, "studio" => Ico::Moon, diff --git a/src/main.rs b/src/main.rs index 62295c88..ae5e617d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -556,14 +556,24 @@ impl App { return; }; let app = &app; + // An installed app opens only while the App Hub catalog still admits + // it. A system app (`os.*`) ships with the build and answers to no + // catalog. + #[cfg(feature = "app-hub")] + if let Some(manifest_id) = apps::card_manifest_id(app).filter(|id| !id.starts_with("os.")) { + if let Err(error) = octosense_app_hub_app::catalog::try_may_open_from_environment( + octosense_app_hub_app::data_root(cx), manifest_id) { + self.notify(cx, "Could not open app", &error); + return; + } + } let hub_port = self.state_mut().hub_port; // launch-or-focus for non-terminal apps: `omarchy-launch-or-focus` // matches `\b\b` case-insensitively against the window's - // CLASS OR TITLE and focuses the first hit. + // CLASS OR TITLE and focuses the first hit. A Card app focuses only + // an instance of itself (`apps::matches_running_app`). if app.policy == LaunchPolicy::OrFocus { - let pattern = app.id.clone(); - let pattern = pattern.as_str(); let mut existing: Vec = self .state_mut() .clients @@ -576,8 +586,7 @@ impl App { && !slot.pane && !slot.is_preview && slot.closing.is_none() - && (clients::word_match(&slot.app, pattern) - || clients::word_match(&slot.title, pattern)) + && apps::matches_running_app(app, &slot.app, &slot.title) }) .map(|(id, _)| *id) .collect(); @@ -595,7 +604,7 @@ impl App { // isolate of its own — never a process, never the pool. if self.apps.hosting(app_id) == Hosting::Module { if let Some(module) = self.apps.module(app_id) { - self.launch_module(cx, module); + self.launch_module(cx, module, app); return; } } @@ -1371,6 +1380,22 @@ impl App { self.request_close(cx, focus); } + /// App Hub finished installing (or updating) `id`. An update replaces + /// the bundle on disk, but a running Card keeps its old code and + /// permissions: end its instances so the next open takes the new one. + #[cfg(feature = "app-hub")] + fn installed_app_changed(&mut self, cx: &mut Cx, id: &str) { + let launch_id = apps::installed_launch_id(id); + let clients: Vec<_> = self.state_mut().clients.iter() + .filter_map(|(&client, slot)| (slot.app == launch_id).then_some(client)) + .collect(); + for client in clients { + self.request_close(cx, client); + } + octosense_app_hub_app::icons::invalidate(); + self.redraw_all(cx); + } + fn request_close(&mut self, cx: &mut Cx, client: ClientId) { // A module instance has no process to ask politely and nothing to // reap later: it ends now, through the same removal as a death. @@ -1941,8 +1966,8 @@ impl App { /// Open `module` as an instance of its own in this process: an isolate, /// a tile in the layout, a local endpoint on the bus. The ordinary /// launch path minus everything a process needs. - fn launch_module(&mut self, cx: &mut Cx, module: &'static dyn AppModule) { - let open = match module.open_schema().empty_open() { + fn launch_module(&mut self, cx: &mut Cx, module: &'static dyn AppModule, app: &clients::AppDef) { + let open = match apps::module_open(module, app) { Ok(open) => open, Err(e) => { log!("wm: {} cannot open without arguments: {}", module.id(), e); @@ -1964,7 +1989,7 @@ impl App { }; self.state_mut() .clients - .insert(id, clients::ClientSlot::module(id, module.id(), module.label())); + .insert(id, clients::ClientSlot::module(id, &app.id, &app.label)); let gap = self.state_mut().gap; self.state_mut().layout.insert(id, area, gap); // The tile is a module tile from its first draw; the root is seated @@ -1983,7 +2008,7 @@ impl App { let frame = self.ai_bus.register_local(id, manifest); self.send_to_pane(frame); } - log!("wm: launched {} as client {} (in-process)", module.id(), id); + log!("wm: launched {} as client {} (in-process, {})", app.id, id, module.id()); self.activate_client(cx, id); self.update_bar(cx); self.redraw_all(cx); @@ -3925,6 +3950,11 @@ fn scan_theme_color(source: &str, key: &str) -> Option { impl MatchEvent for App { fn handle_startup(&mut self, cx: &mut Cx) { + // Where App Hub keeps what it installs: `$OCTOSENSE_APP_DATA`, else + // `apps/` in the platform data directory or OctoSense's own state. + #[cfg(feature = "app-hub")] + octosense_app_hub_app::set_data_root(cx.get_data_dir().map(std::path::PathBuf::from) + .unwrap_or_else(octosense::paths::home).join("apps")); // CLI: --import-theme pulls an omarchy theme and converts // it to splash before the desktop appears. let mut args = std::env::args(); @@ -4130,6 +4160,14 @@ impl MatchEvent for App { let Some(wa) = action.as_widget_action() else { continue; }; + // The store opens what it offers: a built-in by id, an installed + // app by its manifest id. + #[cfg(feature = "app-hub")] + match wa.cast::() { + octosense_app_hub_app::AppHubAction::Launch(id) => self.launch_app(cx, &id), + octosense_app_hub_app::AppHubAction::OpenInstalled(id) => self.launch_app(cx, &apps::installed_launch_id(&id)), + octosense_app_hub_app::AppHubAction::None => {} + } // The shell surfaces: the bar's presses and wheel, the menu's // activations, the flyouts' controls. match wa.cast::() { @@ -4254,6 +4292,10 @@ impl MatchEvent for App { fn handle_signal(&mut self, cx: &mut Cx) { if self.state.is_some() { + #[cfg(feature = "app-hub")] + for id in octosense_app_hub_app::take_completed_installs() { + self.installed_app_changed(cx, &id); + } self.drain_hub(cx); self.drain_client_lines(cx); self.drain_module_upstream(); diff --git a/src/mobile_app.rs b/src/mobile_app.rs index 8715ff96..b01793ab 100644 --- a/src/mobile_app.rs +++ b/src/mobile_app.rs @@ -178,7 +178,7 @@ impl App { self.state_mut().phone.tiles.note_launch(app_id, host::now()); let Some(app) = crate::clients::find_app(app_id) else { return }; if self.apps.hosting(app_id) == Hosting::Module { - if let Some(module) = self.apps.module(app_id) { self.launch_tile_module(cx, module); } + if let Some(module) = self.apps.module(app_id) { self.launch_tile_module(cx, module, &app); } return; } if !host::processes_available() { return; } @@ -201,20 +201,20 @@ impl App { Err(err) => log!("wm: home tile launch of {} failed: {}", app.id, err), } } - fn launch_tile_module(&mut self, cx: &mut Cx, module: &'static dyn AppModule) { - let open = match module.open_schema().empty_open() { + fn launch_tile_module(&mut self, cx: &mut Cx, module: &'static dyn AppModule, app: &clients::AppDef) { + let open = match crate::apps::module_open(module, app) { Ok(open) => open, Err(e) => { log!("wm: {} cannot open without arguments: {}", module.id(), e); return; } }; let id = self.next_id; self.next_id += 1; - let viewport = self.tile_viewport(module.id()).unwrap_or_else(|| { let a = self.desk_area(cx); dvec2(a.w, a.h) }); + let viewport = self.tile_viewport(&app.id).unwrap_or_else(|| { let a = self.desk_area(cx); dvec2(a.w, a.h) }); if let Err(e) = self.module_host.create(cx, id, module, open, viewport) { log!("wm: module {} failed to start for its home tile: {}", module.id(), e); return; } let Some((manifest, root, vm_id)) = self.module_host.get(id).map(|i| (i.manifest(), i.root.clone(), i.vm_id)) else { return }; - self.state_mut().clients.insert(id, clients::ClientSlot::module(id, module.id(), module.label())); + self.state_mut().clients.insert(id, clients::ClientSlot::module(id, &app.id, &app.label)); self.desk(cx).borrow_mut::().map(|mut d| { d.mark_module(id); d.with_module_view(cx, id, |cx, v| v.set_root(cx, id, vm_id, root)); @@ -227,8 +227,8 @@ impl App { let frame = self.ai_bus.register_local(id, manifest); self.send_to_pane(frame); } - self.state_mut().phone.tiles.bind(module.id(), id, true); - log!("wm: launched {} as client {} for its home tile (in-process)", module.id(), id); + self.state_mut().phone.tiles.bind(&app.id, id, true); + log!("wm: launched {} as client {} for its home tile (in-process)", app.id, id); self.animate_phone(cx); } pub(super) fn configure_phone_mode(&mut self,cx:&mut Cx,previous:desktop::DesktopStyle,style:desktop::DesktopStyle) { diff --git a/src/module_host.rs b/src/module_host.rs index a9bed7a5..59171e9d 100644 --- a/src/module_host.rs +++ b/src/module_host.rs @@ -63,7 +63,26 @@ pub struct ModuleHost { /// still need its crate resource resolver for their bundled fonts. Expose /// only that existing resolver during theme registration, then remove it. fn apply_module_style(vm: &mut ScriptVm, sheet: &desktop_style::StyleSheet) { - desktop_style::install(vm, sheet.clone()); + let mut inherited = sheet.clone(); + // A nested Splash (a Card app the `card` module runs) replays this + // trusted theme after its ambient `mod.res` has been stripped. Bind only + // the existing bundled-resource resolver in the theme's lexical scope, so + // that replay can still load the style's fonts. This does not publish a + // resource module to the card's source. + inherited.theme = format!( + "mod._octosense_widgets_before_style = mod.widgets\n\ + mod._octosense_prelude_before_style = mod.prelude.widgets\n\ + let crate_resource = mod.prelude.widgets.crate_resource\n{}", inherited.theme); + // widgets_mod rebuilds these namespaces, including the prelude a Card's + // lowered body uses. Retain host additions while letting the freshly + // themed framework names replace their old ones. + inherited.widgets = format!( + "{}\n\ + mod.widgets = {{..mod._octosense_widgets_before_style, ..mod.widgets}}\n\ + mod.prelude.widgets = {{..mod._octosense_prelude_before_style, ..mod.prelude.widgets}}\n\ + mod._octosense_widgets_before_style = nil\n\ + mod._octosense_prelude_before_style = nil\n", inherited.widgets); + desktop_style::install(vm, inherited); vm.with_reload(|vm| { script_eval!(vm, { mod.res = {crate_resource: mod.prelude.widgets.crate_resource} }); makepad_widgets::widgets_mod(vm); diff --git a/src/octosense/catalog.rs b/src/octosense/catalog.rs index f1fab059..0509dca8 100644 --- a/src/octosense/catalog.rs +++ b/src/octosense/catalog.rs @@ -159,7 +159,8 @@ pub fn loaded() -> &'static Result, String> { static CATALOG: OnceLock, String>> = OnceLock::new(); CATALOG.get_or_init(|| { let Some(path) = catalog_path()? else { - return Ok(crate::apps::bundled_catalog()); + // `clients::registry` adds the linked modules and Card apps. + return Ok(Vec::new()); }; let path = if path.is_absolute() { path diff --git a/src/octosense/style.rs b/src/octosense/style.rs index 8f196006..b7389260 100644 --- a/src/octosense/style.rs +++ b/src/octosense/style.rs @@ -50,7 +50,9 @@ impl DesktopStyle { pub fn load_sheet(style: DesktopStyle, dark: bool) -> StyleSheet { if style != DesktopStyle::OctoSense { - return StyleSheet::load_with_appearance(style.framework(), dark); + let mut sheet = StyleSheet::load_with_appearance(style.framework(), dark); + sheet.icons = icon_assets(style.framework()); + return sheet; } let read = |name: &str, bundled: &str| { // Source checkouts reload on selection; installed/mobile builds use embedded data. @@ -74,15 +76,122 @@ pub fn load_sheet(style: DesktopStyle, dark: bool) -> StyleSheet { name: if dark { "macos-dark" } else { "macos" }.into(), theme: read(theme_name, theme), widgets: read(widgets_name, widgets), - icons: app_icon::load_assets(UpstreamStyle::Macos), + icons: icon_assets(UpstreamStyle::Macos), } } +/// The framework's artwork for a style, plus what this shell's apps need: +/// App Hub's own store icon, and for Maps (a system app the framework has no +/// art for) the route app's map, which it is the successor of. News, Photos +/// and Mail keep the framework's art; a system app whose bundle ships an +/// icon (Camera) draws that instead (`InstalledIcons`). +pub fn icon_assets(style: UpstreamStyle) -> Vec { + fn wear(assets: &mut Vec, name: &str, svg: String) { + match assets.iter_mut().find(|asset| asset.name == name) { + Some(asset) => asset.svg = svg, + None => assets.push(app_icon::IconAsset { name: name.into(), svg }), + } + } + let mut assets = app_icon::load_assets(style); + if !assets.iter().any(|asset| asset.name == "maps") { + if let Some(route) = assets.iter().find(|asset| asset.name == "route").map(|asset| asset.svg.clone()) { + wear(&mut assets, "maps", route); + } + } + #[cfg(feature = "app-hub")] + wear(&mut assets, "apphub", octosense_app_hub_app::APP_ICON_SVG.into()); + assets.sort_by(|a, b| a.name.cmp(&b.name)); + assets +} + #[derive(Default)] -pub struct AppIconDraw(app_icon::AppIconDraw); +pub struct AppIconDraw { + draw: app_icon::AppIconDraw, + #[cfg(feature = "app-hub")] + library: InstalledIcons, + /// The styles whose icon catalog this drawer has seen to, by discriminant. + installed: [bool; UpstreamStyle::ALL.len()], +} impl AppIconDraw { pub fn draw(&mut self, cx: &mut Cx2d, name: &str, style: DesktopStyle, rect: Rect, opacity: f32, ink: Vec4f) { - self.0.draw(cx, name, style.framework(), rect, opacity, ink); + #[cfg(feature = "app-hub")] + if self.library.draw(cx, name, rect, opacity) { + return; + } + let style = style.framework(); + // A style can be drawn before its sheet is applied (a crossfade's + // target, the first frame); the framework would then fall back to + // its own artwork, which has no Maps or App Hub. + if !std::mem::replace(&mut self.installed[style as usize], true) { + app_icon::install(cx, style, &icon_assets(style)); + } + self.draw.draw(cx, name, style, rect, opacity, ink); + } +} + +/// Launcher art App Hub owns: an installed app's icon (`hub:`), and a +/// system app's own art when its bundle ships one (ADR 0004). +#[cfg(feature = "app-hub")] +#[derive(Default)] +struct InstalledIcons { + root: Option, + generation: u64, + entries: std::collections::HashMap>, +} +#[cfg(feature = "app-hub")] +enum InstalledIcon { + Svg(DrawSvg), + Png(DrawImage, Texture), +} + +#[cfg(feature = "app-hub")] +impl InstalledIcons { + fn draw(&mut self, cx: &mut Cx2d, name: &str, rect: Rect, opacity: f32) -> bool { + use octosense_app_hub_app::icons::{self, IconData}; + let system = name.strip_prefix("hub:").is_none() && crate::apps::system_card_apps().iter().any(|a| a.id == name); + let Some(id) = name.strip_prefix("hub:").or(system.then_some(name)) else { return false; }; + let Some(root) = octosense_app_hub_app::data_root_if_set() else { return false; }; + let generation = icons::generation(); + if self.root.as_ref() != Some(&root) || self.generation != generation { + self.entries.clear(); + self.root = Some(root.clone()); + self.generation = generation; + } + if self.entries.len() >= 256 && !self.entries.contains_key(name) { + self.entries.clear(); + } + let icon = self.entries.entry(name.into()).or_insert_with(|| { + let data = if system { octosense_app_hub_app::system_icon(id)? } else { icons::read_installed_icon(&root, id)? }; + match data { + IconData::Svg(source) => { + let mut draw = cx.with_vm(|vm| DrawSvg::script_new_with_default(vm)); + draw.load_from_str(&source); + let (width, height) = draw.svg_doc.as_ref()?.logical_size(); + draw.content_bounds = (0.0, 0.0, width, height); + Some(InstalledIcon::Svg(draw)) + } + IconData::Png(data) => { + let buffer = image_cache::ImageBuffer::from_png(&data).ok()?; + let texture = buffer.into_new_texture(cx); + let draw = cx.with_vm(|vm| DrawImage::script_new_with_default(vm)); + Some(InstalledIcon::Png(draw, texture)) + } + } + }); + match icon { + Some(InstalledIcon::Svg(draw)) => { + draw.color = vec4(-1.0, -1.0, -1.0, -1.0); + draw.opacity = opacity; + draw.draw_abs(cx, rect); + } + Some(InstalledIcon::Png(draw, texture)) => { + draw.draw_vars.set_texture(0, texture); + draw.opacity = opacity; + draw.draw_abs(cx, rect); + } + None => return false, + } + true } } @@ -102,7 +211,7 @@ mod tests { assert_eq!(sheet.name, if dark { "macos-dark" } else { "macos" }); assert_eq!(StyleSheet::parse(&sheet.to_json()), Some(sheet.clone())); assert_eq!(UpstreamStyle::parse(&sheet.name), Some(UpstreamStyle::Macos)); - assert_eq!(sheet.icons, app_icon::load_assets(UpstreamStyle::Macos)); + assert_eq!(sheet.icons, icon_assets(UpstreamStyle::Macos)); desktop_style::install(vm, sheet); vm.bx.captured_errors = Some(Vec::new()); vm.with_reload(makepad_widgets::script_mod); diff --git a/src/shell/launcher.rs b/src/shell/launcher.rs index cf31e498..67cb8edb 100644 --- a/src/shell/launcher.rs +++ b/src/shell/launcher.rs @@ -56,7 +56,9 @@ fn icon_for(id: &str) -> Option { "image" => Ico::Monitor, "video" => Ico::Play, "pdf" => Ico::Check, - "route" => Ico::Globe, + "route" | "maps" => Ico::Globe, + "apphub" => Ico::Menu, + "camera" => Ico::Photo, "mixer" => Ico::Speaker, "vj" => Ico::Headphone, "photos" => Ico::Photo, diff --git a/system-apps.json b/system-apps.json new file mode 100644 index 00000000..390574e0 --- /dev/null +++ b/system-apps.json @@ -0,0 +1,6 @@ +{ + "schema": 1, + "source": "../OctoSense-System-Apps/apps", + "apps": ["news", "photos", "maps", "camera", "mail"], + "assets": {} +} From 5c450252ceccd1b8d943cd41f823f1169f53b78f Mon Sep 17 00:00:00 2001 From: ychen Date: Sat, 26 Sep 2026 00:37:13 -0700 Subject: [PATCH 18/42] Catalog: the system Mail and Photos take their ids from Makepad's examples config/apps.overlay.json drops Makepad's example `mail` and `photos` programs; config/apps.json and config/apps.makepad.json are regenerated with `scripts/upstream.py catalog --apply`. The other Makepad programs stay as the desktop's developer and tool apps. Co-Authored-By: Claude Opus 5.5 (1M context) --- config/apps.json | 16 ---------------- config/apps.makepad.json | 16 ---------------- config/apps.overlay.json | 5 ++++- 3 files changed, 4 insertions(+), 33 deletions(-) diff --git a/config/apps.json b/config/apps.json index 756c4644..c4d36887 100644 --- a/config/apps.json +++ b/config/apps.json @@ -58,14 +58,6 @@ "bin": "sheets", "policy": "focus" }, - { - "id": "photos", - "label": "Photos", - "source": "makepad", - "package": "makepad-photos", - "bin": "photos", - "policy": "focus" - }, { "id": "clock", "label": "Clock", @@ -90,14 +82,6 @@ "bin": "finance", "policy": "focus" }, - { - "id": "mail", - "label": "Mail", - "source": "makepad", - "package": "makepad-mail", - "bin": "mail", - "policy": "focus" - }, { "id": "notes", "label": "Notes", diff --git a/config/apps.makepad.json b/config/apps.makepad.json index 756c4644..c4d36887 100644 --- a/config/apps.makepad.json +++ b/config/apps.makepad.json @@ -58,14 +58,6 @@ "bin": "sheets", "policy": "focus" }, - { - "id": "photos", - "label": "Photos", - "source": "makepad", - "package": "makepad-photos", - "bin": "photos", - "policy": "focus" - }, { "id": "clock", "label": "Clock", @@ -90,14 +82,6 @@ "bin": "finance", "policy": "focus" }, - { - "id": "mail", - "label": "Mail", - "source": "makepad", - "package": "makepad-mail", - "bin": "mail", - "policy": "focus" - }, { "id": "notes", "label": "Notes", diff --git a/config/apps.overlay.json b/config/apps.overlay.json index 0291d183..148fa6bf 100644 --- a/config/apps.overlay.json +++ b/config/apps.overlay.json @@ -20,8 +20,11 @@ "bin": "route" } }, + "_drop": "mail and photos are OctoSense system apps (OctoSense-System-Apps, run by App Hub's Card runner) and take these ids; Makepad's example programs of the same name are not shipped.", "drop": [ - "scope" + "scope", + "mail", + "photos" ], "append": [ {"id": "image", "label": "Image Viewer", "source": "makepad", "package": "makepad-image", "bin": "image", "policy": "new"}, From 0ef0c0aa57a1ba6d82f6962fa5994c3c449fd6ae Mon Sep 17 00:00:00 2001 From: ychen Date: Sat, 26 Sep 2026 00:37:13 -0700 Subject: [PATCH 19/42] README: the app model, the pinned siblings and the Mail demo System apps, store apps, native modules and developer programs; the sibling sources and the runtime patch; running Mail with its demo mailbox and the keychain note. Validation record for this change. Co-Authored-By: Claude Opus 5.5 (1M context) --- README.md | 101 ++++++++++++++++++++++++++++++++++++--------- docs/validation.md | 25 +++++++++++ 2 files changed, 106 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 06e2667d..cff77d31 100644 --- a/README.md +++ b/README.md @@ -7,14 +7,69 @@ release. Its `runtime.json` owns the exact Makepad and Octoscript revisions, shared with AppCards, Mail and the other OctoSense applications. -Before building, run `python3 tools/setup-native.py` (Python 3.9+). The framework -repositories are siblings of this app: `../octoscript-makepad`, `../makepad` -and `../octoscript`. Local changes are preserved; `--update` only updates clean -checkouts. CI verifies the selected release and rejects duplicate Makepad sources. -Use `python3 tools/setup-native.py --check --cargo-manifest Cargo.toml` +Before building, run `python3 tools/setup-native.py` (Python 3.9+). The pinned +repositories are siblings of this app: + +| Sibling | Pinned by | What it is | +| --- | --- | --- | +| `../octoscript-makepad` | `native-runtime.lock.json` | the runtime release | +| `../makepad`, `../octoscript` | that release's `runtime.json` | the framework; Makepad carries the reviewed patch in `runtime-patches.lock.json` | +| `../OctoSense-System-Apps` | `native-apps.lock.json` | the system apps' bundles, the Mail host service and the AppCard assistant | + +Makepad's patch is [OctoSense-org/makepad#30](https://github.com/OctoSense-org/makepad/pull/30) +(contained script apps, host services), the same one OctoSense ROM's Home +applies; setup applies it to the locked revision and checks the resulting tree. +A clean checkout of the commit the patch was cut from (`source_commit`) is +accepted as the same tree. Local changes are preserved; `--update` only updates +clean checkouts. CI verifies the selected release and rejects duplicate Makepad +and octos sources. Use `python3 tools/setup-native.py --check --cargo-manifest Cargo.toml` to check the local dependency graph. Existing platform rendering backends remain part of their applications; the framework controls the shared VM and UI sources. +## App model + +OctoSense-Desktop takes its apps the way OctoSense ROM's Home does. There are +four kinds, and the launcher lists them together: + +| Kind | Where it comes from | How it runs | +| --- | --- | --- | +| **System apps** — News, Photos, Maps, Camera, Mail | [OctoSense-System-Apps](https://github.com/OctoSense-org/OctoSense-System-Apps) `apps//bundle`, chosen by [system-apps.json](system-apps.json) and packed into the build | contained Splash programs (ADR 0004), each in its own isolate under the permissions its manifest asks for, run by App Hub's Card runner (the `card` module); ids `os.`, launcher ids `` | +| **Store apps** | the signed App Hub catalog, installed from the App Hub store (`apphub`) | the same Card runner, under `hub:`; each open is checked against the catalog, and an update closes the old instances | +| **Native modules** | Rust crates linked into this binary | in-process `AppModule`s; only trusted ones: App Hub (store and Card runner), the AppCard assistant, Reference, and the optional `app-*` features | +| **Developer programs** | [config/apps.json](config/apps.json): Reference and Makepad's own apps (Browser, Files, Terminal, Sheets, Notes, …) | separate processes in tiles, built on first launch (see *Add an app*) | + +App Hub is on by default (feature `app-hub`): it links +[`octosense-app-hub-app`](https://github.com/OctoSense-org/OctoSense-App-Hub/tree/4605128d46fb982828d8198e0d71d62a39c7d6d6/crates/app-hub-app) +and the Mail host service (`octosense-mail-service`, in +`../OctoSense-System-Apps/apps/mail/host-service`), which keeps Mail's accounts +and passwords on the host; the Mail app never gets a socket or a secret. A +system app takes precedence over a catalog row of the same id, so Makepad's +example **Mail** and **Photos** programs are no longer in `config/apps.json` +or `config/apps.makepad.json` (`drop` in `config/apps.overlay.json`); if a +personal catalog still lists them, the system app replaces the row. A linked +native module of the same id wins over a system app, for comparison builds +(`--features app-photos` links Makepad's native Photos). + +`.cargo/config.toml` sets `OCTOSENSE_SYSTEM_APPS` to `system-apps.json`; +without it the build ships no system apps. The desktop has no photo library of +its own, so `system-apps.json` mounts no `photos` assets: Photos shows its +sample library from the thumbnails its bundle ships (`thumbs/`, every photo), +and a photo opened full size has no full-resolution file behind it. Home +mounts its own `apps/photos/resources/photos` (87 MB) for that; a desktop that +wants it adds `"assets": {"photos": {"photos": ""}}` to its selection. + +To try Mail without an account or the keychain, run with its demo mailbox +(a file vault, no network, password `demo`): + +```sh +MAKEPAD_APP_CONFIG='{"mail_demo":true}' cargo run --release +``` + +With a real account, Mail stores passwords in the macOS keychain; an +unsigned development binary is a new program to the keychain after every +rebuild, so macOS asks again each time. `OCTOSENSE_MAIL_VAULT=file` keeps +them in a 0600 file in Mail's host directory instead, for development. + A Makepad desktop that hosts compatible applications inside one window. The shell comes from Makepad's WM app; framework libraries remain external Cargo dependencies pinned to the same upstream commit. @@ -63,15 +118,21 @@ cargo makepad android run -p octosense --release The Android launcher label is **OctoSense** and its application ID is `dev.makepad.octosense`. It installs separately from an existing MakeOS Android app because the application ID changed. `run` builds, installs, and launches the app; `build` only creates the APK. -Native Android/iOS builds automatically link **Reference, Sheets, and Photos** -as embedded apps. They need no extra feature flags after runtime setup. On an -installed device, the launcher derives its default catalog from those linked -modules. Missing Clock/Weather tiles give their space to the available app icons. +Native Android/iOS builds automatically link **Reference and Sheets** as +embedded apps, and App Hub with the system apps (News, Photos, Maps, Camera, +Mail) through the default `app-hub` feature. They need no extra feature flags +after runtime setup. On an installed device, the launcher derives its default +catalog from those linked modules and system apps. Missing Clock/Weather tiles +give their space to the available app icons. The phone build also links **AppCard** (`apps/appcard`, feature `app-appcard` -on desktop): the whole Octoscript-AppCard app, hosted in-process in a wide -home tile. The module takes `octos-app` — the crate the standalone AppCard APK -is built from — as a git library and mounts its `AppShell` widget: the routing +on desktop): the whole AppCard assistant, hosted in-process in a wide home +tile. The module takes `octos-app` — the crate the standalone AppCard APK is +built from — from the pinned OctoSense-System-Apps checkout +(`apps/appcard/app/app`, without its default `standalone` feature; every octos +crate comes from git octos-org/octos at the one rev octos-app pins, and +`OCTOSENSE_WORKSPACE` in `.cargo/config.toml` points its asset embedding at the +sibling framework checkouts) and mounts its `AppShell` widget: the routing brain, the card store and transport, the L0 lowering pipeline, sessions, the composer and the kernel agent all run inside the tile's isolate. The kernel (`liboctos.so serve --stdio`) is spawned from this APK's native library dir @@ -110,16 +171,15 @@ the app, not your desktop photo library; local Qwen weights are not packaged. To exercise the same embedded apps on desktop: ```sh -cargo run --features mobile-apps -- --module reference --module sheets --module photos +cargo run --features mobile-apps -- --module reference --module sheets ``` Reference shares its counter and text-input view between the standalone desktop -process and the embedded mobile module. Sheets and Photos remain external Git -crates at the same pinned Makepad revision. +process and the embedded mobile module. Sheets remains an external Git crate at +the same pinned Makepad revision. -Mobile app support is still partial: Sheets needs grid-label and toolbar fixes, -and Photos needs a picture library/import setup. These follow-ups are tracked -in [BACKLOG.md](BACKLOG.md). +Mobile app support is still partial: Sheets needs grid-label and toolbar fixes. +These follow-ups are tracked in [BACKLOG.md](BACKLOG.md). The iOS startup policy is covered by tests, but a complete iOS build currently fails in the pinned Makepad Metal backend; see [validation](docs/validation.md). @@ -132,7 +192,7 @@ The default [config/apps.json](config/apps.json) includes Reference and Makepad' cargo run -- --apps config/apps.makepad.json ``` -It includes Reference plus Makepad's Browser, Files, Terminal, Mixer, Task Manager, Sheets, Photos, Clock, Weather, Finance, Mail, Notes, Calendar, Reminders, Calculator, Fabric, Score, Video Player, Route, VJ, Fab and Director. Image/PDF viewers are registered for file-opening and previews, and AI is registered for the assistant pane (F10). These three helper apps also appear in the launcher unless their IDs (`image`, `pdf`, `aichat`) are listed in `~/.octosense/wm/launcher.hides`. +It includes Reference plus Makepad's Browser, Files, Terminal, Mixer, Task Manager, Sheets, Clock, Weather, Finance, Notes, Calendar, Reminders, Calculator, Fabric, Score, Video Player, Route, VJ, Fab and Director. Image/PDF viewers are registered for file-opening and previews, and AI is registered for the assistant pane (F10). These three helper apps also appear in the launcher unless their IDs (`image`, `pdf`, `aichat`) are listed in `~/.octosense/wm/launcher.hides`. Makepad's apps carry `"source": "makepad"` instead of a path: they resolve through Cargo's dependency graph to the shared runtime checkout prepared above, or to Cargo's cached checkout when Git dependencies are used without path overrides. Each app builds on demand using its package's normal default features. Those builds go to `~/.octosense/build/makepad` rather than into Cargo's cache, which Cargo alone manages. The catalog uses the workspace root manifest to preserve the apps' expected working directory. Files retains the catalog's `--demo` argument; remove it to browse your real filesystem. Fab uses its built-in demo unless you add explicit file arguments. Upstream replaced Studio with Director; the catalog keeps the `studio` ID for existing launch references and runs `makepad-director`. No apps start automatically; `--assistant` remains opt-in. @@ -193,7 +253,7 @@ OctoSense state lives under `~/.octosense`; `OCTOSENSE_HOME` selects another dir Optional launch flags are `--assistant`, `--prewarm`, `--demo-home`, and `--download-wallpapers`. Assistant/prewarm flags require matching apps in your catalog. Theme importing remains an explicit action in the theme menu. -Upstream's linked-module infrastructure is retained behind `app-sheets`, `app-photos`, and `app-aichat` Cargo features; all are off by default. A module must be linked and selected with `--module ` or `wm/apps.splash`. This initial milestone validates process hosting. It does not provide runtime loading of native shared libraries or embedding of unrelated native desktop windows. +Upstream's linked-module infrastructure is retained behind `app-sheets`, `app-photos`, and `app-aichat` Cargo features; all are off by default. A module must be linked and selected with `--module ` or `wm/apps.splash`. App Hub's modules are the exception: the store and the apps its Card runner hosts (system and installed apps) have no process form and always open in-process. This initial milestone validates process hosting. It does not provide runtime loading of native shared libraries or embedding of unrelated native desktop windows. ## Local AI setup @@ -237,6 +297,7 @@ manual commands and how to investigate a failed candidate. ```sh cargo test --locked --workspace +cargo test --locked --workspace --features mobile-apps python3 -m unittest discover -s scripts -p 'test_*.py' ``` diff --git a/docs/validation.md b/docs/validation.md index bcd79961..5afbed59 100644 --- a/docs/validation.md +++ b/docs/validation.md @@ -431,3 +431,28 @@ The diagnostic call stack is in `target/upstream-20260911/trace-tap/host.log`. The starting dirty working tree is preserved in `target/upstream-20260911/before/`; the integration diff and exact file list are saved beside it. Source checkouts were read only. + +## App Hub, system apps and the AppCard repin, 2026-09-26 + +Branch `apps/app-hub-system-apps`, on macOS 26 (Apple silicon), with the +siblings prepared by `tools/setup-native.py` (makepad at the patched tree +`3889a349`, OctoSense-System-Apps `4d99cb58`). + +- `cargo check --workspace`, and with `--features mobile-apps`: clean. +- `cargo test --workspace`: 228 passed; with `--features mobile-apps`: 231 + passed (the bundled-catalog test lists the five system apps as Card apps and + opens every native module). `cargo test -p octosense-mail-service`: 7 passed. +- `python3 -m unittest discover -s scripts -p 'test_*.py'`: 57 passed. +- `python3 scripts/upstream.py catalog`: the catalog matches the pinned + revision under the overlay (which now drops `mail` and `photos`). +- `setup-native.py --check --cargo-manifest Cargo.toml` on a fresh sibling + workspace: one Makepad, one octos (`octos-core` from octos-org/octos + `18fcd3f1` only, also with `--features app-rinx`: Rinx `5ad2c173` pins no + octos), one `octosense-appstore`. +- Release run with `MAKEPAD_REMOTE` and `MAKEPAD_APP_CONFIG='{"mail_demo":true}'` + (isolated `OCTOSENSE_HOME` and `OCTOSENSE_APP_DATA`): the dock and launcher + list App Hub, News, Photos, Maps, Camera and Mail; Mail opened in the Card + runner (`card: os.mail running under 2 capability(ies)`), its sign-in sheet + drew over the app, the demo account (password `demo`) signed in and the + inbox listed the demo messages; Photos opened on its Collections page with + the bundle's thumbnails. No keychain prompt: the demo uses the file vault. From 5eef8d2e62ea588d7c8cce4dcf6b6389588c6930 Mon Sep 17 00:00:00 2001 From: ychen Date: Sat, 26 Sep 2026 01:13:34 -0700 Subject: [PATCH 20/42] README: a full guide to the desktop shell, with a Simplified Chinese version Rewrites the README around the post-App-Hub state: where the repo sits among the OctoSense repositories, layout, sibling setup with tools/setup-native.py, build/run and Cargo features, the app model (system apps, store apps, host services and host-owned sheets, native modules, developer programs, AppCard), demos (Mail demo, remote-control bridge, hidden-window runs), pinning, testing, CI scope, known gaps and contributing. Adds README.zh-CN.md with a language switcher in both. Co-Authored-By: Claude Opus 5.5 (1M context) --- README.md | 479 +++++++++++++++++++++++++----------------------- README.zh-CN.md | 345 ++++++++++++++++++++++++++++++++++ 2 files changed, 599 insertions(+), 225 deletions(-) create mode 100644 README.zh-CN.md diff --git a/README.md b/README.md index cff77d31..727d81c6 100644 --- a/README.md +++ b/README.md @@ -1,307 +1,319 @@ -# OctoSense +# OctoSense Desktop + +English | [简体中文](README.zh-CN.md) + +OctoSense-Desktop is the desktop shell of [OctoSense](https://github.com/OctoSense-org), the agent shell on top of your operating system. It is one Makepad window that is the desktop: a launcher, a dock and tiles, hosting system apps and App Hub store apps as contained script programs, trusted native modules in-process, and Makepad developer programs as child processes. It gets its apps the same way the phone shell, OctoSense-ROM's `home/`, does. + +## Where it sits + +| Repository | Role for this repo | +| --- | --- | +| [OctoSense-ROM](https://github.com/OctoSense-org/OctoSense-ROM) | The phone shell (`home/`). Same app model, same runtime patch, same system apps. | +| [OctoSense-System-Apps](https://github.com/OctoSense-org/OctoSense-System-Apps) | News, Photos, Maps, Camera and Mail bundles, the Mail host service and the AppCard assistant (`octos-app`). Pinned sibling checkout. | +| [OctoSense-App-Hub](https://github.com/OctoSense-org/OctoSense-App-Hub) | The signed catalog, the store and the Card runner. Linked as the Git crate `octosense-app-hub-app`. | +| [OctoScript-App-Design-Flow](https://github.com/OctoSense-org/OctoScript-App-Design-Flow) | Where apps are designed, built and published to the App Hub. | +| [OctoScript-Makepad](https://github.com/OctoSense-org/OctoScript-Makepad) | The runtime release that pins Makepad and OctoScript. Pinned sibling checkout. | +| [makepad (OctoSense fork)](https://github.com/OctoSense-org/makepad) | The framework. Pinned sibling checkout, plus a reviewed patch. | +| [octos](https://github.com/octos-org/octos) | The agent kernel behind the AppCard assistant. Git dependency, one revision (`18fcd3f1`). | + +## Repository layout + +| Path | What it is | +| --- | --- | +| `src/` | The shell (crate `octosense`): tiling, launcher, dock, bar, hosting (`host.rs`, `hub.rs`, `module_host.rs`), app registry (`apps.rs`). | +| `src/shell/` | Bar, launcher, menus, notifications, AI pane, gallery. | +| `src/octosense/` | OctoSense-specific parts: app catalog loading, state paths, styles, Makepad source resolution. | +| `apps/reference/` | `octosense-reference`, a small counter/text-input app that runs both as a hosted process and as a linked module. | +| `apps/appcard/` | `octosense-appcard`, the module that mounts the AppCard assistant (`octos-app`) in a tile. | +| `config/apps.json` | The default developer-program catalog. `apps.makepad.json` is an identical copy for `--apps`; `apps.overlay.json` holds the adaptations applied when regenerating them. | +| `system-apps.json` | Which system apps this build packs, and from where. | +| `native-runtime.lock.json` | The OctoScript-Makepad release (and through it, Makepad and OctoScript). | +| `native-apps.lock.json` | The OctoSense-System-Apps revision. | +| `runtime-patches.lock.json`, `patches/runtime/` | The reviewed Makepad patch ([OctoSense-org/makepad#30](https://github.com/OctoSense-org/makepad/pull/30)) and its expected tree. | +| `tools/setup-native.py` | Prepares and checks the pinned sibling checkouts. | +| `scripts/` | `upstream.py` (WM provenance and catalog regeneration), `smoke.py` (native smoke test), their Python tests, and `provision-appcard-llm.sh` (Android). | +| `upstream/makepad.json` | Provenance of every file imported from Makepad's `apps/wm`. | +| `resources/` | Themes, wallpapers, icons, Android manifest template, startup script. | +| `docs/` | [Validation record](docs/validation.md), [upstream sync](docs/upstream.md), [local AI](docs/local-ai.md), [Android AppCard build](docs/android-appcard-build.md), dated plans. | +| `KEYBINDINGS.md`, `BACKLOG.md` | Keymap notes; open follow-ups. | + +## Prerequisites + +- Stable Rust (`cargo` in `~/.cargo/bin`) and the native toolchain for your OS. On macOS, Xcode Command Line Tools (`xcode-select --install`). +- Git and Python 3.9+ for `tools/setup-native.py`. `scripts/upstream.py` needs Python 3.11+ (it imports `tomllib`). +- Network access for the first setup and build. + +## Set up the sibling workspace + +The build resolves Makepad, OctoScript, OctoScript-Makepad and OctoSense-System-Apps as **siblings** of this checkout (`../makepad`, and so on, through `[patch]` entries in `Cargo.toml`). Put this repository in its own workspace directory and let the setup script fill it in: -## Shared Octoscript-Makepad runtime +```sh +mkdir octosense-ws && cd octosense-ws +git clone https://github.com/OctoSense-org/OctoSense-Desktop.git +cd OctoSense-Desktop +python3 tools/setup-native.py +``` -`native-runtime.lock.json` selects one -[Octoscript-Makepad](https://github.com/OctoSense-org/Octoscript-Makepad) -release. Its `runtime.json` owns the exact Makepad and Octoscript revisions, -shared with AppCards, Mail and the other OctoSense applications. +Result: -Before building, run `python3 tools/setup-native.py` (Python 3.9+). The pinned -repositories are siblings of this app: +```text +octosense-ws/ + OctoSense-Desktop/ this repository + octoscript-makepad/ the release native-runtime.lock.json selects + makepad/, octoscript/ the revisions that release's runtime.json pins; makepad carries the patch + OctoSense-System-Apps/ the revision native-apps.lock.json pins +``` -| Sibling | Pinned by | What it is | -| --- | --- | --- | -| `../octoscript-makepad` | `native-runtime.lock.json` | the runtime release | -| `../makepad`, `../octoscript` | that release's `runtime.json` | the framework; Makepad carries the reviewed patch in `runtime-patches.lock.json` | -| `../OctoSense-System-Apps` | `native-apps.lock.json` | the system apps' bundles, the Mail host service and the AppCard assistant | - -Makepad's patch is [OctoSense-org/makepad#30](https://github.com/OctoSense-org/makepad/pull/30) -(contained script apps, host services), the same one OctoSense ROM's Home -applies; setup applies it to the locked revision and checks the resulting tree. -A clean checkout of the commit the patch was cut from (`source_commit`) is -accepted as the same tree. Local changes are preserved; `--update` only updates -clean checkouts. CI verifies the selected release and rejects duplicate Makepad -and octos sources. Use `python3 tools/setup-native.py --check --cargo-manifest Cargo.toml` -to check the local dependency graph. Existing platform rendering backends remain -part of their applications; the framework controls the shared VM and UI sources. - -## App model - -OctoSense-Desktop takes its apps the way OctoSense ROM's Home does. There are -four kinds, and the launcher lists them together: - -| Kind | Where it comes from | How it runs | -| --- | --- | --- | -| **System apps** — News, Photos, Maps, Camera, Mail | [OctoSense-System-Apps](https://github.com/OctoSense-org/OctoSense-System-Apps) `apps//bundle`, chosen by [system-apps.json](system-apps.json) and packed into the build | contained Splash programs (ADR 0004), each in its own isolate under the permissions its manifest asks for, run by App Hub's Card runner (the `card` module); ids `os.`, launcher ids `` | -| **Store apps** | the signed App Hub catalog, installed from the App Hub store (`apphub`) | the same Card runner, under `hub:`; each open is checked against the catalog, and an update closes the old instances | -| **Native modules** | Rust crates linked into this binary | in-process `AppModule`s; only trusted ones: App Hub (store and Card runner), the AppCard assistant, Reference, and the optional `app-*` features | -| **Developer programs** | [config/apps.json](config/apps.json): Reference and Makepad's own apps (Browser, Files, Terminal, Sheets, Notes, …) | separate processes in tiles, built on first launch (see *Add an app*) | - -App Hub is on by default (feature `app-hub`): it links -[`octosense-app-hub-app`](https://github.com/OctoSense-org/OctoSense-App-Hub/tree/4605128d46fb982828d8198e0d71d62a39c7d6d6/crates/app-hub-app) -and the Mail host service (`octosense-mail-service`, in -`../OctoSense-System-Apps/apps/mail/host-service`), which keeps Mail's accounts -and passwords on the host; the Mail app never gets a socket or a secret. A -system app takes precedence over a catalog row of the same id, so Makepad's -example **Mail** and **Photos** programs are no longer in `config/apps.json` -or `config/apps.makepad.json` (`drop` in `config/apps.overlay.json`); if a -personal catalog still lists them, the system app replaces the row. A linked -native module of the same id wins over a system app, for comparison builds -(`--features app-photos` links Makepad's native Photos). - -`.cargo/config.toml` sets `OCTOSENSE_SYSTEM_APPS` to `system-apps.json`; -without it the build ships no system apps. The desktop has no photo library of -its own, so `system-apps.json` mounts no `photos` assets: Photos shows its -sample library from the thumbnails its bundle ships (`thumbs/`, every photo), -and a photo opened full size has no full-resolution file behind it. Home -mounts its own `apps/photos/resources/photos` (87 MB) for that; a desktop that -wants it adds `"assets": {"photos": {"photos": ""}}` to its selection. - -To try Mail without an account or the keychain, run with its demo mailbox -(a file vault, no network, password `demo`): +| Command | Effect | +| --- | --- | +| `python3 tools/setup-native.py` | Clone missing siblings at their pinned revisions and apply the Makepad patch. | +| `python3 tools/setup-native.py --check` | Verify the siblings without changing anything. | +| `python3 tools/setup-native.py --check --cargo-manifest Cargo.toml` | Also check the locked Cargo graph: one Makepad, one octos, one App Hub. | +| `python3 tools/setup-native.py --update` | Move clean checkouts to the locked revisions (after the lock files change). | +| `--root DIR`, `--cache DIR` | Use another workspace directory; reuse local Git object caches. | + +Local changes in a sibling are preserved; `--update` only moves clean checkouts. A clean checkout of the commit the patch was cut from (`source_commit` in `runtime-patches.lock.json`) is accepted as the same tree. + +## Build and run + +From this directory, after setup: ```sh -MAKEPAD_APP_CONFIG='{"mail_demo":true}' cargo run --release +cargo run --release ``` -With a real account, Mail stores passwords in the macOS keychain; an -unsigned development binary is a new program to the keychain after every -rebuild, so macOS asks again each time. `OCTOSENSE_MAIL_VAULT=file` keeps -them in a 0600 file in Mail's host directory instead, for development. +The desktop starts empty. Start App Hub, a system app or a developer program from the dock, the top-left **Apps** menu, or **⌘Space** (menu and search). **System → Quit OctoSense** closes the desktop and everything it hosts. +Developer programs from `config/apps.json` build on first launch (progress shows in the tile). To build the workspace ahead of time: -A Makepad desktop that hosts compatible applications inside one window. The shell comes from Makepad's WM app; framework libraries remain external Cargo dependencies pinned to the same upstream commit. +```sh +cargo build --release --workspace +``` -## Run +| Platform | Status | +| --- | --- | +| macOS | Supported and validated (source builds, process hosting, App Hub, system apps). | +| Windows, Linux | Code paths are retained from upstream but not validated here. | +| Android | `cargo makepad android run -p octosense --release`; see [Phones](#phones). | +| iOS | Startup policy is tested, but a full build currently fails in the pinned Makepad Metal backend ([validation](docs/validation.md)). | -Install stable Rust and the native development tools for your OS. On macOS, install Xcode Command Line Tools (`xcode-select --install`) if needed. Validated with Rust 1.98.1 on macOS. +A relocatable `.app`, installers and a Linux session compositor are not provided. -From this directory: +### Cargo features -```sh -cargo run -``` +| Feature | Default | Effect | +| --- | --- | --- | +| `app-hub` | on | Links `octosense-app-hub-app` (store `apphub`, Card runner `card`, system apps) and the Mail host service `octosense-mail-service`. Without it the build has no App Hub and no system apps. | +| `app-reference` | off | Links Reference as a module. | +| `app-sheets` | off | Links Makepad's Sheets as a module. | +| `app-photos` | off | Links Makepad's native Photos module; it replaces the Photos system app of the same id (for comparison). | +| `app-appcard` | off | Links the AppCard assistant module (`apps/appcard`). | +| `app-aichat` | off | Links Makepad's AI chat as a module, without its model engine. | +| `app-rinx` | off | Links [Rinx](https://github.com/upstreamlabs/Rinx), the Matrix client, as a module. | +| `mobile-apps` | off | `app-reference` + `app-sheets` + `app-appcard` + `app-hub`: the set phone builds link, for testing on desktop. | -Open **Apps → Reference** from the top-left menu. The reference app has a counter and text input, and runs as a separate process inside a tile. Its first launch builds the release executable; build progress appears in the tile. Later launches reuse Cargo's build cache. To build both executables ahead of time: +A linked module opens with `--module ` (or a `: Module` line in `wm/apps.splash` under the state directory): ```sh -cargo build --release --workspace -cargo run --release +cargo run --release --features app-appcard -- --module appcard +cargo run --release --features mobile-apps -- --module reference --module sheets +cargo run --release --features app-rinx -- --module rinx ``` -Prepare the sibling framework sources with the setup command above; the first build downloads any remaining dependencies. The host and Reference app need no Studio process, model download, or wallpaper download. The default apps resolve through Cargo's dependency graph and build on first launch from the same Makepad checkout as the host; unavailable apps are hidden. Fonts and other framework resources are read from that checkout during source development, so keep it available. +App Hub's modules are the exception: they have no process form and always open in-process. -On macOS, `.cargo/config.toml` sets the native menu-bar name to **OctoSense**. -Makepad otherwise derives it from the checkout directory, which may still be -named `makeos`. Rebuild and relaunch after updating; Cargo regenerates the -development `Info.plist` automatically. +### Flags and environment -The build target selects the startup shell automatically: desktop/web builds start with **OctoSense Light** and its bundled wallpaper, Android uses the Android phone layout, and iOS uses the iOS phone layout. You can still switch styles from the shell's style menu. Native phone toolbars are 48 points high and respect the window's safe-area insets. +| Name | Effect | +| --- | --- | +| `--apps ` | Use this developer-program catalog. | +| `--module ` | Host a linked module in-process. | +| `--assistant`, `--prewarm` | Start the assistant app / prewarm apps (need matching catalog entries). Off by default. | +| `--demo-home`, `--download-wallpapers` | Generate a demo filesystem; fetch the Omarchy theme's full wallpaper set. | +| `OCTOSENSE_HOME` | State directory (default `~/.octosense`; falls back to an existing `~/.makeos`, and `MAKEOS_HOME`). | +| `OCTOSENSE_APP_DATA` | Where App Hub keeps installed apps (default `apps/` in the platform data directory). | +| `OCTOSENSE_HUB`, `OCTOSENSE_HUB_ANCHOR` | App Hub catalog origin (path or URL) and trust anchor; default is the App Hub repository's `main`. | +| `OCTOSENSE_SYSTEM_APPS` | The system-app selection file; `.cargo/config.toml` sets it to `system-apps.json`. | +| `MAKEPAD_APP_CONFIG='{"mail_demo":true}'` | Serve Mail's demo mailbox (see [Demos](#demos)). | +| `OCTOSENSE_MAIL_VAULT=file` | Keep Mail passwords in a 0600 file instead of the macOS keychain. | +| `OCTOS_APP_CORE_BIN`, `OCTOS_APP_CORE_DIR` | Point the AppCard assistant at a local octos kernel. | +| `MAKEPAD_REMOTE`, `MAKEPAD_HIDE_WINDOWS` | Remote-control bridge; hidden windows (see [Demos](#demos)). | -The default desktop starts empty. AI assistant startup, background app prewarming, demo filesystem generation, and wallpaper downloads are off. **System → Quit OctoSense** closes the desktop and its hosted processes. +## The app model -Selecting Omarchy uses a bundled Tokyo Night wallpaper, so its background also works offline on a fresh install. Installed images in `~/.octosense/wm/themes/tokyo-night/backgrounds/` take precedence. Use `cargo run -- --download-wallpapers` to fetch the theme’s full wallpaper set; **⌘CtrlSpace** cycles installed backgrounds. Asset provenance is in [resources/wallpapers/README.md](resources/wallpapers/README.md). +The launcher lists four kinds of app together: -Eight desktop styles are available. The default **OctoSense** is a floating desktop with Liquid Glass window frames, dock, bar and popups. Click **Light / Dark** beside the style name in the top bar to switch appearances, or press **⌘Space** and search for **OctoSense Dark** directly. Light uses pearl and pale aqua surfaces with dark text; Dark keeps the ink-blue palette. Both include matching versions of **Abyssal Currents**, the original oceanic wallpaper, and rounded hosted surfaces. The wallpaper switches with the appearance, fills the window with a centered crop and works offline. Android retains its animated background. +| Kind | Comes from | Runs as | Launcher id | +| --- | --- | --- | --- | +| **System apps**: News, Photos, Maps, Camera, Mail | OctoSense-System-Apps `apps//bundle`, selected by `system-apps.json`, packed into the build | Contained Splash programs in App Hub's Card runner, each in its own isolate under the capabilities its manifest asks for | `` (manifest id `os.`) | +| **Store apps** | The signed App Hub catalog, installed from the store (`apphub`) | The same Card runner. Every open is checked against the catalog; an update closes old instances. | `hub:` | +| **Native modules** | Rust crates linked into this binary | In-process `AppModule`s. Trusted code only: App Hub, AppCard, Reference and the `app-*` features. | module id | +| **Developer programs** | `config/apps.json` | Separate processes in tiles, over Makepad's `--stdin-loop` hosting protocol, built on first launch | catalog `id` | -Use **⌘Space** for the menu, **⌘W** to close a tile, **⌘F** for tile fullscreen, **⌘1…0** to switch workspaces, and **⌘Shift1…0** to move the focused tile. The menu's **Learn → Keybindings** lists the inherited bindings; shortcuts for apps absent from your catalog report that the app is unavailable. +Precedence: a linked native module beats a system app of the same id, and a system app beats a catalog row of the same id. That is why Makepad's example Mail and Photos are dropped from the shipped catalogs (`drop` in `config/apps.overlay.json`). -## Android +### Containment and permissions -With the Makepad Android toolchain installed and a device connected through ADB: +A contained app is a bundle: `manifest.json` (id, version, capabilities) plus `main.splash`. The Card runner grants only the capabilities the manifest lists (Mail asks for `storage` and `mail`). The Makepad patch ([makepad#30](https://github.com/OctoSense-org/makepad/pull/30)) enforces this at every exit of an isolate: network and web sockets answer to the app's host list, raw sockets and servers are refused, files stay in the app's storage jail, and password or one-time-code fields are inert inside a policed isolate. -```sh -cargo makepad android run -p octosense --release -``` +### Host services and host-owned sheets -The Android launcher label is **OctoSense** and its application ID is `dev.makepad.octosense`. It installs separately from an existing MakeOS Android app because the application ID changed. - -`run` builds, installs, and launches the app; `build` only creates the APK. -Native Android/iOS builds automatically link **Reference and Sheets** as -embedded apps, and App Hub with the system apps (News, Photos, Maps, Camera, -Mail) through the default `app-hub` feature. They need no extra feature flags -after runtime setup. On an installed device, the launcher derives its default -catalog from those linked modules and system apps. Missing Clock/Weather tiles -give their space to the available app icons. - -The phone build also links **AppCard** (`apps/appcard`, feature `app-appcard` -on desktop): the whole AppCard assistant, hosted in-process in a wide home -tile. The module takes `octos-app` — the crate the standalone AppCard APK is -built from — from the pinned OctoSense-System-Apps checkout -(`apps/appcard/app/app`, without its default `standalone` feature; every octos -crate comes from git octos-org/octos at the one rev octos-app pins, and -`OCTOSENSE_WORKSPACE` in `.cargo/config.toml` points its asset embedding at the -sibling framework checkouts) and mounts its `AppShell` widget: the routing -brain, the card store and transport, the L0 lowering pipeline, sessions, the -composer and the kernel agent all run inside the tile's isolate. The kernel -(`liboctos.so serve --stdio`) is spawned from this APK's native library dir -when it is bundled (`MAKEPAD_ANDROID_EXTRA_LIBS="liboctos.so="` at -build time); without it the app falls back to its WebSocket transport / login -screen. `ask` is the module's one AI-bus tool (the composer). On a desktop, -`cargo run --features app-appcard -- --module appcard` opens the same app; -`OCTOS_APP_CORE_BIN` / `OCTOS_APP_CORE_DIR` point it at a local kernel. -GPS reaches the app through the buildtool activity described below; -notifications, share and the WebView overlay (the standalone APK's other -Java features) are not wired to the hosted shell yet. - -To get AppCard's Java activity features (GPS, notifications, share and -deep-link intents), this repository's `resources/android/AndroidManifest.xml.template` -and the octos kernel bundled as `liboctos.so`, build with the fork's buildtool -`cargo-makepad` and `MAKEPAD_ANDROID_EXTRA_LIBS` instead of the stock command -above — the full recipe, including the kernel cross-build, is in -[docs/android-appcard-build.md](docs/android-appcard-build.md): +Secrets are the host's. An app that needs an account calls a **host service** through `host.request`; the service runs in the shell with the credentials, and the app never gets a socket or a password. -```sh -MAKEPAD_ANDROID_EXTRA_LIBS="liboctos.so=/abs/path/to/octos/target/aarch64-linux-android/release/octos" \ - /abs/path/to/makepad-buildtool/target/debug/cargo-makepad makepad android run -p octosense --release -``` +Mail is the worked example (`octosense-mail-service`, from `../OctoSense-System-Apps/apps/mail/host-service`): -When the AppCard tile starts, the hosted app spawns that kernel and logs -`stdio: octos=… HOME=…` to logcat; an APK built without it logs `stdio: -bundled octos not found under …; using WebSocket transport` and shows the -app's login screen instead. +- `mail.add_account` raises the host's **sign-in sheet**, a separate isolate drawn over the app. Only that sheet's calls (`mail.sheet.submit`, `mail.sheet.cancel`) can carry a password. +- The service tests the account, stores the password in the platform secret store (macOS keychain), and grants the account only to the app that added it. +- Mail state lives under the host's own directory, outside every app's jail. -Switching desktop OctoSense to the Android style changes its interface; it still -uses desktop process hosting and the full desktop catalog. The other desktop -apps (including Browser, Files, Terminal, and AI Chat) need embedded mobile -implementations before they can be bundled in the phone build. Photos includes -the app, not your desktop photo library; local Qwen weights are not packaged. +New app features that need a password, PIN or token belong in a host service and a host sheet, never in the app's own UI. -To exercise the same embedded apps on desktop: +### Store apps (App Hub) -```sh -cargo run --features mobile-apps -- --module reference --module sheets -``` +App Hub is on by default. Open **App Hub** from the launcher to browse the signed catalog and install apps; installed apps appear in the launcher without a restart. The catalog origin defaults to the App Hub repository and can be pointed elsewhere with `OCTOSENSE_HUB`. To build and publish an app, start from [OctoScript-App-Design-Flow](https://github.com/OctoSense-org/OctoScript-App-Design-Flow). -Reference shares its counter and text-input view between the standalone desktop -process and the embedded mobile module. Sheets remains an external Git crate at -the same pinned Makepad revision. +### Choosing and overriding system apps -Mobile app support is still partial: Sheets needs grid-label and toolbar fixes. -These follow-ups are tracked in [BACKLOG.md](BACKLOG.md). +`system-apps.json` names the apps and where their bundles are: -The iOS startup policy is covered by tests, but a complete iOS build currently -fails in the pinned Makepad Metal backend; see [validation](docs/validation.md). +```json +{ + "schema": 1, + "source": "../OctoSense-System-Apps/apps", + "apps": ["news", "photos", "maps", "camera", "mail"], + "assets": {} +} +``` -## Add an app +- Remove an id from `apps` to leave it out; point `OCTOSENSE_SYSTEM_APPS` at another file for a different selection. Without that variable the build ships no system apps. +- To try a changed bundle, edit it in the `../OctoSense-System-Apps` checkout and rebuild; to ship it, land it in OctoSense-System-Apps and bump `native-apps.lock.json`. +- The desktop mounts no photo library, so Photos shows the thumbnails its bundle ships. To give it full-size photos, add `"assets": {"photos": {"photos": ""}}`. +- A native module of the same id overrides a system app (for example `--features app-photos`). -The default [config/apps.json](config/apps.json) includes Reference and Makepad's own apps. Plain `cargo run` uses this catalog. An additional copy is available for explicit selection: +### Developer programs and the catalog -```sh -cargo run -- --apps config/apps.makepad.json +`config/apps.json` lists Reference and Makepad's own apps (Browser, Files, Terminal, Sheets, Notes, Calendar, Director under the id `studio`, and more). The Image, PDF and AI helpers also appear in the launcher unless their ids (`image`, `pdf`, `aichat`) are listed in `wm/launcher.hides` under the state directory. + +Catalog lookup: `--apps ` if given, else `~/.octosense/apps.json` if it exists, else `config/apps.json`. A catalog is a JSON array; each entry picks one launch target: + +```json +[ + { "id": "notes", "label": "Notes", "manifest": "../notes/Cargo.toml", "package": "my-notes", "bin": "notes", "policy": "new", "args": [] }, + { "id": "installed-notes", "label": "Installed Notes", "executable": "/opt/my-apps/notes" }, + { "id": "browser", "label": "Browser", "source": "makepad", "package": "makepad-browser", "bin": "browser", "policy": "focus" } +] ``` -It includes Reference plus Makepad's Browser, Files, Terminal, Mixer, Task Manager, Sheets, Clock, Weather, Finance, Notes, Calendar, Reminders, Calculator, Fabric, Score, Video Player, Route, VJ, Fab and Director. Image/PDF viewers are registered for file-opening and previews, and AI is registered for the assistant pane (F10). These three helper apps also appear in the launcher unless their IDs (`image`, `pdf`, `aichat`) are listed in `~/.octosense/wm/launcher.hides`. +- `"source": "makepad"` resolves through Cargo to the same Makepad checkout as the host; such builds go to `~/.octosense/build/makepad`. +- Relative paths resolve from the catalog's directory. Arguments are passed literally, without a shell. +- `policy`: `"new"` opens another instance; `"focus"` (default) focuses a running one. +- Do not add `--stdin-loop` or Studio variables; the shell adds them. Restart after editing. +- A hosted program must be a Makepad app built against the same Makepad revision; the hosting protocol is not stable across revisions. Start from `apps/reference`. -Makepad's apps carry `"source": "makepad"` instead of a path: they resolve through Cargo's dependency graph to the shared runtime checkout prepared above, or to Cargo's cached checkout when Git dependencies are used without path overrides. Each app builds on demand using its package's normal default features. Those builds go to `~/.octosense/build/makepad` rather than into Cargo's cache, which Cargo alone manages. The catalog uses the workspace root manifest to preserve the apps' expected working directory. Files retains the catalog's `--demo` argument; remove it to browse your real filesystem. Fab uses its built-in demo unless you add explicit file arguments. Upstream replaced Studio with Director; the catalog keeps the `studio` ID for existing launch references and runs `makepad-director`. No apps start automatically; `--assistant` remains opt-in. +The Makepad rows are generated from upstream's app registry at the pinned revision: -Hosted apps and the host therefore always share one revision's framework and protocol code. Reference builds from this repository and is available regardless. A personal `~/.octosense/apps.json` takes precedence over the project default, while `--apps` always selects the named file. Relative manifest paths are based on the catalog's directory, so use absolute paths if moving this catalog into your home directory. +```sh +python3 scripts/upstream.py catalog # report drift +python3 scripts/upstream.py catalog --apply # rewrite config/apps.json and apps.makepad.json +``` -Applications must be compatible Makepad applications that support the `--stdin-loop` hosting protocol. Use the same Makepad revision as this project; the protocol is not a stable compatibility boundary across arbitrary revisions. Start from [apps/reference](apps/reference). +### The AppCard assistant -The default catalog is [config/apps.json](config/apps.json). To keep a personal catalog, create `~/.octosense/apps.json`; it replaces the default catalog. An explicit catalog can be selected with: +`apps/appcard` (feature `app-appcard`, always linked on phones) hosts the whole AppCard assistant in one tile: `octos-app` from `../OctoSense-System-Apps/apps/appcard/app/app`, built without its `standalone` feature. Routing, cards, sessions, the composer and the kernel agent run inside the tile's isolate; `ask` is the module's AI-bus tool. ```sh -cargo run -- --apps /path/to/apps.json +cargo run --release --features app-appcard -- --module appcard ``` -A catalog is a JSON array. Each entry chooses exactly one of a Cargo manifest, an installed executable, or a named upstream source: +On desktop, `OCTOS_APP_CORE_BIN` and `OCTOS_APP_CORE_DIR` point it at a local octos kernel; without one it shows its login / WebSocket screen. Every octos crate comes from octos-org/octos at the one revision `octos-app` pins. -```json -[ - { - "id": "notes", - "label": "Notes", - "manifest": "../notes/Cargo.toml", - "package": "my-notes", - "bin": "notes", - "policy": "new", - "args": [] - }, - { - "id": "installed-notes", - "label": "Installed Notes", - "executable": "/opt/my-apps/notes", - "policy": "focus" - }, - { - "id": "browser", - "label": "Browser", - "source": "makepad", - "package": "makepad-browser", - "bin": "browser", - "policy": "focus" - } -] +## Demos + +### Mail without an account + +```sh +MAKEPAD_APP_CONFIG='{"mail_demo":true}' cargo run --release ``` -`"source": "makepad"` names the host's Makepad dependency rather than a location: the row resolves through the checkout reported by Cargo, and is skipped on a host that has no such checkout. Relative paths resolve from the catalog's directory. Arguments are passed literally, without a shell. `policy: "new"` opens a new instance; `"focus"` focuses an existing instance and is the default. Restart OctoSense after editing the catalog. Existing personal catalogs should rename `makeos-reference` package/bin entries to `octosense-reference`. Only apps with available launch targets appear in the launcher. Missing manifests, invalid catalogs, and failed starts are reported in the desktop/logs. +Open **Mail**, sign in on the host sheet with any address and password `demo`. The demo serves sample messages from a file vault: no network, no keychain. -The Makepad rows in the shipped catalog are generated from upstream's own registry at the pinned revision, under the named adaptations in [config/apps.overlay.json](config/apps.overlay.json). Check for drift, or regenerate, with: +With a real account on an unsigned development build, macOS asks for keychain access again after every rebuild. For development, keep passwords in a file instead: ```sh -python3 scripts/upstream.py catalog -python3 scripts/upstream.py catalog --apply +OCTOSENSE_MAIL_VAULT=file cargo run --release ``` -OctoSense adds the hosting arguments and connection settings itself. Do not add `--stdin-loop` or Studio connection variables to the catalog. For executable registrations, supply the app's resources as required by that app's packaging. +### Remote-control bridge -## Settings and optional features +Every desktop Makepad app, this shell included, carries a localhost HTTP control surface. Start it with `MAKEPAD_REMOTE=1` (ephemeral port) or `MAKEPAD_REMOTE=`, or `--remote[=PORT]`: -OctoSense state lives under `~/.octosense`; `OCTOSENSE_HOME` selects another directory. Existing installations continue using `~/.makeos` if `~/.octosense` does not exist, preserving settings and local model links. The old `MAKEOS_HOME` override remains supported; `OCTOSENSE_HOME` takes precedence. Theme files and hosting overrides retain the upstream `wm/` layout inside that directory. Hosted apps inherit the OctoSense state root through Makepad's compatible `MAKEPAD_HOME` setting. +```sh +MAKEPAD_REMOTE=8399 cargo run --release +# prints: [makepad-remote] listening on 127.0.0.1:8399 pid=... app=... grabs=... +``` -Optional launch flags are `--assistant`, `--prewarm`, `--demo-home`, and `--download-wallpapers`. Assistant/prewarm flags require matching apps in your catalog. Theme importing remains an explicit action in the theme menu. +| Route | Does | +| --- | --- | +| `/` | Cheat sheet of every route. | +| `/s` | Windows and their geometry. | +| `/snap?q=` | Visible widgets with rects and text, filtered by id/type/text. | +| `/click?x=&y=` | Click at window-local layout points. `/m`, `/k`, `/t` for mouse, keys, text. | +| `/g` | Grab a window to PNG. | +| `/log?n=` | Tail the log. | +| `/gq` | Grab every window, then quit. Use this (or `/quit`) to end any session you started. | -Upstream's linked-module infrastructure is retained behind `app-sheets`, `app-photos`, and `app-aichat` Cargo features; all are off by default. A module must be linked and selected with `--module ` or `wm/apps.splash`. App Hub's modules are the exception: the store and the apps its Card runner hosts (system and installed apps) have no process form and always open in-process. This initial milestone validates process hosting. It does not provide runtime loading of native shared libraries or embedding of unrelated native desktop windows. +Add `&wait=1` to an input route to answer after the next frame. The bridge injects real input and serves screenshots: bind a non-loopback host (`MAKEPAD_REMOTE=0.0.0.0:8399`) only on a trusted network. -## Local AI setup +### Headless UI checks -Store model weights outside this repository, normally at `~/.octosense/weights/Qwen3.5-9B-UD-Q4_K_XL.gguf`. Each user downloads the model once or references an existing copy; model files and machine-specific symlinks stay out of Git. `OCTOSENSE_HOME` relocates the state directory, and `MAKEPAD_AI_CHAT_MODEL` can select a model file anywhere on disk. +On macOS, `MAKEPAD_HIDE_WINDOWS=1` keeps windows off screen while still rendering, so a remote-driven run does not take over the display: -The [local AI setup guide](docs/local-ai.md) covers the assistant app prerequisite, the pinned model download and checksum, reusing existing weights, and checking the **F10** assistant. The desktop and Reference app work without a model. +```sh +MAKEPAD_HIDE_WINDOWS=1 MAKEPAD_REMOTE=1 cargo run --release +``` -## Upstream updates +Makepad's [`makepad_test`](https://github.com/OctoSense-org/makepad/tree/main/libs/makepad_test) crate (in the `../makepad` sibling) builds on the same two pieces: `#[makepad_test]` tests launch the app hidden, drive it over `--remote` with selectors and waits, and close it with `/gq`. This repository does not have a `makepad_test` suite yet. -[upstream/makepad.json](upstream/makepad.json) records every imported WM file, its original path/hash, and the matching framework revision. The source and dependency baseline is [official Makepad at 74b63be8](https://github.com/makepad/makepad/commit/74b63be83e101ab3a28d3604df77e9662d50a833). All framework crates, including `libs/wm_api` and `libs/wm_theme`, stay external at that exact Git revision. OctoSense owns its additional style, theme assets and wallpaper behavior locally. +## Phones -Run this daily, or after any upstream pull. With Python 3.11+, update the Makepad -checkout using your normal Git workflow, then run one command from OctoSense: +With the Makepad Android toolchain installed and a device on ADB: ```sh -git -C ../makepad pull --ff-only origin work -python3 scripts/upstream.py sync +cargo makepad android run -p octosense --release ``` -`sync` defaults to the recorded `../makepad` checkout's current `HEAD`. -WM feature development belongs in this repository; framework updates come from -official Makepad. When the checkout's HEAD matches the -recorded revision, it exits without building. Otherwise it requires a clean -OctoSense tree, saves comparison diffs, stages the merge, updates all dependency -pins and the lockfile, runs compile/Rust/Python checks, builds both profiles, -and runs both native smoke modes. It reuses an ignored staging build cache. +Phone builds always link Reference, Sheets and AppCard, and App Hub with the system apps through the default feature. The launcher label is **OctoSense**, application id `dev.makepad.octosense`. AppCard's Java features (GPS, notifications, share, intents) and the bundled `liboctos.so` kernel need the fork's buildtool and `MAKEPAD_ANDROID_EXTRA_LIBS`; see [docs/android-appcard-build.md](docs/android-appcard-build.md). The dedicated phone shell is OctoSense-ROM's `home/`. + +## Desktop styles and settings + +- Eight desktop styles. Desktop builds start in **OctoSense**, with Liquid Glass frames and a **Light / Dark** switch in the top bar; the others are Omarchy, macOS, Windows, Windows 2000, NeXTSTEP, iOS and Android. Theme sources are in `resources/themes/`, wallpaper provenance in [resources/wallpapers/README.md](resources/wallpapers/README.md). +- Keys: **⌘Space** menu, **⌘W** close tile, **⌘F** tile fullscreen, **⌘1…0** workspaces, **⌘Shift1…0** move tile. **Learn → Keybindings** lists them; see [KEYBINDINGS.md](KEYBINDINGS.md). +- State lives in `~/.octosense` (`OCTOSENSE_HOME`); hosted apps get it as `MAKEPAD_HOME`. +- Local models for the AI pane (**F10**): [docs/local-ai.md](docs/local-ai.md). The desktop works without a model. + +## Pinning and updating siblings + +| To move | Edit | Then | +| --- | --- | --- | +| Makepad / OctoScript | `native-runtime.lock.json` (a new OctoScript-Makepad release), and the `rev` of the Makepad Git dependencies in `Cargo.toml` and `apps/*/Cargo.toml` | Rebase the patch if needed, update `runtime-patches.lock.json` (base, sha256, tree) | +| System apps, Mail service, AppCard | `revision` in `native-apps.lock.json` | — | +| App Hub | `rev` of `octosense-app-hub-app` and the three `[patch]` entries in `Cargo.toml` | — | -After every check passes, it creates a unique `sync/makepad-` branch -and applies the verified changes, leaving them unstaged and uncommitted. You -then review the diff and report, commit, and merge. Conflicts or failed checks -stop with diagnostics and preserve the live import. The command never pulls, -commits, merges branches, or pushes. Full sync currently requires macOS GUI -access; a headless session cannot pass its native runtime checks. +After any change: `python3 tools/setup-native.py --update`, `cargo update` as needed, then `python3 tools/setup-native.py --check --cargo-manifest Cargo.toml` and the tests below. -Reports and captured frames are under `target/makepad-sync/reports/`; the command -prints the exact directory. Use `--source /path/to/makepad` or `--to ` -when needed. See the [full workflow and conflict recovery](docs/upstream.md) for -manual commands and how to investigate a failed candidate. +`scripts/upstream.py sync|status|diff|update` tracks the WM files imported from official Makepad (`upstream/makepad.json`, baseline `74b63be8`); see [docs/upstream.md](docs/upstream.md). It needs `--source` pointing at a full clone of official Makepad: the `../makepad` sibling is a shallow checkout of the fork and does not contain the baseline commit. -## Verification and scope +## Testing ```sh cargo test --locked --workspace cargo test --locked --workspace --features mobile-apps python3 -m unittest discover -s scripts -p 'test_*.py' +python3 scripts/upstream.py catalog +python3 tools/setup-native.py --check --cargo-manifest Cargo.toml ``` -The native smoke test opens and closes its own test windows, isolates settings in a temporary directory, and records app-provided frames/logs. After dependencies are downloaded: +Native smoke tests open their own windows, isolate state in a temporary directory and drive the shell over the remote bridge. They need GUI access and a prior release build: ```sh cargo build --release --locked --workspace @@ -309,8 +321,25 @@ python3 scripts/smoke.py --styles python3 scripts/smoke.py --cargo-run --default-catalog ``` -The first smoke command checks hosted input, workspace movement, fullscreen resizing, independent instances, all eight desktop styles (including both OctoSense appearances, glass and menus), failed launches, and quitting during an unfinished build. The second uses exactly `cargo run` with the shipped catalog. Python supplies app-local remote control and isolated state through the environment; neither is required for normal use. Smoke runs set Cargo offline and require GUI access. +Results for each change are recorded in [docs/validation.md](docs/validation.md). + +## CI + +`.github/workflows/runtime.yml` runs on every push and pull request, on macOS 14: `setup-native.py`, `cargo check --locked --workspace --features mobile-apps`, and `setup-native.py --check --cargo-manifest Cargo.toml`. It does **not** run `cargo test`, the Python tests or the smoke tests; run those locally before opening a PR. + +## Known gaps + +- Only macOS is validated. Windows and Linux are untested; the iOS build fails in the pinned Metal backend. +- No packaged `.app` or installer; source builds read fonts and resources from the `../makepad` checkout, so keep it in place. +- Photos on desktop has thumbnails only unless you mount a photo directory. +- The hosted AppCard assistant does not yet wire notifications, share or the WebView overlay. +- Mobile Sheets needs grid-label and toolbar fixes ([BACKLOG.md](BACKLOG.md)). +- No `makepad_test` UI suite; CI compiles but does not test. + +## Contributing + +`main` is protected: every change goes through a pull request (admins included), and force pushes are blocked. Branch from `main`, run `setup-native.py --check`, the Rust and Python tests above and, for UI changes, a smoke or remote-driven run; record native checks in `docs/validation.md`. Keep the one-Makepad, one-octos, one-App-Hub rule: `setup-native.py --check --cargo-manifest Cargo.toml` must pass. -See the [validation record](docs/validation.md). Source builds and process hosting are the initial target on macOS. Linux/Windows branches are retained but have not been validated here. A relocatable `.app`, installer, web/mobile delivery, and a Linux session compositor are separate work. +## License -OctoSense is licensed under the [Apache License 2.0](LICENSE); see [NOTICE](NOTICE). The copied Makepad source is covered by its [original MIT notice](LICENSES/Makepad-MIT.txt). Dependencies retain their respective licenses. +Apache License 2.0 ([LICENSE](LICENSE), [NOTICE](NOTICE)). Source copied from Makepad keeps its [MIT notice](LICENSES/Makepad-MIT.txt). Dependencies keep their own licenses. diff --git a/README.zh-CN.md b/README.zh-CN.md new file mode 100644 index 00000000..61155418 --- /dev/null +++ b/README.zh-CN.md @@ -0,0 +1,345 @@ +# OctoSense Desktop + +[English](README.md) | 简体中文 + +OctoSense-Desktop 是 [OctoSense](https://github.com/OctoSense-org)(运行在操作系统之上的 Agent 交互 Shell)的桌面端 Shell。它是一个 Makepad 窗口,这个窗口本身就是桌面:launcher、dock 和平铺窗格(tile)。系统应用和 App Hub 商店应用以隔离的脚本程序运行,受信任的原生模块在进程内运行,Makepad 开发者程序作为子进程运行。它获取应用的方式与手机 Shell(OctoSense-ROM 的 `home/`)完全相同。 + +## 在仓库体系中的位置 + +| 仓库 | 与本仓库的关系 | +| --- | --- | +| [OctoSense-ROM](https://github.com/OctoSense-org/OctoSense-ROM/blob/main/README.zh-CN.md) | 手机 Shell(`home/`)。相同的应用模型、相同的运行时补丁、相同的系统应用。 | +| [OctoSense-System-Apps](https://github.com/OctoSense-org/OctoSense-System-Apps) | 新闻、相册、地图、相机、邮件的应用包,邮件宿主服务,以及 AppCard 助手(`octos-app`)。以固定版本的同级目录检出。 | +| [OctoSense-App-Hub](https://github.com/OctoSense-org/OctoSense-App-Hub) | 签名目录、商店和 Card 运行器。以 Git crate `octosense-app-hub-app` 链接。 | +| [OctoScript-App-Design-Flow](https://github.com/OctoSense-org/OctoScript-App-Design-Flow) | 设计、构建应用并发布到 App Hub 的地方。 | +| [OctoScript-Makepad](https://github.com/OctoSense-org/OctoScript-Makepad) | 固定 Makepad 与 OctoScript 版本的运行时发行版。以固定版本的同级目录检出。 | +| [makepad(OctoSense 分支)](https://github.com/OctoSense-org/makepad) | 框架。以固定版本的同级目录检出,并打上经过评审的补丁。 | +| [octos](https://github.com/octos-org/octos) | AppCard 助手背后的 Agent 内核。Git 依赖,只有一个版本(`18fcd3f1`)。 | + +## 仓库结构 + +| 路径 | 内容 | +| --- | --- | +| `src/` | Shell 本体(crate `octosense`):平铺、launcher、dock、状态栏、应用托管(`host.rs`、`hub.rs`、`module_host.rs`)、应用注册表(`apps.rs`)。 | +| `src/shell/` | 状态栏、launcher、菜单、通知、AI 面板、gallery。 | +| `src/octosense/` | OctoSense 专有部分:应用目录加载、状态路径、样式、Makepad 源码定位。 | +| `apps/reference/` | `octosense-reference`,一个计数器加文本输入的小应用,既能作为托管进程运行,也能作为链接模块运行。 | +| `apps/appcard/` | `octosense-appcard`,在一个 tile 中挂载 AppCard 助手(`octos-app`)的模块。 | +| `config/apps.json` | 默认的开发者程序目录。`apps.makepad.json` 是供 `--apps` 使用的相同副本;`apps.overlay.json` 保存重新生成时应用的调整。 | +| `system-apps.json` | 本次构建打包哪些系统应用,以及从哪里取。 | +| `native-runtime.lock.json` | OctoScript-Makepad 发行版(并由它确定 Makepad 和 OctoScript)。 | +| `native-apps.lock.json` | OctoSense-System-Apps 的版本。 | +| `runtime-patches.lock.json`、`patches/runtime/` | 经过评审的 Makepad 补丁([OctoSense-org/makepad#30](https://github.com/OctoSense-org/makepad/pull/30))及其期望的源码树。 | +| `tools/setup-native.py` | 准备并检查固定版本的同级目录。 | +| `scripts/` | `upstream.py`(WM 来源记录与目录重新生成)、`smoke.py`(原生冒烟测试)、它们的 Python 测试,以及 `provision-appcard-llm.sh`(Android)。 | +| `upstream/makepad.json` | 每个从 Makepad `apps/wm` 引入的文件的来源记录。 | +| `resources/` | 主题、壁纸、图标、Android manifest 模板、启动脚本。 | +| `docs/` | [验证记录](docs/validation.md)、[上游同步](docs/upstream.md)、[本地 AI](docs/local-ai.md)、[Android AppCard 构建](docs/android-appcard-build.md)、按日期的计划文档。 | +| `KEYBINDINGS.md`、`BACKLOG.md` | 快捷键说明;待办事项。 | + +## 前置条件 + +- 稳定版 Rust(`cargo` 位于 `~/.cargo/bin`)以及所在系统的原生工具链。macOS 需要 Xcode Command Line Tools(`xcode-select --install`)。 +- Git,以及供 `tools/setup-native.py` 使用的 Python 3.9+。`scripts/upstream.py` 需要 Python 3.11+(它导入 `tomllib`)。 +- 首次准备和构建时需要联网。 + +## 准备同级工作区 + +构建时,Makepad、OctoScript、OctoScript-Makepad 和 OctoSense-System-Apps 都作为本仓库的**同级目录**解析(`../makepad` 等,通过 `Cargo.toml` 中的 `[patch]` 条目)。把本仓库放进一个单独的工作区目录,再由准备脚本补齐其余部分: + +```sh +mkdir octosense-ws && cd octosense-ws +git clone https://github.com/OctoSense-org/OctoSense-Desktop.git +cd OctoSense-Desktop +python3 tools/setup-native.py +``` + +结果: + +```text +octosense-ws/ + OctoSense-Desktop/ this repository + octoscript-makepad/ the release native-runtime.lock.json selects + makepad/, octoscript/ the revisions that release's runtime.json pins; makepad carries the patch + OctoSense-System-Apps/ the revision native-apps.lock.json pins +``` + +| 命令 | 作用 | +| --- | --- | +| `python3 tools/setup-native.py` | 按固定版本克隆缺失的同级仓库,并应用 Makepad 补丁。 | +| `python3 tools/setup-native.py --check` | 只校验同级目录,不做任何修改。 | +| `python3 tools/setup-native.py --check --cargo-manifest Cargo.toml` | 同时检查锁定的 Cargo 依赖图:只有一个 Makepad、一个 octos、一个 App Hub。 | +| `python3 tools/setup-native.py --update` | 把干净的检出移动到锁定版本(在锁文件变更之后)。 | +| `--root DIR`、`--cache DIR` | 使用其他工作区目录;复用本地 Git 对象缓存。 | + +同级目录中的本地修改会被保留;`--update` 只移动干净的检出。补丁所基于的提交(`runtime-patches.lock.json` 中的 `source_commit`)的干净检出,会被视为同一棵源码树。 + +## 构建与运行 + +准备完成后,在本目录执行: + +```sh +cargo run --release +``` + +桌面启动时是空的。从 dock、左上角的 **Apps** 菜单或 **⌘Space**(菜单和搜索)启动 App Hub、系统应用或开发者程序。**System → Quit OctoSense** 关闭桌面及其托管的所有内容。 + +`config/apps.json` 中的开发者程序在首次启动时构建(进度显示在 tile 中)。如需预先构建整个工作区: + +```sh +cargo build --release --workspace +``` + +| 平台 | 状态 | +| --- | --- | +| macOS | 已支持并验证(源码构建、进程托管、App Hub、系统应用)。 | +| Windows、Linux | 保留了上游的代码路径,但在本仓库未经验证。 | +| Android | `cargo makepad android run -p octosense --release`;见[手机](#手机)。 | +| iOS | 启动策略有测试覆盖,但完整构建目前在固定版本的 Makepad Metal 后端中失败([验证记录](docs/validation.md))。 | + +目前不提供可重定位的 `.app`、安装包,也没有 Linux 会话合成器。 + +### Cargo features + +| Feature | 默认 | 作用 | +| --- | --- | --- | +| `app-hub` | 开 | 链接 `octosense-app-hub-app`(商店 `apphub`、Card 运行器 `card`、系统应用)以及邮件宿主服务 `octosense-mail-service`。关闭后构建中没有 App Hub,也没有系统应用。 | +| `app-reference` | 关 | 把 Reference 作为模块链接。 | +| `app-sheets` | 关 | 把 Makepad 的 Sheets 作为模块链接。 | +| `app-photos` | 关 | 链接 Makepad 的原生 Photos 模块;它会替换同 id 的相册系统应用(用于对比)。 | +| `app-appcard` | 关 | 链接 AppCard 助手模块(`apps/appcard`)。 | +| `app-aichat` | 关 | 把 Makepad 的 AI chat 作为模块链接,不含模型引擎。 | +| `app-rinx` | 关 | 把 Matrix 客户端 [Rinx](https://github.com/upstreamlabs/Rinx) 作为模块链接。 | +| `mobile-apps` | 关 | `app-reference` + `app-sheets` + `app-appcard` + `app-hub`:手机构建链接的同一组模块,用于在桌面上测试。 | + +已链接的模块用 `--module ` 打开(或在状态目录下的 `wm/apps.splash` 中写一行 `: Module`): + +```sh +cargo run --release --features app-appcard -- --module appcard +cargo run --release --features mobile-apps -- --module reference --module sheets +cargo run --release --features app-rinx -- --module rinx +``` + +App Hub 的模块是例外:它们没有进程形态,总是在进程内打开。 + +### 命令行参数与环境变量 + +| 名称 | 作用 | +| --- | --- | +| `--apps ` | 使用指定的开发者程序目录。 | +| `--module ` | 在进程内托管一个已链接的模块。 | +| `--assistant`、`--prewarm` | 启动助手应用 / 预热应用(需要目录中有对应条目)。默认关闭。 | +| `--demo-home`、`--download-wallpapers` | 生成演示文件系统;下载 Omarchy 主题的完整壁纸集。 | +| `OCTOSENSE_HOME` | 状态目录(默认 `~/.octosense`;若不存在则沿用已有的 `~/.makeos`,以及 `MAKEOS_HOME`)。 | +| `OCTOSENSE_APP_DATA` | App Hub 保存已安装应用的位置(默认是平台数据目录下的 `apps/`)。 | +| `OCTOSENSE_HUB`、`OCTOSENSE_HUB_ANCHOR` | App Hub 目录来源(路径或 URL)和信任锚;默认是 App Hub 仓库的 `main`。 | +| `OCTOSENSE_SYSTEM_APPS` | 系统应用选择文件;`.cargo/config.toml` 将其设为 `system-apps.json`。 | +| `MAKEPAD_APP_CONFIG='{"mail_demo":true}'` | 提供邮件的演示邮箱(见[演示](#演示))。 | +| `OCTOSENSE_MAIL_VAULT=file` | 把邮件密码保存在权限为 0600 的文件中,而不是 macOS 钥匙串。 | +| `OCTOS_APP_CORE_BIN`、`OCTOS_APP_CORE_DIR` | 让 AppCard 助手使用本地的 octos 内核。 | +| `MAKEPAD_REMOTE`、`MAKEPAD_HIDE_WINDOWS` | 远程控制桥;隐藏窗口(见[演示](#演示))。 | + +## 应用模型 + +launcher 把四类应用列在一起: + +| 类别 | 来源 | 运行方式 | Launcher id | +| --- | --- | --- | --- | +| **系统应用**:新闻、相册、地图、相机、邮件 | OctoSense-System-Apps 的 `apps//bundle`,由 `system-apps.json` 选择,打包进构建 | App Hub 的 Card 运行器中隔离运行的 Splash 程序,每个应用一个 isolate,只拥有其清单申请的能力 | ``(清单 id `os.`) | +| **商店应用** | 签名的 App Hub 目录,从商店(`apphub`)安装 | 同一个 Card 运行器。每次打开都会对照目录检查;更新会关闭旧实例。 | `hub:` | +| **原生模块** | 链接进本二进制的 Rust crate | 进程内的 `AppModule`。只允许受信任的代码:App Hub、AppCard、Reference 以及各 `app-*` feature。 | 模块 id | +| **开发者程序** | `config/apps.json` | tile 中的独立进程,通过 Makepad 的 `--stdin-loop` 托管协议运行,首次启动时构建 | 目录 `id` | + +优先级:已链接的原生模块优先于同 id 的系统应用,系统应用优先于同 id 的目录条目。因此 Makepad 示例中的 Mail 和 Photos 已从随附目录中移除(`config/apps.overlay.json` 中的 `drop`)。 + +### 隔离与权限 + +隔离运行的应用是一个包:`manifest.json`(id、版本、能力)加上 `main.splash`。Card 运行器只授予清单中列出的能力(邮件申请 `storage` 和 `mail`)。Makepad 补丁([makepad#30](https://github.com/OctoSense-org/makepad/pull/30))在 isolate 的每个出口执行这一约束:网络请求和 web socket 受应用的主机列表约束,原始 socket 和服务端被拒绝,文件访问限制在应用的存储沙箱内,密码和一次性验证码输入框在受约束的 isolate 中不起作用。 + +### 宿主服务与宿主自有面板 + +密钥属于宿主。需要账户的应用通过 `host.request` 调用**宿主服务**;服务在 Shell 中持有凭据运行,应用永远拿不到 socket,也拿不到密码。 + +邮件是完整的示例(`octosense-mail-service`,来自 `../OctoSense-System-Apps/apps/mail/host-service`): + +- `mail.add_account` 弹出宿主的**登录面板**,这是覆盖在应用之上的独立 isolate。只有这个面板的调用(`mail.sheet.submit`、`mail.sheet.cancel`)可以携带密码。 +- 服务先测试账户,再把密码存入平台的密钥存储(macOS 钥匙串),并且只把账户授予添加它的应用。 +- 邮件状态保存在宿主自己的目录中,位于所有应用的沙箱之外。 + +需要密码、PIN 或令牌的新功能,应放在宿主服务和宿主自有面板中,绝不放在应用自己的界面里。 + +### 商店应用(App Hub) + +App Hub 默认开启。从 launcher 打开 **App Hub**,浏览签名目录并安装应用;安装后的应用无需重启就会出现在 launcher 中。目录来源默认是 App Hub 仓库,可以用 `OCTOSENSE_HUB` 指向其他位置。要构建和发布应用,从 [OctoScript-App-Design-Flow](https://github.com/OctoSense-org/OctoScript-App-Design-Flow) 开始。 + +### 选择与覆盖系统应用 + +`system-apps.json` 列出应用及其应用包所在位置: + +```json +{ + "schema": 1, + "source": "../OctoSense-System-Apps/apps", + "apps": ["news", "photos", "maps", "camera", "mail"], + "assets": {} +} +``` + +- 从 `apps` 中删除某个 id 即可不打包它;把 `OCTOSENSE_SYSTEM_APPS` 指向另一个文件即可换一套选择。没有这个变量时,构建不包含任何系统应用。 +- 想试用修改过的应用包,就在 `../OctoSense-System-Apps` 检出中修改并重新构建;要正式发布,把修改合入 OctoSense-System-Apps,再更新 `native-apps.lock.json`。 +- 桌面端没有挂载照片库,因此相册显示的是应用包自带的缩略图。要提供原尺寸照片,添加 `"assets": {"photos": {"photos": ""}}`。 +- 同 id 的原生模块会覆盖系统应用(例如 `--features app-photos`)。 + +### 开发者程序与目录 + +`config/apps.json` 列出 Reference 和 Makepad 自带的应用(Browser、Files、Terminal、Sheets、Notes、Calendar、id 为 `studio` 的 Director 等)。Image、PDF 和 AI 辅助应用也会出现在 launcher 中,除非它们的 id(`image`、`pdf`、`aichat`)写在状态目录下的 `wm/launcher.hides` 中。 + +目录查找顺序:给了 `--apps ` 就用它;否则若存在 `~/.octosense/apps.json` 就用它;否则用 `config/apps.json`。目录是一个 JSON 数组,每个条目选择一种启动目标: + +```json +[ + { "id": "notes", "label": "Notes", "manifest": "../notes/Cargo.toml", "package": "my-notes", "bin": "notes", "policy": "new", "args": [] }, + { "id": "installed-notes", "label": "Installed Notes", "executable": "/opt/my-apps/notes" }, + { "id": "browser", "label": "Browser", "source": "makepad", "package": "makepad-browser", "bin": "browser", "policy": "focus" } +] +``` + +- `"source": "makepad"` 通过 Cargo 解析到与宿主相同的 Makepad 检出;这类构建输出到 `~/.octosense/build/makepad`。 +- 相对路径相对于目录文件所在目录解析。参数按原样传递,不经过 shell。 +- `policy`:`"new"` 打开新实例;`"focus"`(默认)聚焦已运行的实例。 +- 不要添加 `--stdin-loop` 或 Studio 相关变量,Shell 会自己添加。修改后需重启。 +- 被托管的程序必须是基于同一 Makepad 版本构建的 Makepad 应用;托管协议在不同版本之间并不稳定。可以从 `apps/reference` 开始。 + +目录中的 Makepad 条目由固定版本上游的应用注册表生成: + +```sh +python3 scripts/upstream.py catalog # report drift +python3 scripts/upstream.py catalog --apply # rewrite config/apps.json and apps.makepad.json +``` + +### AppCard 助手 + +`apps/appcard`(feature `app-appcard`,手机构建总是链接)在一个 tile 中托管完整的 AppCard 助手:来自 `../OctoSense-System-Apps/apps/appcard/app/app` 的 `octos-app`,构建时关闭其 `standalone` feature。路由、卡片、会话、输入框和内核 Agent 都在该 tile 的 isolate 中运行;`ask` 是该模块的 AI 总线工具。 + +```sh +cargo run --release --features app-appcard -- --module appcard +``` + +在桌面上,`OCTOS_APP_CORE_BIN` 和 `OCTOS_APP_CORE_DIR` 让它使用本地 octos 内核;没有内核时显示登录 / WebSocket 界面。所有 octos crate 都来自 octos-org/octos,且只有 `octos-app` 固定的那一个版本。 + +## 演示 + +### 无账户试用邮件 + +```sh +MAKEPAD_APP_CONFIG='{"mail_demo":true}' cargo run --release +``` + +打开 **Mail**,在宿主面板上用任意地址和密码 `demo` 登录。演示模式从文件保险库提供示例邮件:不联网,不用钥匙串。 + +在未签名的开发构建上使用真实账户时,每次重新构建后 macOS 都会再次请求钥匙串访问权限。开发时可以改用文件保存密码: + +```sh +OCTOSENSE_MAIL_VAULT=file cargo run --release +``` + +### 远程控制桥 + +每个桌面端 Makepad 应用(包括本 Shell)都内置一个本机 HTTP 控制接口。用 `MAKEPAD_REMOTE=1`(临时端口)、`MAKEPAD_REMOTE=` 或 `--remote[=PORT]` 启用: + +```sh +MAKEPAD_REMOTE=8399 cargo run --release +# prints: [makepad-remote] listening on 127.0.0.1:8399 pid=... app=... grabs=... +``` + +| 路由 | 作用 | +| --- | --- | +| `/` | 所有路由的速查表。 | +| `/s` | 窗口及其几何信息。 | +| `/snap?q=` | 可见控件及其矩形和文本,可按 id/类型/文本过滤。 | +| `/click?x=&y=` | 在窗口内布局坐标处点击。`/m`、`/k`、`/t` 分别对应鼠标、按键、文本。 | +| `/g` | 把窗口截图为 PNG。 | +| `/log?n=` | 查看日志末尾。 | +| `/gq` | 截取所有窗口后退出。凡是自己启动的会话,都用它(或 `/quit`)结束。 | + +在输入类路由后加 `&wait=1`,会等下一帧绘制完成后再返回。该接口会注入真实输入并提供截图:只有在可信网络中才绑定非回环地址(`MAKEPAD_REMOTE=0.0.0.0:8399`)。 + +### 无界面 UI 检查 + +在 macOS 上,`MAKEPAD_HIDE_WINDOWS=1` 让窗口不显示在屏幕上但仍然渲染,这样远程驱动的运行不会占用屏幕: + +```sh +MAKEPAD_HIDE_WINDOWS=1 MAKEPAD_REMOTE=1 cargo run --release +``` + +Makepad 的 [`makepad_test`](https://github.com/OctoSense-org/makepad/tree/main/libs/makepad_test) crate(位于 `../makepad` 同级目录中)正是基于这两者:`#[makepad_test]` 测试以隐藏窗口启动应用,通过 `--remote` 用选择器和等待条件驱动它,最后用 `/gq` 关闭。本仓库目前还没有 `makepad_test` 测试套件。 + +## 手机 + +安装 Makepad Android 工具链并通过 ADB 连接设备后: + +```sh +cargo makepad android run -p octosense --release +``` + +手机构建总是链接 Reference、Sheets 和 AppCard,并通过默认 feature 链接 App Hub 及系统应用。launcher 显示名为 **OctoSense**,应用 id 为 `dev.makepad.octosense`。AppCard 的 Java 功能(GPS、通知、分享、intent)以及打包的 `liboctos.so` 内核需要使用分支的 buildtool 和 `MAKEPAD_ANDROID_EXTRA_LIBS`;见 [docs/android-appcard-build.md](docs/android-appcard-build.md)。专门的手机 Shell 是 OctoSense-ROM 的 `home/`。 + +## 桌面样式与设置 + +- 共八种桌面样式。桌面构建默认使用 **OctoSense**,带 Liquid Glass 窗框,顶栏有 **Light / Dark** 切换;其余为 Omarchy、macOS、Windows、Windows 2000、NeXTSTEP、iOS 和 Android。主题源文件在 `resources/themes/`,壁纸来源见 [resources/wallpapers/README.md](resources/wallpapers/README.md)。 +- 快捷键:**⌘Space** 菜单,**⌘W** 关闭 tile,**⌘F** tile 全屏,**⌘1…0** 切换工作区,**⌘Shift1…0** 移动 tile。**Learn → Keybindings** 列出全部快捷键;另见 [KEYBINDINGS.md](KEYBINDINGS.md)。 +- 状态保存在 `~/.octosense`(`OCTOSENSE_HOME`);托管应用通过 `MAKEPAD_HOME` 获得该路径。 +- AI 面板(**F10**)的本地模型:[docs/local-ai.md](docs/local-ai.md)。没有模型时桌面照常工作。 + +## 固定与更新同级仓库 + +| 要更新的内容 | 修改 | 然后 | +| --- | --- | --- | +| Makepad / OctoScript | `native-runtime.lock.json`(新的 OctoScript-Makepad 发行版),以及 `Cargo.toml` 和 `apps/*/Cargo.toml` 中 Makepad Git 依赖的 `rev` | 必要时变基补丁,更新 `runtime-patches.lock.json`(base、sha256、tree) | +| 系统应用、邮件服务、AppCard | `native-apps.lock.json` 中的 `revision` | — | +| App Hub | `Cargo.toml` 中 `octosense-app-hub-app` 的 `rev` 以及三个 `[patch]` 条目 | — | + +任何更改之后:执行 `python3 tools/setup-native.py --update`,按需执行 `cargo update`,再运行 `python3 tools/setup-native.py --check --cargo-manifest Cargo.toml` 和下面的测试。 + +`scripts/upstream.py sync|status|diff|update` 跟踪从官方 Makepad 引入的 WM 文件(`upstream/makepad.json`,基线 `74b63be8`);见 [docs/upstream.md](docs/upstream.md)。它需要用 `--source` 指向官方 Makepad 的完整克隆:`../makepad` 同级目录是分支的浅克隆,不包含基线提交。 + +## 测试 + +```sh +cargo test --locked --workspace +cargo test --locked --workspace --features mobile-apps +python3 -m unittest discover -s scripts -p 'test_*.py' +python3 scripts/upstream.py catalog +python3 tools/setup-native.py --check --cargo-manifest Cargo.toml +``` + +原生冒烟测试会打开自己的窗口,把状态隔离在临时目录中,并通过远程控制桥驱动 Shell。需要 GUI 访问权限,并且先完成 release 构建: + +```sh +cargo build --release --locked --workspace +python3 scripts/smoke.py --styles +python3 scripts/smoke.py --cargo-run --default-catalog +``` + +每次变更的结果记录在 [docs/validation.md](docs/validation.md)。 + +## CI + +`.github/workflows/runtime.yml` 在每次 push 和 pull request 时于 macOS 14 上运行:`setup-native.py`、`cargo check --locked --workspace --features mobile-apps`,以及 `setup-native.py --check --cargo-manifest Cargo.toml`。它**不**运行 `cargo test`、Python 测试或冒烟测试;提交 PR 前请在本地运行。 + +## 已知不足 + +- 只有 macOS 经过验证。Windows 和 Linux 未测试;iOS 构建在固定版本的 Metal 后端中失败。 +- 没有打包好的 `.app` 或安装包;源码构建从 `../makepad` 检出读取字体和资源,请保留该目录。 +- 桌面端相册只有缩略图,除非挂载照片目录。 +- 托管的 AppCard 助手尚未接入通知、分享和 WebView 浮层。 +- 手机端 Sheets 还需要修复网格标签和工具栏([BACKLOG.md](BACKLOG.md))。 +- 没有 `makepad_test` UI 测试套件;CI 只编译,不测试。 + +## 参与贡献 + +`main` 受保护:所有更改都必须通过 pull request(管理员也不例外),且禁止强制推送。从 `main` 创建分支,运行 `setup-native.py --check` 和上面的 Rust、Python 测试;涉及 UI 的更改还要做一次冒烟测试或远程驱动运行,并把原生检查记录到 `docs/validation.md`。保持“一个 Makepad、一个 octos、一个 App Hub”的规则:`setup-native.py --check --cargo-manifest Cargo.toml` 必须通过。 + +## 许可证 + +Apache License 2.0([LICENSE](LICENSE)、[NOTICE](NOTICE))。从 Makepad 复制的源码保留其 [MIT 声明](LICENSES/Makepad-MIT.txt)。各依赖保留各自的许可证。 From 966e696f9af58212d98844d14f5b8ea6fff80348 Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Sat, 26 Sep 2026 01:14:37 -0700 Subject: [PATCH 21/42] README: MAKEPAD_REMOTE=1 binds port 1; use =on for an ephemeral port Co-Authored-By: Claude Opus 5.5 (1M context) --- README.md | 4 ++-- README.zh-CN.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 727d81c6..7e063015 100644 --- a/README.md +++ b/README.md @@ -245,7 +245,7 @@ OCTOSENSE_MAIL_VAULT=file cargo run --release ### Remote-control bridge -Every desktop Makepad app, this shell included, carries a localhost HTTP control surface. Start it with `MAKEPAD_REMOTE=1` (ephemeral port) or `MAKEPAD_REMOTE=`, or `--remote[=PORT]`: +Every desktop Makepad app, this shell included, carries a localhost HTTP control surface. Start it with `MAKEPAD_REMOTE=`, `MAKEPAD_REMOTE=on` (ephemeral port; a number is always read as the port, so `1` means port 1 and fails), or `--remote[=PORT]`: ```sh MAKEPAD_REMOTE=8399 cargo run --release @@ -269,7 +269,7 @@ Add `&wait=1` to an input route to answer after the next frame. The bridge injec On macOS, `MAKEPAD_HIDE_WINDOWS=1` keeps windows off screen while still rendering, so a remote-driven run does not take over the display: ```sh -MAKEPAD_HIDE_WINDOWS=1 MAKEPAD_REMOTE=1 cargo run --release +MAKEPAD_HIDE_WINDOWS=1 MAKEPAD_REMOTE=on cargo run --release ``` Makepad's [`makepad_test`](https://github.com/OctoSense-org/makepad/tree/main/libs/makepad_test) crate (in the `../makepad` sibling) builds on the same two pieces: `#[makepad_test]` tests launch the app hidden, drive it over `--remote` with selectors and waits, and close it with `/gq`. This repository does not have a `makepad_test` suite yet. diff --git a/README.zh-CN.md b/README.zh-CN.md index 61155418..61c14a3e 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -245,7 +245,7 @@ OCTOSENSE_MAIL_VAULT=file cargo run --release ### 远程控制桥 -每个桌面端 Makepad 应用(包括本 Shell)都内置一个本机 HTTP 控制接口。用 `MAKEPAD_REMOTE=1`(临时端口)、`MAKEPAD_REMOTE=` 或 `--remote[=PORT]` 启用: +每个桌面端 Makepad 应用(包括本 Shell)都内置一个本机 HTTP 控制接口。用 `MAKEPAD_REMOTE=`、`MAKEPAD_REMOTE=on`(临时端口;数字一律按端口解析,`1` 即端口 1,会绑定失败)或 `--remote[=PORT]` 启用: ```sh MAKEPAD_REMOTE=8399 cargo run --release @@ -269,7 +269,7 @@ MAKEPAD_REMOTE=8399 cargo run --release 在 macOS 上,`MAKEPAD_HIDE_WINDOWS=1` 让窗口不显示在屏幕上但仍然渲染,这样远程驱动的运行不会占用屏幕: ```sh -MAKEPAD_HIDE_WINDOWS=1 MAKEPAD_REMOTE=1 cargo run --release +MAKEPAD_HIDE_WINDOWS=1 MAKEPAD_REMOTE=on cargo run --release ``` Makepad 的 [`makepad_test`](https://github.com/OctoSense-org/makepad/tree/main/libs/makepad_test) crate(位于 `../makepad` 同级目录中)正是基于这两者:`#[makepad_test]` 测试以隐藏窗口启动应用,通过 `--remote` 用选择器和等待条件驱动它,最后用 `/gq` 关闭。本仓库目前还没有 `makepad_test` 测试套件。 From 594c468af25cd17baf0813b0243a74bbedf87c2e Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Sat, 26 Sep 2026 01:26:01 -0700 Subject: [PATCH 22/42] Docs: work with any coding agent; no octos or AppCard-repo coupling - AGENTS.md says it works the same for Codex, Claude Code, Cursor, Gemini CLI, Copilot or a person, and CLAUDE.md / GEMINI.md import it for agents that only read those names. - tools/octo is this repo's own CLI around hub and card-host; it is unrelated to octos and needs no AI service. - Links to OctoSense-AppCard (being deleted) point at OctoSense-System-Apps apps/appcard, where the runtime and L0 live now. - Agent-specific leftovers removed (a For-Claude skill line, a 'Hypotheses for Codex' heading, local scratch paths). Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/android-appcard-build.md | 4 ++-- docs/plans/2026-09-04-makeos-extraction.md | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/android-appcard-build.md b/docs/android-appcard-build.md index 3c0cc602..6c249bf4 100644 --- a/docs/android-appcard-build.md +++ b/docs/android-appcard-build.md @@ -68,8 +68,8 @@ The exact invocation used for the foundation PR, with the paths of that session, for the record: ```sh -MAKEPAD_ANDROID_EXTRA_LIBS="liboctos.so=/private/tmp/claude-505/-Users-user-home-splash/0bdb704b-c531-48c0-b6a5-009881090313/scratchpad/appcard-apk/octos/target/aarch64-linux-android/release/octos" \ - /private/tmp/claude-505/-Users-user-home-splash/0bdb704b-c531-48c0-b6a5-009881090313/scratchpad/bt-1b11c4a/target/debug/cargo-makepad \ +MAKEPAD_ANDROID_EXTRA_LIBS="liboctos.so=/abs/path/to/octos/target/aarch64-linux-android/release/octos" \ + /abs/path/to/makepad-buildtool/target/debug/cargo-makepad \ makepad android run -p octosense --release ``` diff --git a/docs/plans/2026-09-04-makeos-extraction.md b/docs/plans/2026-09-04-makeos-extraction.md index 4f8286ce..cd57dc6b 100644 --- a/docs/plans/2026-09-04-makeos-extraction.md +++ b/docs/plans/2026-09-04-makeos-extraction.md @@ -2,7 +2,6 @@ > Historical record from before the OctoSense rename. Original names, commands and artifact paths are retained for traceability. -> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task after the user agrees to the scope. **Goal:** Create a minimal MakeOS project in this directory that opens with `cargo run`, hosts compatible Makepad applications, and records every copied upstream file for deliberate updates. From 5d270aa79ecddfd64e02d51505f6dcea09b7f599 Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Sat, 26 Sep 2026 10:11:06 -0700 Subject: [PATCH 23/42] Ship Rinx mini apps with the shared Octos runtime --- Cargo.lock | 46 ++++++++++++++++++---------- Cargo.toml | 63 +++++++++++++++++++++++++++------------ apps/appcard/Cargo.toml | 4 +-- apps/reference/Cargo.toml | 4 +-- native-apps.lock.json | 2 +- native-runtime.lock.json | 2 +- runtime-patches.lock.json | 16 +--------- src/main.rs | 4 +++ src/mobile_app.rs | 2 +- 9 files changed, 87 insertions(+), 56 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6c15b8e4..513202c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -356,7 +356,7 @@ dependencies = [ [[package]] name = "article-core" version = "0.1.0" -source = "git+https://github.com/upstreamlabs/Rinx?rev=5ad2c1739e5b4776588518ab76440a5367c9968e#5ad2c1739e5b4776588518ab76440a5367c9968e" +source = "git+https://github.com/hagency-org/Rinx.git?rev=ba5cd50e320742339806712d48dea2901dff3318#ba5cd50e320742339806712d48dea2901dff3318" dependencies = [ "blake3", "image", @@ -373,7 +373,7 @@ dependencies = [ [[package]] name = "article-makepad" version = "0.1.0" -source = "git+https://github.com/upstreamlabs/Rinx?rev=5ad2c1739e5b4776588518ab76440a5367c9968e#5ad2c1739e5b4776588518ab76440a5367c9968e" +source = "git+https://github.com/hagency-org/Rinx.git?rev=ba5cd50e320742339806712d48dea2901dff3318#ba5cd50e320742339806712d48dea2901dff3318" dependencies = [ "article-core", "libc", @@ -5706,7 +5706,7 @@ dependencies = [ [[package]] name = "matrix-sdk" version = "0.18.0" -source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +source = "git+https://github.com/matrix-org/matrix-rust-sdk?rev=6892cb217ae4a886571e928c8efcccfbec5490a6#6892cb217ae4a886571e928c8efcccfbec5490a6" dependencies = [ "anymap2", "as_variant", @@ -5764,7 +5764,7 @@ dependencies = [ [[package]] name = "matrix-sdk-base" version = "0.18.0" -source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +source = "git+https://github.com/matrix-org/matrix-rust-sdk?rev=6892cb217ae4a886571e928c8efcccfbec5490a6#6892cb217ae4a886571e928c8efcccfbec5490a6" dependencies = [ "as_variant", "async-trait", @@ -5791,7 +5791,7 @@ dependencies = [ [[package]] name = "matrix-sdk-common" version = "0.18.0" -source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +source = "git+https://github.com/matrix-org/matrix-rust-sdk?rev=6892cb217ae4a886571e928c8efcccfbec5490a6#6892cb217ae4a886571e928c8efcccfbec5490a6" dependencies = [ "eyeball-im", "futures-core", @@ -5814,7 +5814,7 @@ dependencies = [ [[package]] name = "matrix-sdk-crypto" version = "0.18.0" -source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +source = "git+https://github.com/matrix-org/matrix-rust-sdk?rev=6892cb217ae4a886571e928c8efcccfbec5490a6#6892cb217ae4a886571e928c8efcccfbec5490a6" dependencies = [ "aes 0.9.3", "as_variant", @@ -5853,7 +5853,7 @@ dependencies = [ [[package]] name = "matrix-sdk-indexeddb" version = "0.18.0" -source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +source = "git+https://github.com/matrix-org/matrix-rust-sdk?rev=6892cb217ae4a886571e928c8efcccfbec5490a6#6892cb217ae4a886571e928c8efcccfbec5490a6" dependencies = [ "async-trait", "base64 0.23.1", @@ -5885,7 +5885,7 @@ dependencies = [ [[package]] name = "matrix-sdk-sqlite" version = "0.18.0" -source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +source = "git+https://github.com/matrix-org/matrix-rust-sdk?rev=6892cb217ae4a886571e928c8efcccfbec5490a6#6892cb217ae4a886571e928c8efcccfbec5490a6" dependencies = [ "as_variant", "async-trait", @@ -5912,7 +5912,7 @@ dependencies = [ [[package]] name = "matrix-sdk-store-encryption" version = "0.18.0" -source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +source = "git+https://github.com/matrix-org/matrix-rust-sdk?rev=6892cb217ae4a886571e928c8efcccfbec5490a6#6892cb217ae4a886571e928c8efcccfbec5490a6" dependencies = [ "base64 0.23.1", "blake3", @@ -5934,7 +5934,7 @@ dependencies = [ [[package]] name = "matrix-sdk-ui" version = "0.18.0" -source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +source = "git+https://github.com/matrix-org/matrix-rust-sdk?rev=6892cb217ae4a886571e928c8efcccfbec5490a6#6892cb217ae4a886571e928c8efcccfbec5490a6" dependencies = [ "as_variant", "async-rx", @@ -7159,7 +7159,7 @@ dependencies = [ [[package]] name = "octosense-app-hub" version = "0.1.0" -source = "git+https://www.github.com/OctoSense-org/OctoSense-App-Hub?rev=4605128d46fb982828d8198e0d71d62a39c7d6d6#4605128d46fb982828d8198e0d71d62a39c7d6d6" +source = "git+https://www.github.com/OctoSense-org/OctoSense-App-Hub?rev=b0591e2caa0dc5c62927f7b85dbf826a50618a03#b0591e2caa0dc5c62927f7b85dbf826a50618a03" dependencies = [ "base64 0.22.1", "blake3", @@ -7175,7 +7175,7 @@ dependencies = [ [[package]] name = "octosense-app-hub-app" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/OctoSense-App-Hub?rev=4605128d46fb982828d8198e0d71d62a39c7d6d6#4605128d46fb982828d8198e0d71d62a39c7d6d6" +source = "git+https://www.github.com/OctoSense-org/OctoSense-App-Hub?rev=b0591e2caa0dc5c62927f7b85dbf826a50618a03#b0591e2caa0dc5c62927f7b85dbf826a50618a03" dependencies = [ "makepad-app-module", "makepad-widgets", @@ -7189,7 +7189,7 @@ dependencies = [ [[package]] name = "octosense-app-policy" version = "0.1.0" -source = "git+https://www.github.com/OctoSense-org/OctoSense-App-Hub?rev=4605128d46fb982828d8198e0d71d62a39c7d6d6#4605128d46fb982828d8198e0d71d62a39c7d6d6" +source = "git+https://www.github.com/OctoSense-org/OctoSense-App-Hub?rev=b0591e2caa0dc5c62927f7b85dbf826a50618a03#b0591e2caa0dc5c62927f7b85dbf826a50618a03" dependencies = [ "blake3", "makepad-widgets", @@ -7210,7 +7210,7 @@ dependencies = [ [[package]] name = "octosense-appstore" version = "0.1.0" -source = "git+https://www.github.com/OctoSense-org/OctoSense-App-Hub?rev=4605128d46fb982828d8198e0d71d62a39c7d6d6#4605128d46fb982828d8198e0d71d62a39c7d6d6" +source = "git+https://www.github.com/OctoSense-org/OctoSense-App-Hub?rev=b0591e2caa0dc5c62927f7b85dbf826a50618a03#b0591e2caa0dc5c62927f7b85dbf826a50618a03" dependencies = [ "makepad-app-module", "makepad-widgets", @@ -8369,7 +8369,7 @@ dependencies = [ [[package]] name = "rinx" version = "1.0.0-beta.1" -source = "git+https://github.com/upstreamlabs/Rinx?rev=5ad2c1739e5b4776588518ab76440a5367c9968e#5ad2c1739e5b4776588518ab76440a5367c9968e" +source = "git+https://github.com/hagency-org/Rinx.git?rev=ba5cd50e320742339806712d48dea2901dff3318#ba5cd50e320742339806712d48dea2901dff3318" dependencies = [ "anyhow", "arboard", @@ -8405,10 +8405,16 @@ dependencies = [ "mime", "mime_guess", "num_cpus", + "octos-app-transport", + "octoscript-makepad", + "octoscript-ui-l0", + "octoscript-widgets", + "octosense-app-policy", "pulldown-cmark 0.13.4", "rand 0.8.8", "rangemap", "rfd", + "rinx-miniapp-core", "robius-directories", "robius-file-picker", "robius-location", @@ -8427,10 +8433,20 @@ dependencies = [ "tracing-subscriber", "unicode-segmentation 1.13.3", "url", + "uuid", "webpki-root-certs", "winresource", ] +[[package]] +name = "rinx-miniapp-core" +version = "0.1.0" +source = "git+https://github.com/hagency-org/Rinx.git?rev=ba5cd50e320742339806712d48dea2901dff3318#ba5cd50e320742339806712d48dea2901dff3318" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "rmcp" version = "1.8.0" diff --git a/Cargo.toml b/Cargo.toml index 25d3aa8a..a2f70eb3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,19 +40,19 @@ path = "src/main.rs" [dependencies] # `appcard,maps`: the Octoscript-AppCard widget set (kit, text roles, weather, # finance, matplot) and the map navigation layer the appcard module renders. -makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4", features = ["appcard", "maps"] } -makepad-studio-protocol = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4" } -makepad-network = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4" } -makepad-wm-api = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4" } +makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "3fda5b6f9b48f390bd5396938a95d30ad035cd6d", features = ["appcard", "maps"] } +makepad-studio-protocol = { git = "https://github.com/OctoSense-org/makepad.git", rev = "3fda5b6f9b48f390bd5396938a95d30ad035cd6d" } +makepad-network = { git = "https://github.com/OctoSense-org/makepad.git", rev = "3fda5b6f9b48f390bd5396938a95d30ad035cd6d" } +makepad-wm-api = { git = "https://github.com/OctoSense-org/makepad.git", rev = "3fda5b6f9b48f390bd5396938a95d30ad035cd6d" } # The AI services wire only: the WM relays frames between the aichat child and the apps. -makepad-ai-services = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4" } -makepad-strict-json = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4" } +makepad-ai-services = { git = "https://github.com/OctoSense-org/makepad.git", rev = "3fda5b6f9b48f390bd5396938a95d30ad035cd6d" } +makepad-strict-json = { git = "https://github.com/OctoSense-org/makepad.git", rev = "3fda5b6f9b48f390bd5396938a95d30ad035cd6d" } # In-process hosting (aicontrol §3): the module contract, the palette applied # into each instance's isolate, and the modules this build links. -makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4" } -makepad-wm-theme = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4" } -makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4", default-features = false, optional = true } -makepad-photos = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4", default-features = false, optional = true } +makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "3fda5b6f9b48f390bd5396938a95d30ad035cd6d" } +makepad-wm-theme = { git = "https://github.com/OctoSense-org/makepad.git", rev = "3fda5b6f9b48f390bd5396938a95d30ad035cd6d" } +makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "3fda5b6f9b48f390bd5396938a95d30ad035cd6d", default-features = false, optional = true } +makepad-photos = { git = "https://github.com/OctoSense-org/makepad.git", rev = "3fda5b6f9b48f390bd5396938a95d30ad035cd6d", default-features = false, optional = true } octosense-reference = { path = "apps/reference", optional = true } # The whole Octoscript-AppCard app hosted in a tile, in-process (Phase B of # the AppCard port): octos-app's AppShell — routing brain, cards, kernel agent. @@ -62,7 +62,7 @@ octosense-appcard = { path = "apps/appcard", optional = true } # module (`card`) that runs contained script apps, the system apps packed at # build time from the selection `OCTOSENSE_SYSTEM_APPS` names # (system-apps.json, .cargo/config.toml), installed apps and their icons. -octosense-app-hub-app = { git = "https://github.com/OctoSense-org/OctoSense-App-Hub", rev = "4605128d46fb982828d8198e0d71d62a39c7d6d6", optional = true } +octosense-app-hub-app = { git = "https://github.com/OctoSense-org/OctoSense-App-Hub", rev = "b0591e2caa0dc5c62927f7b85dbf826a50618a03", optional = true } # The `mail` host service the Mail system app calls through `host.request`: # accounts, message lists and send run here, with the passwords the host # holds; the app never gets a socket or a secret. From the pinned @@ -71,27 +71,27 @@ octosense-app-hub-app = { git = "https://github.com/OctoSense-org/OctoSense-App- octosense-mail-service = { path = "../OctoSense-System-Apps/apps/mail/host-service", optional = true } # Rinx (the Matrix client with Moments and articles) as an in-process module. # Built without its standalone window entry point. -rinx = { git = "https://github.com/upstreamlabs/Rinx", rev = "5ad2c1739e5b4776588518ab76440a5367c9968e", default-features = false, features = ["octosense-module"], optional = true } +rinx = { git = "https://github.com/hagency-org/Rinx.git", rev = "ba5cd50e320742339806712d48dea2901dff3318", default-features = false, features = ["octosense-module"], optional = true } # The assistant as a module, seated in the pane in-process: the web build's # only chat, a desktop option. Without the engine feature: no model runtime, # NoModel and the tool console (the child process keeps the real models). -makepad-aichat = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4", default-features = false, optional = true } +makepad-aichat = { git = "https://github.com/OctoSense-org/makepad.git", rev = "3fda5b6f9b48f390bd5396938a95d30ad035cd6d", default-features = false, optional = true } # A phone cannot spawn the desktop catalog's Cargo binaries. Link the # available modules into native mobile builds without requiring CLI flags. [target.'cfg(any(target_os = "android", target_os = "ios"))'.dependencies] octosense-reference = { path = "apps/reference" } octosense-appcard = { path = "apps/appcard" } -makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4", default-features = false } +makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "3fda5b6f9b48f390bd5396938a95d30ad035cd6d", default-features = false } [features] # App Hub is part of the standard launcher: the store, the Card runner and # the system apps (News, Photos, Maps, Camera, Mail) it runs. -default = ["app-hub"] +default = ["app-hub", "app-rinx", "app-appcard"] # Also usable on desktop for testing the same embedded apps with --module. # Photos is the system app here; `app-photos` links Makepad's native Photos # module instead, for comparison (a linked module of the same id wins). -mobile-apps = ["app-reference", "app-sheets", "app-appcard", "app-hub"] +mobile-apps = ["app-rinx", "app-reference", "app-sheets", "app-appcard", "app-hub"] # App Hub and the host services its system apps call. app-hub = ["dep:octosense-app-hub-app", "dep:octosense-mail-service"] app-reference = ["dep:octosense-reference"] @@ -178,6 +178,31 @@ ruma = { git = "https://github.com/project-robius/ruma.git", branch = "tsp" } # cannot point a source at another rev of itself, so every App Hub library # resolves through the www host alias of the same repository, at the pin. [patch."https://github.com/OctoSense-org/OctoSense-App-Hub"] -octosense-app-hub = { git = "https://www.github.com/OctoSense-org/OctoSense-App-Hub", rev = "4605128d46fb982828d8198e0d71d62a39c7d6d6" } -octosense-app-policy = { git = "https://www.github.com/OctoSense-org/OctoSense-App-Hub", rev = "4605128d46fb982828d8198e0d71d62a39c7d6d6" } -octosense-appstore = { git = "https://www.github.com/OctoSense-org/OctoSense-App-Hub", rev = "4605128d46fb982828d8198e0d71d62a39c7d6d6" } +octosense-app-hub = { git = "https://www.github.com/OctoSense-org/OctoSense-App-Hub", rev = "b0591e2caa0dc5c62927f7b85dbf826a50618a03" } +octosense-app-policy = { git = "https://www.github.com/OctoSense-org/OctoSense-App-Hub", rev = "b0591e2caa0dc5c62927f7b85dbf826a50618a03" } +octosense-appstore = { git = "https://www.github.com/OctoSense-org/OctoSense-App-Hub", rev = "b0591e2caa0dc5c62927f7b85dbf826a50618a03" } + +octosense-app-hub-app = { git = "https://www.github.com/OctoSense-org/OctoSense-App-Hub", rev = "b0591e2caa0dc5c62927f7b85dbf826a50618a03" } + +[patch."https://github.com/OctoSense-org/makepad"] +makepad-script = { path = "../makepad/platform/script" } +makepad-widgets = { path = "../makepad/widgets" } +makepad-code-editor = { path = "../makepad/code_editor" } +makepad-ai = { path = "../makepad/libs/makepad_ai" } +makepad-studio-protocol = { path = "../makepad/platform/studio" } +makepad-network = { path = "../makepad/platform/network" } +makepad-wm-api = { path = "../makepad/libs/wm_api" } +makepad-ai-services = { path = "../makepad/libs/ai/services" } +makepad-strict-json = { path = "../makepad/libs/strict_json" } +makepad-app-module = { path = "../makepad/libs/app_module" } +makepad-wm-theme = { path = "../makepad/libs/wm_theme" } +makepad-sheets = { path = "../makepad/apps/sheets" } +makepad-photos = { path = "../makepad/apps/photos" } +makepad-aichat = { path = "../makepad/apps/aichat" } +# Rinx's article editor: the Markdown and math components. +makepad-markdown = { path = "../makepad/libs/markdown" } +makepad-markdown-widgets = { path = "../makepad/libs/markdown_widgets" } + +# Share AppCard's transport registry with Rinx; never link two core owners. +[patch."https://github.com/OctoSense-org/OctoSense-System-Apps.git"] +octos-app-transport = { path = "../OctoSense-System-Apps/apps/appcard/app/crates/octos-app-transport" } diff --git a/apps/appcard/Cargo.toml b/apps/appcard/Cargo.toml index 8a4c21de..e8c674e0 100644 --- a/apps/appcard/Cargo.toml +++ b/apps/appcard/Cargo.toml @@ -6,8 +6,8 @@ license = "Apache-2.0" publish = false [dependencies] -makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4", features = ["appcard", "maps"] } -makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4" } +makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "3fda5b6f9b48f390bd5396938a95d30ad035cd6d", features = ["appcard", "maps"] } +makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "3fda5b6f9b48f390bd5396938a95d30ad035cd6d" } # The whole AppCard assistant as a library, from the pinned # OctoSense-System-Apps checkout beside this repository (apps/appcard/app/app; # native-apps.lock.json, tools/setup-native.py): routing brain, card store and diff --git a/apps/reference/Cargo.toml b/apps/reference/Cargo.toml index 18e2d10e..e616bb67 100644 --- a/apps/reference/Cargo.toml +++ b/apps/reference/Cargo.toml @@ -6,5 +6,5 @@ license = "Apache-2.0" publish = false [dependencies] -makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4" } -makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4" } +makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "3fda5b6f9b48f390bd5396938a95d30ad035cd6d" } +makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "3fda5b6f9b48f390bd5396938a95d30ad035cd6d" } diff --git a/native-apps.lock.json b/native-apps.lock.json index 25e9df59..782be5fb 100644 --- a/native-apps.lock.json +++ b/native-apps.lock.json @@ -3,7 +3,7 @@ "repositories": { "system-apps": { "url": "https://github.com/OctoSense-org/OctoSense-System-Apps.git", - "revision": "4d99cb589d00207a9a63e32d46de6685843929d1" + "revision": "1576bec6a585758e48c3252030cad2a9bf9c80db" } } } diff --git a/native-runtime.lock.json b/native-runtime.lock.json index cbc46b6e..1c1b2926 100644 --- a/native-runtime.lock.json +++ b/native-runtime.lock.json @@ -1,5 +1,5 @@ { "schema_version": 1, "url": "https://github.com/OctoSense-org/Octoscript-Makepad.git", - "revision": "c4c9682219d5bb549856e35086adf1b354844dc3" + "revision": "b9034566b0607c5bb8c04134d17139f99989e1d0" } diff --git a/runtime-patches.lock.json b/runtime-patches.lock.json index 3c1db06b..bf00d958 100644 --- a/runtime-patches.lock.json +++ b/runtime-patches.lock.json @@ -1,17 +1,3 @@ { - "schema_version": 1, - "makepad": { - "base_revision": "1d3d383e84a66dbb18a4a860f505430c9d5b20f4", - "source_commit": "d7a834c74783e2032bc9e2cb9e693eaa2927b1d9", - "patch": "patches/runtime/makepad-contained-apps.patch", - "sha256": "70419d98644af0820816f8bbc480365fa214c71f96eea0dba4b541c7ec9d59a2", - "tree": "3889a3496226def432ecdf8131a9d12eb822e59b", - "product_fixes": [ - "Contained script apps (OctoSense-org/makepad#30): every path out of an isolate answers to its policy; images/web grants; WebReader, GestureView, SheetView, CameraPreview; parse_feed and fs.read_bytes.", - "Host services: a host takes only its own isolates' requests, and an asynchronous answer wakes the UI.", - "A contained app cannot collect a secret: password and one-time-code fields are inert in a policed isolate.", - "Text: a run's trailing space keeps its width unless the row wraps there; every widget takes text/set_text from script.", - "Sockets and servers: net.web_socket answers to the app's host list like net.http_request; net.socket_stream and net.http_server are refused in a policed isolate; a Video's network and file sources answer to the host list and the storage jail." - ] - } + "schema_version": 1 } diff --git a/src/main.rs b/src/main.rs index ae5e617d..79777a07 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4403,6 +4403,10 @@ impl AppMain for App { } } self.phone_animation_event(cx,event); + if self.state.as_ref().is_some_and(|state| state.style.target.mobile()) && event.back_pressed() { + self.phone_action(cx, mobile::PhoneHit::Back); + return; + } if let Some(ne) = self.style_frame.is_event(event) { if self.state.is_some() { let dt=if self.style_time==0.0 {0.0}else{(ne.time-self.style_time).min(0.05)}; diff --git a/src/mobile_app.rs b/src/mobile_app.rs index b01793ab..8a3f8bc3 100644 --- a/src/mobile_app.rs +++ b/src/mobile_app.rs @@ -340,7 +340,7 @@ impl App { self.state_mut().phone.keyboard_target=0.0; self.animate_phone(cx); } - fn phone_action(&mut self,cx:&mut Cx,hit:PhoneHit) { + pub(super) fn phone_action(&mut self,cx:&mut Cx,hit:PhoneHit) { match hit { PhoneHit::App(app)=>{ // A running window of the app, a home tile's own client From 0f7105f983a19e7c02820739265542945c736cb6 Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Sat, 26 Sep 2026 10:27:55 -0700 Subject: [PATCH 24/42] Runtime to Octoscript-Makepad 463e3da8 (makepad cd812acd); drop the makepad#30 patch and the App Hub alias - native-runtime.lock.json selects Octoscript-Makepad main 463e3da8, whose runtime.json pins makepad main cd812acd, the merge of OctoSense-org/makepad#30. The Makepad revs in Cargo.toml and apps/*/Cargo.toml follow. - makepad#30 is in the runtime now, so runtime-patches.lock.json is empty (schema_version only, as setup-native.py accepts) and patches/runtime/makepad-contained-apps.patch is gone. The patched tree 3889a349 is exactly cd812acd's tree. - App Hub moves to main 0d36f50b (OctoSense-App-Hub#4). The pinned System-Apps' Mail service now names the same rev (System-Apps#5), so the www.github.com [patch] that kept one App Hub is removed; the graph has one App Hub, one Makepad and one octos source. - native-apps.lock.json selects OctoSense-System-Apps main 21b9897f. - README (en + zh-CN) and setup-native.py's docstring stop describing the runtime patch and the three App Hub [patch] entries. Co-Authored-By: Claude Opus 5.5 (1M context) --- Cargo.lock | 8 +- Cargo.toml | 38 +- README.md | 18 +- README.zh-CN.md | 18 +- apps/appcard/Cargo.toml | 4 +- apps/reference/Cargo.toml | 4 +- native-apps.lock.json | 2 +- native-runtime.lock.json | 2 +- patches/runtime/makepad-contained-apps.patch | 4414 ------------------ runtime-patches.lock.json | 16 +- tools/setup-native.py | 6 +- 11 files changed, 47 insertions(+), 4483 deletions(-) delete mode 100644 patches/runtime/makepad-contained-apps.patch diff --git a/Cargo.lock b/Cargo.lock index 6c15b8e4..87411fa2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7159,7 +7159,7 @@ dependencies = [ [[package]] name = "octosense-app-hub" version = "0.1.0" -source = "git+https://www.github.com/OctoSense-org/OctoSense-App-Hub?rev=4605128d46fb982828d8198e0d71d62a39c7d6d6#4605128d46fb982828d8198e0d71d62a39c7d6d6" +source = "git+https://github.com/OctoSense-org/OctoSense-App-Hub?rev=0d36f50b9f0fdfbd8247b5deb8da8baa53c83066#0d36f50b9f0fdfbd8247b5deb8da8baa53c83066" dependencies = [ "base64 0.22.1", "blake3", @@ -7175,7 +7175,7 @@ dependencies = [ [[package]] name = "octosense-app-hub-app" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/OctoSense-App-Hub?rev=4605128d46fb982828d8198e0d71d62a39c7d6d6#4605128d46fb982828d8198e0d71d62a39c7d6d6" +source = "git+https://github.com/OctoSense-org/OctoSense-App-Hub?rev=0d36f50b9f0fdfbd8247b5deb8da8baa53c83066#0d36f50b9f0fdfbd8247b5deb8da8baa53c83066" dependencies = [ "makepad-app-module", "makepad-widgets", @@ -7189,7 +7189,7 @@ dependencies = [ [[package]] name = "octosense-app-policy" version = "0.1.0" -source = "git+https://www.github.com/OctoSense-org/OctoSense-App-Hub?rev=4605128d46fb982828d8198e0d71d62a39c7d6d6#4605128d46fb982828d8198e0d71d62a39c7d6d6" +source = "git+https://github.com/OctoSense-org/OctoSense-App-Hub?rev=0d36f50b9f0fdfbd8247b5deb8da8baa53c83066#0d36f50b9f0fdfbd8247b5deb8da8baa53c83066" dependencies = [ "blake3", "makepad-widgets", @@ -7210,7 +7210,7 @@ dependencies = [ [[package]] name = "octosense-appstore" version = "0.1.0" -source = "git+https://www.github.com/OctoSense-org/OctoSense-App-Hub?rev=4605128d46fb982828d8198e0d71d62a39c7d6d6#4605128d46fb982828d8198e0d71d62a39c7d6d6" +source = "git+https://github.com/OctoSense-org/OctoSense-App-Hub?rev=0d36f50b9f0fdfbd8247b5deb8da8baa53c83066#0d36f50b9f0fdfbd8247b5deb8da8baa53c83066" dependencies = [ "makepad-app-module", "makepad-widgets", diff --git a/Cargo.toml b/Cargo.toml index 25d3aa8a..6c2a036e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,19 +40,19 @@ path = "src/main.rs" [dependencies] # `appcard,maps`: the Octoscript-AppCard widget set (kit, text roles, weather, # finance, matplot) and the map navigation layer the appcard module renders. -makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4", features = ["appcard", "maps"] } -makepad-studio-protocol = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4" } -makepad-network = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4" } -makepad-wm-api = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4" } +makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "cd812acde910e9c4523f83509356450eb8edf908", features = ["appcard", "maps"] } +makepad-studio-protocol = { git = "https://github.com/OctoSense-org/makepad.git", rev = "cd812acde910e9c4523f83509356450eb8edf908" } +makepad-network = { git = "https://github.com/OctoSense-org/makepad.git", rev = "cd812acde910e9c4523f83509356450eb8edf908" } +makepad-wm-api = { git = "https://github.com/OctoSense-org/makepad.git", rev = "cd812acde910e9c4523f83509356450eb8edf908" } # The AI services wire only: the WM relays frames between the aichat child and the apps. -makepad-ai-services = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4" } -makepad-strict-json = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4" } +makepad-ai-services = { git = "https://github.com/OctoSense-org/makepad.git", rev = "cd812acde910e9c4523f83509356450eb8edf908" } +makepad-strict-json = { git = "https://github.com/OctoSense-org/makepad.git", rev = "cd812acde910e9c4523f83509356450eb8edf908" } # In-process hosting (aicontrol §3): the module contract, the palette applied # into each instance's isolate, and the modules this build links. -makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4" } -makepad-wm-theme = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4" } -makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4", default-features = false, optional = true } -makepad-photos = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4", default-features = false, optional = true } +makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "cd812acde910e9c4523f83509356450eb8edf908" } +makepad-wm-theme = { git = "https://github.com/OctoSense-org/makepad.git", rev = "cd812acde910e9c4523f83509356450eb8edf908" } +makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "cd812acde910e9c4523f83509356450eb8edf908", default-features = false, optional = true } +makepad-photos = { git = "https://github.com/OctoSense-org/makepad.git", rev = "cd812acde910e9c4523f83509356450eb8edf908", default-features = false, optional = true } octosense-reference = { path = "apps/reference", optional = true } # The whole Octoscript-AppCard app hosted in a tile, in-process (Phase B of # the AppCard port): octos-app's AppShell — routing brain, cards, kernel agent. @@ -62,7 +62,9 @@ octosense-appcard = { path = "apps/appcard", optional = true } # module (`card`) that runs contained script apps, the system apps packed at # build time from the selection `OCTOSENSE_SYSTEM_APPS` names # (system-apps.json, .cargo/config.toml), installed apps and their icons. -octosense-app-hub-app = { git = "https://github.com/OctoSense-org/OctoSense-App-Hub", rev = "4605128d46fb982828d8198e0d71d62a39c7d6d6", optional = true } +# Same rev as the Mail host service's octosense-appstore (OctoSense-System-Apps): +# one App Hub source, so one host-service registry. +octosense-app-hub-app = { git = "https://github.com/OctoSense-org/OctoSense-App-Hub", rev = "0d36f50b9f0fdfbd8247b5deb8da8baa53c83066", optional = true } # The `mail` host service the Mail system app calls through `host.request`: # accounts, message lists and send run here, with the passwords the host # holds; the app never gets a socket or a secret. From the pinned @@ -75,14 +77,14 @@ rinx = { git = "https://github.com/upstreamlabs/Rinx", rev = "5ad2c1739e5b477658 # The assistant as a module, seated in the pane in-process: the web build's # only chat, a desktop option. Without the engine feature: no model runtime, # NoModel and the tool console (the child process keeps the real models). -makepad-aichat = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4", default-features = false, optional = true } +makepad-aichat = { git = "https://github.com/OctoSense-org/makepad.git", rev = "cd812acde910e9c4523f83509356450eb8edf908", default-features = false, optional = true } # A phone cannot spawn the desktop catalog's Cargo binaries. Link the # available modules into native mobile builds without requiring CLI flags. [target.'cfg(any(target_os = "android", target_os = "ios"))'.dependencies] octosense-reference = { path = "apps/reference" } octosense-appcard = { path = "apps/appcard" } -makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4", default-features = false } +makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "cd812acde910e9c4523f83509356450eb8edf908", default-features = false } [features] # App Hub is part of the standard launcher: the store, the Card runner and @@ -171,13 +173,3 @@ makepad-code-editor = { path = "../makepad/code_editor" } # Rinx's Matrix patches (see Rinx's Cargo.toml). [patch."https://github.com/ruma/ruma"] ruma = { git = "https://github.com/project-robius/ruma.git", branch = "tsp" } - -# One App Hub. The Mail host service pins App Hub's `octosense-appstore` at -# an older rev than the shell crate above; two copies would be two host -# service registries, and the Card runner would never see Mail's. A patch -# cannot point a source at another rev of itself, so every App Hub library -# resolves through the www host alias of the same repository, at the pin. -[patch."https://github.com/OctoSense-org/OctoSense-App-Hub"] -octosense-app-hub = { git = "https://www.github.com/OctoSense-org/OctoSense-App-Hub", rev = "4605128d46fb982828d8198e0d71d62a39c7d6d6" } -octosense-app-policy = { git = "https://www.github.com/OctoSense-org/OctoSense-App-Hub", rev = "4605128d46fb982828d8198e0d71d62a39c7d6d6" } -octosense-appstore = { git = "https://www.github.com/OctoSense-org/OctoSense-App-Hub", rev = "4605128d46fb982828d8198e0d71d62a39c7d6d6" } diff --git a/README.md b/README.md index 7e063015..9df7fa52 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,12 @@ OctoSense-Desktop is the desktop shell of [OctoSense](https://github.com/OctoSen | Repository | Role for this repo | | --- | --- | -| [OctoSense-ROM](https://github.com/OctoSense-org/OctoSense-ROM) | The phone shell (`home/`). Same app model, same runtime patch, same system apps. | +| [OctoSense-ROM](https://github.com/OctoSense-org/OctoSense-ROM) | The phone shell (`home/`). Same app model, same runtime, same system apps. | | [OctoSense-System-Apps](https://github.com/OctoSense-org/OctoSense-System-Apps) | News, Photos, Maps, Camera and Mail bundles, the Mail host service and the AppCard assistant (`octos-app`). Pinned sibling checkout. | | [OctoSense-App-Hub](https://github.com/OctoSense-org/OctoSense-App-Hub) | The signed catalog, the store and the Card runner. Linked as the Git crate `octosense-app-hub-app`. | | [OctoScript-App-Design-Flow](https://github.com/OctoSense-org/OctoScript-App-Design-Flow) | Where apps are designed, built and published to the App Hub. | | [OctoScript-Makepad](https://github.com/OctoSense-org/OctoScript-Makepad) | The runtime release that pins Makepad and OctoScript. Pinned sibling checkout. | -| [makepad (OctoSense fork)](https://github.com/OctoSense-org/makepad) | The framework. Pinned sibling checkout, plus a reviewed patch. | +| [makepad (OctoSense fork)](https://github.com/OctoSense-org/makepad) | The framework. Pinned sibling checkout. | | [octos](https://github.com/octos-org/octos) | The agent kernel behind the AppCard assistant. Git dependency, one revision (`18fcd3f1`). | ## Repository layout @@ -29,7 +29,7 @@ OctoSense-Desktop is the desktop shell of [OctoSense](https://github.com/OctoSen | `system-apps.json` | Which system apps this build packs, and from where. | | `native-runtime.lock.json` | The OctoScript-Makepad release (and through it, Makepad and OctoScript). | | `native-apps.lock.json` | The OctoSense-System-Apps revision. | -| `runtime-patches.lock.json`, `patches/runtime/` | The reviewed Makepad patch ([OctoSense-org/makepad#30](https://github.com/OctoSense-org/makepad/pull/30)) and its expected tree. | +| `runtime-patches.lock.json` | Reviewed patches on top of the pinned Makepad, with their expected trees. Empty today: the pinned Makepad carries everything the shell needs. | | `tools/setup-native.py` | Prepares and checks the pinned sibling checkouts. | | `scripts/` | `upstream.py` (WM provenance and catalog regeneration), `smoke.py` (native smoke test), their Python tests, and `provision-appcard-llm.sh` (Android). | | `upstream/makepad.json` | Provenance of every file imported from Makepad's `apps/wm`. | @@ -60,19 +60,19 @@ Result: octosense-ws/ OctoSense-Desktop/ this repository octoscript-makepad/ the release native-runtime.lock.json selects - makepad/, octoscript/ the revisions that release's runtime.json pins; makepad carries the patch + makepad/, octoscript/ the revisions that release's runtime.json pins OctoSense-System-Apps/ the revision native-apps.lock.json pins ``` | Command | Effect | | --- | --- | -| `python3 tools/setup-native.py` | Clone missing siblings at their pinned revisions and apply the Makepad patch. | +| `python3 tools/setup-native.py` | Clone missing siblings at their pinned revisions (and apply any patch `runtime-patches.lock.json` names). | | `python3 tools/setup-native.py --check` | Verify the siblings without changing anything. | | `python3 tools/setup-native.py --check --cargo-manifest Cargo.toml` | Also check the locked Cargo graph: one Makepad, one octos, one App Hub. | | `python3 tools/setup-native.py --update` | Move clean checkouts to the locked revisions (after the lock files change). | | `--root DIR`, `--cache DIR` | Use another workspace directory; reuse local Git object caches. | -Local changes in a sibling are preserved; `--update` only moves clean checkouts. A clean checkout of the commit the patch was cut from (`source_commit` in `runtime-patches.lock.json`) is accepted as the same tree. +Local changes in a sibling are preserved; `--update` only moves clean checkouts. When `runtime-patches.lock.json` names a patch, a clean checkout of the commit it was cut from (`source_commit`) is accepted as the same tree. ## Build and run @@ -154,7 +154,7 @@ Precedence: a linked native module beats a system app of the same id, and a syst ### Containment and permissions -A contained app is a bundle: `manifest.json` (id, version, capabilities) plus `main.splash`. The Card runner grants only the capabilities the manifest lists (Mail asks for `storage` and `mail`). The Makepad patch ([makepad#30](https://github.com/OctoSense-org/makepad/pull/30)) enforces this at every exit of an isolate: network and web sockets answer to the app's host list, raw sockets and servers are refused, files stay in the app's storage jail, and password or one-time-code fields are inert inside a policed isolate. +A contained app is a bundle: `manifest.json` (id, version, capabilities) plus `main.splash`. The Card runner grants only the capabilities the manifest lists (Mail asks for `storage` and `mail`). The pinned Makepad ([makepad#30](https://github.com/OctoSense-org/makepad/pull/30)) enforces this at every exit of an isolate: network and web sockets answer to the app's host list, raw sockets and servers are refused, files stay in the app's storage jail, and password or one-time-code fields are inert inside a policed isolate. ### Host services and host-owned sheets @@ -295,9 +295,9 @@ Phone builds always link Reference, Sheets and AppCard, and App Hub with the sys | To move | Edit | Then | | --- | --- | --- | -| Makepad / OctoScript | `native-runtime.lock.json` (a new OctoScript-Makepad release), and the `rev` of the Makepad Git dependencies in `Cargo.toml` and `apps/*/Cargo.toml` | Rebase the patch if needed, update `runtime-patches.lock.json` (base, sha256, tree) | +| Makepad / OctoScript | `native-runtime.lock.json` (a new OctoScript-Makepad release), and the `rev` of the Makepad Git dependencies in `Cargo.toml` and `apps/*/Cargo.toml` | If a patch is needed on top, record it in `runtime-patches.lock.json` (base, sha256, tree) | | System apps, Mail service, AppCard | `revision` in `native-apps.lock.json` | — | -| App Hub | `rev` of `octosense-app-hub-app` and the three `[patch]` entries in `Cargo.toml` | — | +| App Hub | `rev` of `octosense-app-hub-app` in `Cargo.toml` | Keep it at the App Hub rev the pinned System-Apps Mail service names (`octosense-appstore`), so the graph has one App Hub | After any change: `python3 tools/setup-native.py --update`, `cargo update` as needed, then `python3 tools/setup-native.py --check --cargo-manifest Cargo.toml` and the tests below. diff --git a/README.zh-CN.md b/README.zh-CN.md index 61c14a3e..c075f8a9 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -8,12 +8,12 @@ OctoSense-Desktop 是 [OctoSense](https://github.com/OctoSense-org)(运行在 | 仓库 | 与本仓库的关系 | | --- | --- | -| [OctoSense-ROM](https://github.com/OctoSense-org/OctoSense-ROM/blob/main/README.zh-CN.md) | 手机 Shell(`home/`)。相同的应用模型、相同的运行时补丁、相同的系统应用。 | +| [OctoSense-ROM](https://github.com/OctoSense-org/OctoSense-ROM/blob/main/README.zh-CN.md) | 手机 Shell(`home/`)。相同的应用模型、相同的运行时、相同的系统应用。 | | [OctoSense-System-Apps](https://github.com/OctoSense-org/OctoSense-System-Apps) | 新闻、相册、地图、相机、邮件的应用包,邮件宿主服务,以及 AppCard 助手(`octos-app`)。以固定版本的同级目录检出。 | | [OctoSense-App-Hub](https://github.com/OctoSense-org/OctoSense-App-Hub) | 签名目录、商店和 Card 运行器。以 Git crate `octosense-app-hub-app` 链接。 | | [OctoScript-App-Design-Flow](https://github.com/OctoSense-org/OctoScript-App-Design-Flow) | 设计、构建应用并发布到 App Hub 的地方。 | | [OctoScript-Makepad](https://github.com/OctoSense-org/OctoScript-Makepad) | 固定 Makepad 与 OctoScript 版本的运行时发行版。以固定版本的同级目录检出。 | -| [makepad(OctoSense 分支)](https://github.com/OctoSense-org/makepad) | 框架。以固定版本的同级目录检出,并打上经过评审的补丁。 | +| [makepad(OctoSense 分支)](https://github.com/OctoSense-org/makepad) | 框架。以固定版本的同级目录检出。 | | [octos](https://github.com/octos-org/octos) | AppCard 助手背后的 Agent 内核。Git 依赖,只有一个版本(`18fcd3f1`)。 | ## 仓库结构 @@ -29,7 +29,7 @@ OctoSense-Desktop 是 [OctoSense](https://github.com/OctoSense-org)(运行在 | `system-apps.json` | 本次构建打包哪些系统应用,以及从哪里取。 | | `native-runtime.lock.json` | OctoScript-Makepad 发行版(并由它确定 Makepad 和 OctoScript)。 | | `native-apps.lock.json` | OctoSense-System-Apps 的版本。 | -| `runtime-patches.lock.json`、`patches/runtime/` | 经过评审的 Makepad 补丁([OctoSense-org/makepad#30](https://github.com/OctoSense-org/makepad/pull/30))及其期望的源码树。 | +| `runtime-patches.lock.json` | 固定 Makepad 之上经过评审的补丁及其期望的源码树。目前为空:固定的 Makepad 已包含 Shell 所需的一切。 | | `tools/setup-native.py` | 准备并检查固定版本的同级目录。 | | `scripts/` | `upstream.py`(WM 来源记录与目录重新生成)、`smoke.py`(原生冒烟测试)、它们的 Python 测试,以及 `provision-appcard-llm.sh`(Android)。 | | `upstream/makepad.json` | 每个从 Makepad `apps/wm` 引入的文件的来源记录。 | @@ -60,19 +60,19 @@ python3 tools/setup-native.py octosense-ws/ OctoSense-Desktop/ this repository octoscript-makepad/ the release native-runtime.lock.json selects - makepad/, octoscript/ the revisions that release's runtime.json pins; makepad carries the patch + makepad/, octoscript/ the revisions that release's runtime.json pins OctoSense-System-Apps/ the revision native-apps.lock.json pins ``` | 命令 | 作用 | | --- | --- | -| `python3 tools/setup-native.py` | 按固定版本克隆缺失的同级仓库,并应用 Makepad 补丁。 | +| `python3 tools/setup-native.py` | 按固定版本克隆缺失的同级仓库(并应用 `runtime-patches.lock.json` 中列出的补丁,如有)。 | | `python3 tools/setup-native.py --check` | 只校验同级目录,不做任何修改。 | | `python3 tools/setup-native.py --check --cargo-manifest Cargo.toml` | 同时检查锁定的 Cargo 依赖图:只有一个 Makepad、一个 octos、一个 App Hub。 | | `python3 tools/setup-native.py --update` | 把干净的检出移动到锁定版本(在锁文件变更之后)。 | | `--root DIR`、`--cache DIR` | 使用其他工作区目录;复用本地 Git 对象缓存。 | -同级目录中的本地修改会被保留;`--update` 只移动干净的检出。补丁所基于的提交(`runtime-patches.lock.json` 中的 `source_commit`)的干净检出,会被视为同一棵源码树。 +同级目录中的本地修改会被保留;`--update` 只移动干净的检出。当 `runtime-patches.lock.json` 列出补丁时,该补丁所基于的提交(`source_commit`)的干净检出,会被视为同一棵源码树。 ## 构建与运行 @@ -154,7 +154,7 @@ launcher 把四类应用列在一起: ### 隔离与权限 -隔离运行的应用是一个包:`manifest.json`(id、版本、能力)加上 `main.splash`。Card 运行器只授予清单中列出的能力(邮件申请 `storage` 和 `mail`)。Makepad 补丁([makepad#30](https://github.com/OctoSense-org/makepad/pull/30))在 isolate 的每个出口执行这一约束:网络请求和 web socket 受应用的主机列表约束,原始 socket 和服务端被拒绝,文件访问限制在应用的存储沙箱内,密码和一次性验证码输入框在受约束的 isolate 中不起作用。 +隔离运行的应用是一个包:`manifest.json`(id、版本、能力)加上 `main.splash`。Card 运行器只授予清单中列出的能力(邮件申请 `storage` 和 `mail`)。固定的 Makepad([makepad#30](https://github.com/OctoSense-org/makepad/pull/30))在 isolate 的每个出口执行这一约束:网络请求和 web socket 受应用的主机列表约束,原始 socket 和服务端被拒绝,文件访问限制在应用的存储沙箱内,密码和一次性验证码输入框在受约束的 isolate 中不起作用。 ### 宿主服务与宿主自有面板 @@ -295,9 +295,9 @@ cargo makepad android run -p octosense --release | 要更新的内容 | 修改 | 然后 | | --- | --- | --- | -| Makepad / OctoScript | `native-runtime.lock.json`(新的 OctoScript-Makepad 发行版),以及 `Cargo.toml` 和 `apps/*/Cargo.toml` 中 Makepad Git 依赖的 `rev` | 必要时变基补丁,更新 `runtime-patches.lock.json`(base、sha256、tree) | +| Makepad / OctoScript | `native-runtime.lock.json`(新的 OctoScript-Makepad 发行版),以及 `Cargo.toml` 和 `apps/*/Cargo.toml` 中 Makepad Git 依赖的 `rev` | 如需在其上打补丁,记录到 `runtime-patches.lock.json`(base、sha256、tree) | | 系统应用、邮件服务、AppCard | `native-apps.lock.json` 中的 `revision` | — | -| App Hub | `Cargo.toml` 中 `octosense-app-hub-app` 的 `rev` 以及三个 `[patch]` 条目 | — | +| App Hub | `Cargo.toml` 中 `octosense-app-hub-app` 的 `rev` | 与固定的 System-Apps 中 Mail 服务引用的 App Hub 版本(`octosense-appstore`)保持一致,使依赖图中只有一份 App Hub | 任何更改之后:执行 `python3 tools/setup-native.py --update`,按需执行 `cargo update`,再运行 `python3 tools/setup-native.py --check --cargo-manifest Cargo.toml` 和下面的测试。 diff --git a/apps/appcard/Cargo.toml b/apps/appcard/Cargo.toml index 8a4c21de..edf96231 100644 --- a/apps/appcard/Cargo.toml +++ b/apps/appcard/Cargo.toml @@ -6,8 +6,8 @@ license = "Apache-2.0" publish = false [dependencies] -makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4", features = ["appcard", "maps"] } -makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4" } +makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "cd812acde910e9c4523f83509356450eb8edf908", features = ["appcard", "maps"] } +makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "cd812acde910e9c4523f83509356450eb8edf908" } # The whole AppCard assistant as a library, from the pinned # OctoSense-System-Apps checkout beside this repository (apps/appcard/app/app; # native-apps.lock.json, tools/setup-native.py): routing brain, card store and diff --git a/apps/reference/Cargo.toml b/apps/reference/Cargo.toml index 18e2d10e..8814bb4a 100644 --- a/apps/reference/Cargo.toml +++ b/apps/reference/Cargo.toml @@ -6,5 +6,5 @@ license = "Apache-2.0" publish = false [dependencies] -makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4" } -makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "1d3d383e84a66dbb18a4a860f505430c9d5b20f4" } +makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "cd812acde910e9c4523f83509356450eb8edf908" } +makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "cd812acde910e9c4523f83509356450eb8edf908" } diff --git a/native-apps.lock.json b/native-apps.lock.json index 25e9df59..90b0f793 100644 --- a/native-apps.lock.json +++ b/native-apps.lock.json @@ -3,7 +3,7 @@ "repositories": { "system-apps": { "url": "https://github.com/OctoSense-org/OctoSense-System-Apps.git", - "revision": "4d99cb589d00207a9a63e32d46de6685843929d1" + "revision": "21b9897f995c769deda3f24d80adb36d46a92548" } } } diff --git a/native-runtime.lock.json b/native-runtime.lock.json index cbc46b6e..9e20e807 100644 --- a/native-runtime.lock.json +++ b/native-runtime.lock.json @@ -1,5 +1,5 @@ { "schema_version": 1, "url": "https://github.com/OctoSense-org/Octoscript-Makepad.git", - "revision": "c4c9682219d5bb549856e35086adf1b354844dc3" + "revision": "463e3da8ac170a987e34552a1f96551d2a2cc6ea" } diff --git a/patches/runtime/makepad-contained-apps.patch b/patches/runtime/makepad-contained-apps.patch deleted file mode 100644 index f7f1153f..00000000 --- a/patches/runtime/makepad-contained-apps.patch +++ /dev/null @@ -1,4414 +0,0 @@ -diff --git a/draw/src/text/layouter.rs b/draw/src/text/layouter.rs -index 56f7a4d63..616010173 100644 ---- a/draw/src/text/layouter.rs -+++ b/draw/src/text/layouter.rs -@@ -383,7 +383,7 @@ fn layout_multiline(mut self) -> LaidoutText { - .enumerate() - { - if line_index != 0 { -- self.finish_current_row(true); -+ self.finish_current_row(true, false); - } - if self.is_past_max_rows() { - break; -@@ -394,7 +394,7 @@ fn layout_multiline(mut self) -> LaidoutText { - if has_ellipsis_config { - self.apply_ellipsis_truncation() - } else { -- self.finish_current_row(false); -+ self.finish_current_row(false, false); - self.finish_with(false) - } - } -@@ -457,7 +457,7 @@ fn layout_by_word(&mut self, len: usize) { - } else if self.current_row_is_empty() && !self.current_row_is_continuation() { - self.layout_by_grapheme(fitter.pop()); - } else { -- self.finish_current_row(false); -+ self.finish_current_row(false, true); - } - } - } -@@ -482,7 +482,7 @@ fn layout_by_grapheme(&mut self, len: usize) { - if self.current_row_is_empty() { - self.layout_directly(fitter.pop()); - } else { -- self.finish_current_row(false); -+ self.finish_current_row(false, true); - } - } - } -@@ -518,7 +518,8 @@ fn append_text(&mut self, text: &ShapedText) { - self.current_row_end += text.text.len(); - } - -- fn finish_current_row(&mut self, newline: bool) { -+ /// `soft_wrap`: the row ends because the next word did not fit. -+ fn finish_current_row(&mut self, newline: bool, soft_wrap: bool) { - let font = self.font_family.fonts().first(); - let font_size_in_lpxs = self.style.font_size_in_lpxs(); - let ascender_in_lpxs = font.map_or(0.0, |font| font.ascender_in_ems()) * font_size_in_lpxs; -@@ -531,10 +532,12 @@ fn finish_current_row(&mut self, newline: bool) { - .text - .substr(self.current_row_start..self.current_row_end); - let mut width_in_lpxs = self.current_point_in_lpxs.x; -- if self.options.wrap { -+ if soft_wrap { - // A space consumed at a soft line break still belongs to the text - // (and caret positions), but must not widen or offset the visible - // line. Keep the glyphs and source text intact for hit testing. -+ // Only there: a run's last row keeps its trailing space, which is -+ // the gap before the next run (`to Mail`). - let visible_end = text.trim_end().len(); - for glyph in self.glyphs.iter().rev() { - if glyph.cluster < visible_end { -@@ -722,7 +725,7 @@ fn finish_current_row_if_pending(&mut self) { - let has_pending_content = - self.current_row_start != self.current_row_end || !self.glyphs.is_empty(); - if has_pending_content || self.rows.is_empty() { -- self.finish_current_row(false); -+ self.finish_current_row(false, false); - } - } - } -@@ -1496,6 +1499,16 @@ fn letter_spacing_changes_layout_cache_and_wrapping() { - assert_eq!(centered.rows[0].text.as_str(), "CARD "); - assert!((centered.rows[0].width_in_lpxs - word.size_in_lpxs.width).abs() < 0.01); - assert!((centered.rows[0].origin_in_lpxs.x - 0.05).abs() < 0.01); -+ -+ // A run that ends in a space, laid out where it fits: the space is -+ // the gap before whatever run follows (`to Mail`), not a -+ // soft line break, so it keeps its width. -+ let spaced = layouter.get_or_layout(BorrowedLayoutParams { -+ text: "CARD ", -+ options: LayoutOptions {max_width_in_lpxs: Some(1000.0), wrap: true, ..params.options}, -+ ..params -+ }); -+ assert!(spaced.rows[0].width_in_lpxs > word.size_in_lpxs.width + 1.0); - } - - #[test] -diff --git a/libs/html/src/lib.rs b/libs/html/src/lib.rs -index bac0ce06e..03012b53d 100644 ---- a/libs/html/src/lib.rs -+++ b/libs/html/src/lib.rs -@@ -3417,7 +3417,13 @@ fn text_lookups_skip_parser_artifacts() { - /// Treating U+00A0 and U+3000 as collapsible ate ` ` runs and the - /// full-width spaces in CJK text. - #[test] -- fn only_ascii_whitespace_collapses() { -+ fn a_space_before_an_inline_tag_is_kept() { -+ assert_eq!(text_of("

Welcome to Mail now

"), "Welcome to Mail now"); -+ assert_eq!(text_of("

Welcome to Mail

x

"), "Welcome to Mailx"); -+ } -+ -+ #[test] -+ fn only_ascii_whitespace_collapses() { - assert_eq!(text_of("

a  b

"), "a\u{a0} b"); - assert_eq!(text_of("

  

"), "\u{a0}\u{a0}"); - assert_eq!(text_of("

\u{3000}x\u{3000}

"), "\u{3000}x\u{3000}"); -diff --git a/platform/script/src/lib.rs b/platform/script/src/lib.rs -index f929c8784..69d10c047 100644 ---- a/platform/script/src/lib.rs -+++ b/platform/script/src/lib.rs -@@ -24,6 +24,7 @@ macro_rules! script_eval { - pub mod gen_index; - pub mod heap; - pub mod mod_gc; -+pub mod mod_feed; - pub mod mod_html; - pub mod math_aot; - pub mod mod_math; -diff --git a/platform/script/src/mod_feed.rs b/platform/script/src/mod_feed.rs -new file mode 100644 -index 000000000..497cbc9e0 ---- /dev/null -+++ b/platform/script/src/mod_feed.rs -@@ -0,0 +1,872 @@ -+//! `"".parse_feed(style)`: RSS 2.0 or Atom into an array of rows, -+//! `{title, link, source, published, summary, image}`, for an app that reads -+//! feeds. Parsing in script would spend an app's instruction budget on string -+//! scanning; this is pure computation, so every isolate may call it. -+//! -+//! `style` is `"google"` (titles end in ` - Source`), `"digest"` (TechMeme: -+//! titles end in ` (Outlet)`) or anything else for plain feeds. A document -+//! that is not a feed yields `nil`; a malformed one yields fewer rows. -+//! -+//! Items are split on ` bool { -+ ["http://", "https://"].iter().any(|scheme| s.get(..scheme.len()).is_some_and(|head| head.eq_ignore_ascii_case(scheme))) -+} -+ -+/// One story, whatever the feed. -+#[derive(Clone, Debug, Default, PartialEq)] -+struct Headline { -+ title: String, -+ link: String, -+ source: String, -+ source_id: String, -+ published: Option, -+ points: Option, -+ comments: Option, -+ summary: String, -+ discussion: Option, -+ image: Option, -+} -+ -+fn json_string(out: &mut String, s: &str) { -+ out.push('"'); -+ for c in s.chars() { -+ match c { -+ '"' => out.push_str("\\\""), -+ '\\' => out.push_str("\\\\"), -+ '\n' => out.push_str("\\n"), -+ '\r' => out.push_str("\\r"), -+ '\t' => out.push_str("\\t"), -+ c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)), -+ c => out.push(c), -+ } -+ } -+ out.push('"'); -+} -+ -+fn rows_json(rows: &[Headline]) -> String { -+ let mut out = String::from("["); -+ for (i, row) in rows.iter().enumerate() { -+ if i > 0 { -+ out.push(','); -+ } -+ out.push_str("{\"title\":"); -+ json_string(&mut out, &row.title); -+ out.push_str(",\"link\":"); -+ json_string(&mut out, &row.link); -+ out.push_str(",\"source\":"); -+ json_string(&mut out, &row.source); -+ out.push_str(",\"summary\":"); -+ json_string(&mut out, &row.summary); -+ out.push_str(",\"image\":"); -+ json_string(&mut out, row.image.as_deref().unwrap_or("")); -+ out.push_str(",\"published\":"); -+ match row.published { -+ Some(secs) => out.push_str(&secs.to_string()), -+ None => out.push('0'), -+ } -+ out.push('}'); -+ } -+ out.push(']'); -+ out -+} -+ -+pub fn define_feed_module(heap: &mut ScriptHeap, native: &mut ScriptNative) { -+ native.add_type_method( -+ heap, -+ ScriptValueType::REDUX_STRING, -+ id!(parse_feed), -+ &[(id!(style), NIL)], -+ |vm, args| { -+ let sself = script_value!(vm, args.self); -+ let style_value = script_value!(vm, args.style); -+ let style = vm.bx.heap.string_with(style_value, |_, s| s.to_string()).unwrap_or_default(); -+ let style = match style.as_str() { -+ "google" => Style { split_source: true, digest: false }, -+ "digest" => Style { split_source: false, digest: true }, -+ _ => Style::default(), -+ }; -+ let Some(parsed) = vm.bx.heap.string_with(sself, |_, s| parse_styled(s, style)) else { -+ return script_err_unexpected!(vm.bx.threads.cur_ref().trap, "parse_feed called on non-string value"); -+ }; -+ let Ok(rows) = parsed else { return NIL }; -+ let json = rows_json(&rows); -+ let mut json_parser = std::mem::take(&mut vm.bx.threads.cur().json_parser); -+ let result = json_parser.read_json(&json, &mut vm.bx.heap); -+ vm.bx.threads.cur().json_parser = json_parser; -+ result -+ }, -+ ); -+} -+ -+ -+ -+/// How a feed spells its stories, beyond plain RSS or Atom. -+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] -+struct Style { -+ /// Google News: titles end in ` - Source`. -+ split_source: bool, -+ /// A digest (TechMeme): titles end in ` (Author/Outlet)` and the -+ /// description is `Outlet: headline` again. -+ digest: bool, -+} -+ -+fn parse_styled(xml: &str, style: Style) -> Result, String> { -+ // A feed that prefixes every Atom element (``) reads like -+ // a bare one once the prefix is dropped. -+ let unprefixed; -+ let xml = if !xml.contains("`. -+ let body = &chunk[chunk.find('>')? + 1..]; -+ let body = body.split_once(close).map_or(body, |(b, _)| b); -+ parse_item(body, is_atom, style) -+ }) -+ .collect(); -+ Ok(rows) -+} -+ -+const RSS: (&str, &str, bool) = ("", false); -+const ATOM: (&str, &str, bool) = ("", true); -+ -+/// The item element to split on and whether the feed is Atom: the root -+/// element decides (``; `` or ``), else the first item -+/// element found. -+fn feed_kind(xml: &str) -> Option<(&'static str, &'static str, bool)> { -+ match root_element(xml) { -+ Some("feed") => Some(ATOM), -+ Some("rss" | "rdf:RDF") => Some(RSS), -+ _ if has_element(xml, "item") => Some(RSS), -+ _ if has_element(xml, "entry") => Some(ATOM), -+ _ => None, -+ } -+} -+ -+/// The name of the root element: the first element after the prolog (a -+/// byte-order mark, whitespace, ``, ``, ``). -+/// Looking only there keeps a `` inside a description from naming -+/// the kind. -+fn root_element(xml: &str) -> Option<&str> { -+ let prolog_space = |c: char| c.is_whitespace() || c == '\u{feff}'; -+ let mut rest = xml.trim_start_matches(prolog_space); -+ loop { -+ let after_prolog = if let Some(after) = rest.strip_prefix("")?.1 -+ } else if let Some(after) = rest.strip_prefix("")?.1 -+ } else if let Some(after) = rest.strip_prefix("`; what follows is not an element, so the caller falls -+ // back to the item heuristic. Feeds do not carry one. -+ after.split_once('>')?.1 -+ } else { -+ break; -+ }; -+ rest = after_prolog.trim_start_matches(prolog_space); -+ } -+ let name = rest.strip_prefix('<')?; -+ let end = name.find(|c: char| c.is_whitespace() || c == '>' || c == '/').unwrap_or(name.len()); -+ Some(&name[..end]).filter(|n| !n.is_empty()) -+} -+ -+/// Whether `xml` opens an element named exactly `name`. -+fn has_element(xml: &str, name: &str) -> bool { -+ let open = format!("<{name}"); -+ let mut from = 0; -+ while let Some(pos) = xml[from..].find(&open) { -+ from += pos + open.len(); -+ if xml[from..].chars().next().is_some_and(|c| c == '>' || c == '/' || c.is_whitespace()) { -+ return true; -+ } -+ } -+ false -+} -+ -+/// One `` / `` body as a row; None without a title or an http -+/// link. -+fn parse_item(body: &str, is_atom: bool, style: Style) -> Option { -+ let mut title = clean_text(element_text(body, "title")?); -+ if title.is_empty() { -+ return None; -+ } -+ let link = if is_atom { atom_link(body) } else { rss_link(body) }?; -+ let mut source = if is_atom { -+ String::new() -+ } else { -+ element_text(body, "source").map(clean_text).unwrap_or_default() -+ }; -+ if style.split_source { -+ if let Some((head, tail)) = split_google_source(&title) { -+ title = head; -+ source = tail; -+ } -+ } -+ if style.digest { -+ if let Some((head, outlet)) = trailing_outlet(&title) { -+ title = head; -+ source = outlet; -+ } -+ } -+ let mut summary = element_text(body, "description") -+ .or_else(|| element_text(body, "summary")) -+ .or_else(|| element_text(body, "content")) -+ .or_else(|| element_text(body, "content:encoded")) -+ .map(|d| truncate(&clean_text(d), SUMMARY_CHARS)) -+ .unwrap_or_default(); -+ // A description of `Outlet: headline` (a digest's): the outlet is the -+ // row's publisher when nothing named one, and a description that is -+ // only the headline again is no summary. -+ if let Some((outlet, rest)) = outlet_prefix(&title, &summary) { -+ if source.is_empty() { -+ source = outlet; -+ } -+ summary = rest; -+ } -+ let published = element_text(body, "pubDate") -+ .and_then(|d| parse_rfc822(d.trim())) -+ .or_else(|| element_text(body, "published").and_then(|d| parse_rfc3339(d.trim()))) -+ .or_else(|| element_text(body, "updated").and_then(|d| parse_rfc3339(d.trim()))) -+ .or_else(|| element_text(body, "dc:date").and_then(|d| parse_rfc3339(d.trim()))); -+ // The model stamps `source_id` when the rows land: one reader serves -+ // every feed. -+ Some(Headline { -+ title, -+ link, -+ source, -+ source_id: String::new(), -+ published, -+ points: None, -+ comments: None, -+ summary, -+ discussion: None, -+ image: image_url(body), -+ }) -+} -+ -+/// The story's picture, when the item carries one: a `` -+/// that is an image (its `medium` or `type` says so, or neither is given), -+/// a ``, an `` with an image type, else the -+/// first `` in the description or content. Only an http(s) URL -+/// counts, and it is asked for over https whatever the feed says: feeds -+/// still carry plain http picture links (TechMeme's), the app's own -+/// connections are held to transport security on Apple platforms, and a -+/// picture host without https costs the story its picture, not its row. -+fn image_url(body: &str) -> Option { -+ media_image(body, "media:content", true) -+ .or_else(|| media_image(body, "media:thumbnail", false)) -+ .or_else(|| media_image(body, "enclosure", true)) -+ .or_else(|| inline_image(body)) -+ .map(|url| match url.strip_prefix("http://") { -+ Some(rest) => format!("https://{rest}"), -+ None => url, -+ }) -+} -+ -+/// The `url` of the first `` element that is an image. `typed`: the -+/// element carries a `medium` or `type`, and it must say image when it -+/// does (an enclosure must say so; a media:content with neither is taken -+/// as one). Every such element is looked at, not just the first: a feed -+/// may list a video before its still. -+fn media_image(body: &str, tag: &str, typed: bool) -> Option { -+ let open = format!("<{tag}"); -+ let mut from = 0; -+ while let Some(pos) = body[from..].find(&open) { -+ let at = from + pos; -+ let after = &body[at + open.len()..]; -+ let next = after.chars().next()?; -+ from = at + open.len(); -+ if next != '>' && next != '/' && !next.is_whitespace() { -+ continue; -+ } -+ let tag_end = after.find('>')?; -+ let attrs = &after[..tag_end]; -+ if typed { -+ let medium = attribute(attrs, "medium"); -+ let kind = attribute(attrs, "type"); -+ let says_image = medium.as_deref() == Some("image") || kind.as_deref().is_some_and(|t| t.starts_with("image/")); -+ let says_nothing = medium.is_none() && kind.is_none() && tag == "media:content"; -+ if !says_image && !says_nothing { -+ continue; -+ } -+ } -+ if let Some(url) = attribute(attrs, "url").map(|u| decode_entities(&u)) { -+ if is_http_url(&url) { -+ return Some(url); -+ } -+ } -+ } -+ None -+} -+ -+/// The first `` in the item's HTML description or content that is -+/// not a tiny one (a permalink icon, a tracking pixel: anything whose -+/// `width` or `height` says under `MIN_IMAGE_PX`). Tags and attributes in -+/// any case, values quoted or not. -+fn inline_image(body: &str) -> Option { -+ let html = element_text(body, "description") -+ .or_else(|| element_text(body, "content:encoded")) -+ .or_else(|| element_text(body, "content"))?; -+ // CDATA markers dropped and entities decoded: feeds double-encode HTML. -+ let html = decode_entities(&html.replace("", "")); -+ // ASCII lowercasing keeps every byte offset, so positions found in the -+ // lowered copy index the original. -+ let lower = html.to_ascii_lowercase(); -+ let mut from = 0; -+ while let Some(pos) = lower[from..].find("') else { break }; -+ let attrs = &html[at..at + tag_end]; -+ from = at + tag_end; -+ if is_tiny(attrs) { -+ continue; -+ } -+ if let Some(src) = attribute_ci(attrs, "src").map(|u| decode_entities(&u)) { -+ if is_http_url(&src) { -+ return Some(src); -+ } -+ } -+ } -+ None -+} -+ -+/// Under this many pixels on a side an image is an icon, not a picture. -+const MIN_IMAGE_PX: f64 = 50.0; -+ -+/// Whether an ``'s `width` or `height` attribute says it is an icon. -+fn is_tiny(attrs: &str) -> bool { -+ ["width", "height"].iter().any(|key| { -+ attribute_ci(attrs, key) -+ .and_then(|v| v.trim().trim_end_matches("px").trim().parse::().ok()) -+ .is_some_and(|px| px < MIN_IMAGE_PX) -+ }) -+} -+ -+/// `attribute` for HTML: the name in any case, the value quoted or bare -+/// (ending at whitespace). -+fn attribute_ci(attrs: &str, name: &str) -> Option { -+ let lower = attrs.to_ascii_lowercase(); -+ let key = format!("{}=", name.to_ascii_lowercase()); -+ let mut from = 0; -+ while let Some(pos) = lower[from..].find(&key) { -+ let at = from + pos; -+ let before_ok = at == 0 || lower[..at].ends_with(|c: char| c.is_whitespace()); -+ from = at + key.len(); -+ if !before_ok { -+ continue; -+ } -+ let rest = &attrs[at + key.len()..]; -+ let value = match rest.chars().next()? { -+ quote @ ('"' | '\'') => { -+ let value = &rest[1..]; -+ &value[..value.find(quote)?] -+ } -+ _ => rest.split(|c: char| c.is_whitespace()).next().unwrap_or(""), -+ }; -+ return Some(value.to_string()); -+ } -+ None -+} -+ -+/// `Outlet: headline …` in a description whose remainder opens with the -+/// item's own title: the outlet, and what is left after the headline -+/// (empty when the description was the headline alone). None when the -+/// description does not have that shape. -+fn outlet_prefix(title: &str, summary: &str) -> Option<(String, String)> { -+ // The colon alone: a stripped `
` may have left no space after it. -+ let (outlet, rest) = summary.split_once(':')?; -+ let outlet = outlet.trim(); -+ let rest = rest.trim_start(); -+ if !looks_like_name(outlet) { -+ return None; -+ } -+ let title = title.trim().trim_end_matches('…').trim(); -+ let head: String = title.chars().take(TITLE_MATCH_CHARS).collect(); -+ if head.is_empty() || !rest.to_lowercase().starts_with(&head.to_lowercase()) { -+ return None; -+ } -+ let rest = rest.trim(); -+ // What follows the headline, compared character by character (a -+ // lowercase form can differ in length from its original, so byte -+ // offsets of one never index the other). -+ let remainder = strip_prefix_ci(rest, title).unwrap_or("").trim(); -+ let remainder = remainder.trim_start_matches(['—', '-', '·', ':', ',']).trim(); -+ Some((outlet.to_string(), remainder.to_string())) -+} -+ -+/// `text` after `prefix`, matched case-insensitively character by -+/// character; None when `text` does not start with it. -+fn strip_prefix_ci<'a>(text: &'a str, prefix: &str) -> Option<&'a str> { -+ let mut rest = text; -+ for want in prefix.chars() { -+ let got = rest.chars().next()?; -+ if !got.to_lowercase().eq(want.to_lowercase()) { -+ return None; -+ } -+ rest = &rest[got.len_utf8()..]; -+ } -+ Some(rest) -+} -+ -+/// A digest title's trailing ` (Author/Outlet)` or ` (Outlet)`: the title -+/// without it, and the outlet (the last `/`-separated part). None when the -+/// title does not end that way, or what is in the parentheses is too long -+/// to be a name. -+fn trailing_outlet(title: &str) -> Option<(String, String)> { -+ let title = title.trim(); -+ let inner_end = title.strip_suffix(')')?; -+ let open = inner_end.rfind('(')?; -+ let inner = inner_end[open + 1..].trim(); -+ let head = inner_end[..open].trim_end(); -+ if inner.is_empty() || head.is_empty() || inner.chars().count() > OUTLET_CHARS || inner.contains('(') { -+ return None; -+ } -+ let outlet = inner.rsplit('/').next().unwrap_or(inner).trim(); -+ if outlet.is_empty() { -+ return None; -+ } -+ Some((head.to_string(), outlet.to_string())) -+} -+ -+/// Whether `text` reads as an outlet's name rather than the start of a -+/// sentence: a few words, each capitalised or a number (`Financial Times`, -+/// `9to5Mac`, `The Verge`), connectors aside; no sentence punctuation. -+fn looks_like_name(text: &str) -> bool { -+ let words: Vec<&str> = text.split_whitespace().collect(); -+ if words.is_empty() || text.chars().count() > OUTLET_CHARS || text.contains(['.', '!', '?']) { -+ return false; -+ } -+ words.iter().all(|word| { -+ matches!(*word, "of" | "the" | "and" | "de" | "for" | "&") -+ || word.chars().next().is_some_and(|c| c.is_uppercase() || c.is_ascii_digit()) -+ }) -+} -+ -+/// An outlet's name is a few words: anything longer is a sentence with a -+/// colon in it. -+const OUTLET_CHARS: usize = 40; -+/// How much of the headline the description must open with. -+const TITLE_MATCH_CHARS: usize = 24; -+ -+/// Google News titles end in ` - Source`; the last dash is the separator. -+fn split_google_source(title: &str) -> Option<(String, String)> { -+ let (head, tail) = title.rsplit_once(" - ")?; -+ let (head, tail) = (head.trim(), tail.trim()); -+ (!head.is_empty() && !tail.is_empty()).then(|| (head.to_string(), tail.to_string())) -+} -+ -+/// The first `...` in `xml` as its attribute text and raw -+/// inner text. A tag name must end at `>`, whitespace or `/`, so `title` -+/// never matches `titleX`. A self-closing element has empty inner text. -+fn element<'a>(xml: &'a str, tag: &str) -> Option<(&'a str, &'a str)> { -+ let open = format!("<{tag}"); -+ let close = format!(""); -+ let mut from = 0; -+ while let Some(pos) = xml[from..].find(&open) { -+ let at = from + pos; -+ let after = &xml[at + open.len()..]; -+ let next = after.chars().next()?; -+ if next != '>' && next != '/' && !next.is_whitespace() { -+ from = at + 1; -+ continue; -+ } -+ let tag_end = after.find('>')?; -+ let attrs = after[..tag_end].trim_end(); -+ if let Some(attrs) = attrs.strip_suffix('/') { -+ return Some((attrs, "")); -+ } -+ let content = &after[tag_end + 1..]; -+ let end = content.find(&close)?; -+ return Some((attrs, &content[..end])); -+ } -+ None -+} -+ -+/// The raw inner text of the first `` in `xml`. -+fn element_text<'a>(xml: &'a str, tag: &str) -> Option<&'a str> { -+ element(xml, tag).map(|(_, text)| text) -+} -+ -+/// An RSS item's `` when it is an http(s) URL, else its `` when -+/// that is one and not marked `isPermaLink="false"`. -+fn rss_link(body: &str) -> Option { -+ element_text(body, "link") -+ .map(clean_text) -+ .filter(|l| is_http_url(l)) -+ .or_else(|| permalink_guid(body)) -+} -+ -+fn permalink_guid(body: &str) -> Option { -+ let (attrs, text) = element(body, "guid")?; -+ if attribute(attrs, "isPermaLink").is_some_and(|v| v.eq_ignore_ascii_case("false")) { -+ return None; -+ } -+ Some(clean_text(text)).filter(|g| is_http_url(g)) -+} -+ -+/// Atom links are ``; the article is the one -+/// without `rel` or with `rel="alternate"`. -+fn atom_link(xml: &str) -> Option { -+ let mut from = 0; -+ while let Some(pos) = xml[from..].find("')?; -+ let attrs = &after[..tag_end]; -+ // Past the whole tag: restarting inside it would rescan to its `>` -+ // once per ` Option { -+ let key = format!("{name}="); -+ let mut from = 0; -+ while let Some(pos) = attrs[from..].find(&key) { -+ let at = from + pos; -+ let before_ok = at == 0 || attrs[..at].ends_with(|c: char| c.is_whitespace()); -+ let rest = &attrs[at + key.len()..]; -+ from = at + key.len(); -+ if !before_ok { -+ continue; -+ } -+ let quote = rest.chars().next()?; -+ if quote != '"' && quote != '\'' { -+ continue; -+ } -+ let value = &rest[1..]; -+ let end = value.find(quote)?; -+ return Some(value[..end].to_string()); -+ } -+ None -+} -+ -+/// CDATA markers dropped (they can sit anywhere in a description), entities -+/// decoded, HTML tags stripped, entities decoded again (feeds double-encode -+/// HTML in descriptions), whitespace collapsed. -+pub(crate) fn clean_text(raw: &str) -> String { -+ let text = raw.replace("", ""); -+ let text = decode_entities(text.trim()); -+ let text = strip_tags(&text); -+ let text = decode_entities(&text); -+ text.split_whitespace().collect::>().join(" ") -+} -+ -+/// What an angle-bracketed run turns into once stripped. -+enum TagKind { -+ /// Wraps words inside a sentence: dropped, so `Example:` keeps -+ /// its colon attached. -+ Inline, -+ /// Separates words or lines: a space, so `line
break` never fuses. -+ Block, -+ /// Not an HTML tag at all: a title may well say `<3` or `x < y`. -+ Text, -+} -+ -+const INLINE_TAGS: &[&str] = &[ -+ "a", "abbr", "b", "big", "cite", "code", "del", "em", "font", "i", "ins", "mark", "q", "s", "small", "span", -+ "strike", "strong", "sub", "sup", "tt", "u", -+]; -+ -+const BLOCK_TAGS: &[&str] = &[ -+ "article", "aside", "audio", "blockquote", "br", "center", "dd", "div", "dl", "dt", "figcaption", "figure", -+ "footer", "h1", "h2", "h3", "h4", "h5", "h6", "header", "hr", "iframe", "img", "li", "nav", "ol", "p", "picture", -+ "pre", "section", "source", "table", "tbody", "td", "th", "thead", "tr", "ul", "video", -+]; -+ -+fn tag_kind(tag: &str) -> TagKind { -+ // Comments and processing instructions are markup too. -+ if tag.starts_with('!') || tag.starts_with('?') { -+ return TagKind::Block; -+ } -+ let name = tag -+ .trim_start_matches('/') -+ .split(|c: char| c.is_whitespace() || c == '/') -+ .next() -+ .unwrap_or("") -+ .to_ascii_lowercase(); -+ if INLINE_TAGS.contains(&name.as_str()) { -+ TagKind::Inline -+ } else if BLOCK_TAGS.contains(&name.as_str()) { -+ TagKind::Block -+ } else { -+ TagKind::Text -+ } -+} -+ -+/// Remove known HTML tags (see `TagKind`); an unclosed `<` is text. -+fn strip_tags(text: &str) -> String { -+ let mut out = String::with_capacity(text.len()); -+ let mut rest = text; -+ while let Some(open) = rest.find('<') { -+ out.push_str(&rest[..open]); -+ let after = &rest[open..]; -+ let Some(close) = after.find('>') else { -+ rest = after; -+ break; -+ }; -+ match tag_kind(&after[1..close]) { -+ TagKind::Inline => {} -+ TagKind::Block => out.push(' '), -+ TagKind::Text => out.push_str(&after[..=close]), -+ } -+ rest = &after[close + 1..]; -+ } -+ out.push_str(rest); -+ out -+} -+ -+/// The scan for an entity's `;` looks at `&` and the ten bytes after it, -+/// so an entity is at most nine characters between `&` and `;`; the -+/// longest wanted, `􏿿`, has eight. -+const MAX_ENTITY_LEN: usize = 10; -+ -+/// The named entities feeds actually use, plus numeric ones. A numeric -+/// entity for a control character (other than tab, newline and carriage -+/// return) stays as written, so no feed can smuggle one into a label. -+pub(crate) fn decode_entities(text: &str) -> String { -+ let mut out = String::with_capacity(text.len()); -+ let mut rest = text; -+ while let Some(pos) = rest.find('&') { -+ out.push_str(&rest[..pos]); -+ let after = &rest[pos..]; -+ // Only look as far as an entity can reach, so a wall of `&` stays linear. -+ let Some(end) = after.bytes().take(MAX_ENTITY_LEN + 1).position(|b| b == b';') else { -+ out.push('&'); -+ rest = &after[1..]; -+ continue; -+ }; -+ let entity = &after[1..end]; -+ let decoded = match entity { -+ "amp" => Some('&'), -+ "lt" => Some('<'), -+ "gt" => Some('>'), -+ "quot" => Some('"'), -+ "apos" => Some('\''), -+ "nbsp" => Some(' '), -+ "mdash" => Some('—'), -+ "ndash" => Some('–'), -+ "hellip" => Some('…'), -+ "lsquo" => Some('‘'), -+ "rsquo" => Some('’'), -+ "ldquo" => Some('“'), -+ "rdquo" => Some('”'), -+ _ => numeric_entity(entity), -+ }; -+ match decoded { -+ Some(c) => { -+ out.push(c); -+ rest = &after[end + 1..]; -+ } -+ None => { -+ out.push('&'); -+ rest = &after[1..]; -+ } -+ } -+ } -+ out.push_str(rest); -+ out -+} -+ -+/// `#65` or `#x41`, digits only (no sign), and never a control character -+/// other than tab, newline or carriage return. -+fn numeric_entity(entity: &str) -> Option { -+ let number = entity.strip_prefix('#')?; -+ let code = match number.strip_prefix(['x', 'X']) { -+ Some(hex) if !hex.is_empty() && hex.bytes().all(|b| b.is_ascii_hexdigit()) => u32::from_str_radix(hex, 16).ok()?, -+ Some(_) => return None, -+ None if !number.is_empty() && number.bytes().all(|b| b.is_ascii_digit()) => number.parse().ok()?, -+ None => return None, -+ }; -+ char::from_u32(code).filter(|c| !c.is_control() || matches!(c, '\n' | '\t' | '\r')) -+} -+ -+/// At most `max` characters: cut to `max - 1`, a trailing space dropped, an -+/// ellipsis appended. -+pub(crate) fn truncate(text: &str, max: usize) -> String { -+ if text.chars().count() <= max { -+ return text.to_string(); -+ } -+ let cut: String = text.chars().take(max.saturating_sub(1)).collect(); -+ format!("{}…", cut.trim_end()) -+} -+ -+const MONTHS: [&str; 12] = ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"]; -+ -+/// `Wed, 16 Sep 2026 08:15:01 -0400`, the weekday and seconds optional, -+/// the zone `GMT`/`UT`/`UTC`/`Z`, `±HHMM`, or a US abbreviation. -+pub(crate) fn parse_rfc822(text: &str) -> Option { -+ let mut parts: Vec<&str> = text.split_whitespace().collect(); -+ if parts.first().is_some_and(|p| p.ends_with(',')) { -+ parts.remove(0); -+ } -+ if parts.len() < 4 { -+ return None; -+ } -+ let day: i64 = parts[0].parse().ok()?; -+ let month_name = parts[1].to_lowercase(); -+ let month = MONTHS.iter().position(|m| month_name.starts_with(m))? as i64 + 1; -+ let year: i64 = parts[2].parse().ok()?; -+ let year = if year < 100 { year + 2000 } else { year }; -+ let clock = hms(parts[3])?; -+ let offset = parts.get(4).map(|z| zone_offset(z)).unwrap_or(0); -+ unix_seconds(year, month, day, clock, offset) -+} -+ -+/// `2026-09-16T08:30:00+02:00`, `...Z`, fractional seconds allowed, a -+/// missing zone read as UTC. -+pub(crate) fn parse_rfc3339(text: &str) -> Option { -+ let (date, time) = text.split_once(['T', 't', ' '])?; -+ let mut d = date.split('-'); -+ let year: i64 = d.next()?.parse().ok()?; -+ let month: i64 = d.next()?.parse().ok()?; -+ let day: i64 = d.next()?.parse().ok()?; -+ let zone_at = time.find(['Z', 'z', '+']).or_else(|| time.rfind('-')).unwrap_or(time.len()); -+ let (clock, zone) = time.split_at(zone_at); -+ let clock = hms(clock.split('.').next()?)?; -+ unix_seconds(year, month, day, clock, zone_offset(zone)) -+} -+ -+/// Unix seconds for a civil date and clock in a zone `offset` seconds east -+/// of UTC; None when a date field is out of range. A day past its month's -+/// end is let through, as a feed's own clock would have it. -+fn unix_seconds(year: i64, month: i64, day: i64, (h, m, s): (i64, i64, i64), offset: i64) -> Option { -+ if !(1..=9999).contains(&year) || !(1..=12).contains(&month) || !(1..=31).contains(&day) { -+ return None; -+ } -+ Some(days_from_civil(year, month, day) * 86_400 + h * 3600 + m * 60 + s - offset) -+} -+ -+/// `HH:MM` or `HH:MM:SS`, each field in range (a leap second allowed). -+fn hms(text: &str) -> Option<(i64, i64, i64)> { -+ let mut p = text.split(':'); -+ let h: i64 = p.next()?.parse().ok()?; -+ let m: i64 = p.next()?.parse().ok()?; -+ let s: i64 = match p.next() { -+ Some(s) => s.parse().ok()?, -+ None => 0, -+ }; -+ ((0..=23).contains(&h) && (0..=59).contains(&m) && (0..=60).contains(&s)).then_some((h, m, s)) -+} -+ -+/// Seconds east of UTC for a zone token. Zone names other than the UTC -+/// spellings and the US abbreviations (CET, BST, AEST, ...) are read as -+/// UTC: a time off by a few hours beats no time at all. -+fn zone_offset(zone: &str) -> i64 { -+ match zone.to_uppercase().as_str() { -+ "GMT" | "UT" | "UTC" | "Z" | "" => 0, -+ "EST" => -5 * 3600, -+ "EDT" => -4 * 3600, -+ "CST" => -6 * 3600, -+ "CDT" => -5 * 3600, -+ "MST" => -7 * 3600, -+ "MDT" => -6 * 3600, -+ "PST" => -8 * 3600, -+ "PDT" => -7 * 3600, -+ z => { -+ let sign = if z.starts_with('-') { -1 } else { 1 }; -+ let digits: String = z.chars().filter(|c| c.is_ascii_digit()).collect(); -+ if digits.len() < 4 { -+ return 0; -+ } -+ let hh: i64 = digits[..2].parse().unwrap_or(0); -+ let mm: i64 = digits[2..4].parse().unwrap_or(0); -+ sign * (hh * 3600 + mm * 60) -+ } -+ } -+} -+ -+/// Days since 1970-01-01 for a proleptic Gregorian date. -+pub(crate) fn days_from_civil(y: i64, m: i64, d: i64) -> i64 { -+ let y = if m <= 2 { y - 1 } else { y }; -+ let era = (if y >= 0 { y } else { y - 399 }) / 400; -+ let yoe = y - era * 400; -+ let mp = if m > 2 { m - 3 } else { m + 9 }; -+ let doy = (153 * mp + 2) / 5 + d - 1; -+ let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; -+ era * 146_097 + doe - 719_468 -+} -+ -+ -+#[cfg(test)] -+mod tests { -+ use super::*; -+ -+ const RSS: &str = r#"T -+ <![CDATA[Rust 2.0 & you]]>https://example.com/a -+ <p>Big <b>news</b></p> -+ Tue, 10 Jun 2025 12:00:00 GMT -+ -+ Second - The Outlethttps://example.com/b -+ https://example.com/c -+ "#; -+ -+ const ATOM: &str = r#"Atom post -+ 2025-06-10T12:00:00Z -+ Short"#; -+ -+ #[test] -+ fn rss_items_become_rows_with_cleaned_text_times_and_https_images() { -+ let rows = parse_styled(RSS, Style::default()).unwrap(); -+ assert_eq!(rows.len(), 2, "an untitled item is skipped"); -+ assert_eq!(rows[0].title, "Rust 2.0 & you"); -+ assert_eq!(rows[0].summary, "Big news"); -+ assert_eq!(rows[0].published, Some(1_749_556_800)); -+ assert_eq!(rows[0].image.as_deref(), Some("https://img.example.com/a.jpg")); -+ assert_eq!(rows[1].title, "Second - The Outlet", "plain style keeps the title whole"); -+ } -+ -+ #[test] -+ fn google_style_takes_the_outlet_off_the_title() { -+ let rows = parse_styled(RSS, Style { split_source: true, digest: false }).unwrap(); -+ assert_eq!(rows[1].title, "Second"); -+ assert_eq!(rows[1].source, "The Outlet"); -+ } -+ -+ #[test] -+ fn atom_entries_parse_and_non_feeds_are_refused() { -+ let rows = parse_styled(ATOM, Style::default()).unwrap(); -+ assert_eq!(rows[0].link, "https://example.org/p"); -+ assert_eq!(rows[0].published, Some(1_749_556_800)); -+ assert!(parse_styled("nope", Style::default()).is_err()); -+ } -+ -+ #[test] -+ fn rows_serialize_to_json_the_script_reader_accepts() { -+ let rows = parse_styled(RSS, Style::default()).unwrap(); -+ let json = rows_json(&rows); -+ assert!(json.starts_with("[{\"title\":\"Rust 2.0 & you\""), "{json}"); -+ let quoted = rows_json(&[Headline { title: "a \"q\"\n".into(), ..Default::default() }]); -+ assert!(quoted.contains("\"a \\\"q\\\"\\n\""), "{quoted}"); -+ } -+} -diff --git a/platform/script/src/vm.rs b/platform/script/src/vm.rs -index c19d2512c..c579c89c3 100644 ---- a/platform/script/src/vm.rs -+++ b/platform/script/src/vm.rs -@@ -3,6 +3,7 @@ - use crate::makepad_error_log::*; - use crate::makepad_live_id::*; - use crate::mod_gc::*; -+use crate::mod_feed::*; - use crate::mod_html::*; - use crate::mod_math::*; - use crate::mod_pod::*; -@@ -1787,6 +1788,7 @@ pub fn new() -> Self { - define_std_module(&mut heap, &mut native); - define_regex_module(&mut heap, &mut native); - define_html_module(&mut heap, &mut native); -+ define_feed_module(&mut heap, &mut native); - define_shader_module(&mut heap, &mut native); - define_gc_module(&mut heap, &mut native); - let pod_builtins = define_pod_module(&mut heap, &mut native); -diff --git a/platform/script/std/src/gate.rs b/platform/script/std/src/gate.rs -index 9625a12cc..f74915c23 100644 ---- a/platform/script/std/src/gate.rs -+++ b/platform/script/std/src/gate.rs -@@ -1,10 +1,11 @@ - //! The one question every way out of an isolate asks: may THIS heap reach - //! THIS URL? - //! --//! Three paths issue requests on a script's behalf — `net.http_request`, --//! `res.http_resource` (artwork) and the data fetches behind `sys.*` — and --//! a network grant that covers only the first is not a grant. All three ask --//! here. The answer comes from a gate the embedding host installs; with no -+//! Four paths issue requests on a script's behalf — `net.http_request`, -+//! `net.web_socket`, `res.http_resource` (artwork) and the data fetches -+//! behind `sys.*` — and a network grant that covers only the first is not a -+//! grant. All of them ask here. Listening servers (`net.http_server`) and raw -+//! streams (`net.socket_stream`) name no URL, so they ask the socket gate. The answer comes from a gate the embedding host installs; with no - //! gate installed, everything is allowed, which is the behaviour every - //! existing host had. A host that installs a gate answers per heap, so an - //! app's allowlist follows its isolate and nothing else. -@@ -16,6 +17,45 @@ - - thread_local! { - static URL_GATE: Cell> = const { Cell::new(None) }; -+ static MEDIA_GATE: Cell> = const { Cell::new(None) }; -+ static SOCKET_GATE: Cell> = const { Cell::new(None) }; -+} -+ -+/// `heap_key -> allowed`: may this heap open what a host list cannot -+/// describe — a listening server (`net.http_server`) or a raw TCP/TLS stream -+/// (`net.socket_stream`)? A server answers whoever connects, and a raw -+/// stream speaks any protocol to any port, so neither fits a per-URL gate. -+pub type ScriptSocketGate = fn(usize) -> bool; -+ -+/// Install (or clear) the gate for listening servers and raw sockets. With -+/// no socket gate, both are allowed, as they were for every existing host. -+pub fn set_script_socket_gate(gate: Option) { -+ SOCKET_GATE.with(|g| g.set(gate)); -+} -+ -+/// Whether `heap_key` may listen or open a raw socket. True when no gate is -+/// installed. -+pub fn script_sockets_allowed(heap_key: usize) -> bool { -+ SOCKET_GATE.with(|g| match g.get() { -+ Some(gate) => gate(heap_key), -+ None => true, -+ }) -+} -+ -+/// Install (or clear) the gate for media loads (`http_resource`: images and -+/// other artwork). A host can grant an app artwork from anywhere without -+/// granting it requests to anywhere. With no media gate, media answers to -+/// the URL gate like every other path. -+pub fn set_script_media_gate(gate: Option) { -+ MEDIA_GATE.with(|g| g.set(gate)); -+} -+ -+/// Whether `heap_key` may load media from `url`. -+pub fn script_media_url_allowed(heap_key: usize, url: &str) -> bool { -+ match MEDIA_GATE.with(|g| g.get()) { -+ Some(gate) => gate(heap_key, url), -+ None => script_url_allowed(heap_key, url), -+ } - } - - /// Install (or clear) the gate for this thread. Isolates are UI-thread-owned, -@@ -102,4 +142,34 @@ fn only_heap_seven(heap: usize, _url: &str) -> bool { - assert!(!script_url_allowed(8, "https://anything.example")); - set_script_url_gate(None); - } -+ -+ #[test] -+ fn media_falls_back_to_the_url_gate_until_it_has_its_own() { -+ fn only_heap_seven(heap: usize, _url: &str) -> bool { -+ heap == 7 -+ } -+ fn every_heap(_heap: usize, _url: &str) -> bool { -+ true -+ } -+ set_script_url_gate(Some(only_heap_seven)); -+ assert!(!script_media_url_allowed(8, "https://cdn.example/a.jpg")); -+ set_script_media_gate(Some(every_heap)); -+ assert!(script_media_url_allowed(8, "https://cdn.example/a.jpg")); -+ assert!(!script_url_allowed(8, "https://cdn.example/a.jpg"), "requests keep their own gate"); -+ set_script_media_gate(None); -+ set_script_url_gate(None); -+ } -+ -+ #[test] -+ fn no_socket_gate_allows_sockets_and_a_gate_decides() { -+ set_script_socket_gate(None); -+ assert!(script_sockets_allowed(8)); -+ fn only_heap_seven(heap: usize) -> bool { -+ heap == 7 -+ } -+ set_script_socket_gate(Some(only_heap_seven)); -+ assert!(script_sockets_allowed(7)); -+ assert!(!script_sockets_allowed(8)); -+ set_script_socket_gate(None); -+ } - } -diff --git a/platform/script/std/src/net.rs b/platform/script/std/src/net.rs -index ba16994ca..7814d6f8e 100644 ---- a/platform/script/std/src/net.rs -+++ b/platform/script/std/src/net.rs -@@ -14,6 +14,11 @@ - use std::sync::mpsc::channel; - use std::time::Duration; - -+ -+/// The response body cap for a script request that names none: an isolate -+/// may not buffer an unbounded body, and 16 MiB is past any page or feed. -+pub const SCRIPT_DEFAULT_BODY_LIMIT: u64 = 16 * 1024 * 1024; -+ - pub struct ScriptWebSocket { - #[allow(unused)] - pub id: LiveId, -@@ -786,7 +791,19 @@ pub fn script_mod(vm: &mut ScriptVm) { - return script_err_type_mismatch!(vm.trap(), "invalid net arg type"); - } - -+ // A server answers whoever connects; no host list describes that. -+ let heap_key = vm.bx.heap.heap_key(); -+ if !crate::gate::script_sockets_allowed(heap_key) { -+ return script_err_io!(vm.trap(), "this app may not open a listening server"); -+ } -+ if vm.std_mut::().net.is_none() { -+ return script_err_io!(vm.trap(), "script net runtime is not configured"); -+ } -+ - let options = HttpServerOptions::script_from_value(vm, options); -+ let Ok(listen_address) = options.listen.parse() else { -+ return script_err_invalid_args!(vm.trap(), "invalid listen address {}", options.listen); -+ }; - let events = HttpServerEvents::script_from_value(vm, events); - - let (server_tx, server_rx) = channel(); -@@ -800,7 +817,7 @@ pub fn script_mod(vm: &mut ScriptVm) { - }); - - let server = HttpServer { -- listen_address: options.listen.parse().unwrap(), -+ listen_address, - post_max_size: 1024 * 1024 * 10, - post_max_size_overrides: Vec::new(), - pre_admit_posts: false, -@@ -838,7 +855,12 @@ pub fn script_mod(vm: &mut ScriptVm) { - { - return script_err_type_mismatch!(vm.trap(), "invalid net arg type"); - } -- let request = HttpRequest::script_from_value(vm, request); -+ let mut request = HttpRequest::script_from_value(vm, request); -+ // A script that never names a limit reads back 0, which refused -+ // every body with "response body exceeds configured limit". -+ if request.max_response_body_bytes == 0 { -+ request.max_response_body_bytes = SCRIPT_DEFAULT_BODY_LIMIT; -+ } - let events = HttpEvents::script_from_value(vm, events); - // The host's per-isolate allowlist, when one is installed: a - // network grant that only opened this module would otherwise -@@ -893,12 +915,12 @@ pub fn script_mod(vm: &mut ScriptVm) { - } - if prop == id!(error) { - if let Some(error) = error { -- return vm.new_string_with(|_vm, out| out.push_str(&error)).into(); -+ return vm.bx.heap.new_string_from_str(&error).into(); - } - return NIL; - } - if prop == id!(host) { -- return vm.new_string_with(|_vm, out| out.push_str(&host)).into(); -+ return vm.bx.heap.new_string_from_str(&host).into(); - } - script_err_not_found!(vm.trap(), "invalid socket_stream prop") - }); -@@ -1071,7 +1093,7 @@ pub fn script_mod(vm: &mut ScriptVm) { - match socket_stream_poll(vm, handle) { - SocketStreamPoll::Data(data) => { - let string = String::from_utf8_lossy(&data); -- vm.new_string_with(|_vm, out| out.push_str(&string)).into() -+ vm.bx.heap.new_string_from_str(&string).into() - } - SocketStreamPoll::Closed(Some(err)) => script_err_io!(vm.trap(), "{err}"), - SocketStreamPoll::Closed(None) => NIL, -@@ -1116,6 +1138,12 @@ pub fn script_mod(vm: &mut ScriptVm) { - return script_err_type_mismatch!(vm.trap(), "invalid net arg type"); - } - let events = WebSocketEvents::script_from_value(vm, events); -+ // The same per-isolate allowlist as `http_request`: a socket is -+ // a request that stays open. -+ let heap_key = vm.bx.heap.heap_key(); -+ if !crate::gate::script_url_allowed(heap_key, &request.url) { -+ return script_err_io!(vm.trap(), "this app may not reach {}", request.url); -+ } - - let std = vm.std_mut::(); - let Some(runtime) = std.net.as_ref() else { -@@ -1143,6 +1171,12 @@ pub fn script_mod(vm: &mut ScriptVm) { - if !script_has_proto!(vm, options, net.SocketStreamOptions) { - return script_err_type_mismatch!(vm.trap(), "invalid socket_stream arg type"); - } -+ // A raw stream speaks any protocol to any port, so a host list -+ // cannot bound it: a policed isolate is refused outright. -+ let heap_key = vm.bx.heap.heap_key(); -+ if !crate::gate::script_sockets_allowed(heap_key) { -+ return script_err_io!(vm.trap(), "this app may not open a raw socket"); -+ } - // Raw sockets obey the same gate as http_request/web_socket, so a sandboxed - // VM without a net runtime can't open TCP connections. - if vm.std_mut::().net.is_none() { -diff --git a/platform/src/event/finger.rs b/platform/src/event/finger.rs -index 0fb1eabaf..bc7c319cd 100644 ---- a/platform/src/event/finger.rs -+++ b/platform/src/event/finger.rs -@@ -493,6 +493,13 @@ pub fn is_digit_captured_elsewhere(&self, digit_id: DigitId, area: Area) -> bool - self.captures.iter().any(|v| v.digit_id == digit_id && v.area != area) - } - -+ /// Every area that captured this digit. A container that only wants to -+ /// yield to its own children (a Button inside it), not to the scroll view -+ /// around it, compares these rects with its own. -+ pub fn digit_capture_areas(&self, digit_id: DigitId) -> Vec { -+ self.captures.iter().filter(|v| v.digit_id == digit_id).map(|v| v.area).collect() -+ } -+ - /// The area that captured the touch with the given uid, if any. - /// Lets a raw `Event::LongPress` handler check which widget owns the press. - pub fn touch_capture_area(&self, uid: u64) -> Option { -diff --git a/platform/src/script/res.rs b/platform/src/script/res.rs -index f94c296b2..42b9f4cfe 100644 ---- a/platform/src/script/res.rs -+++ b/platform/src/script/res.rs -@@ -1189,11 +1189,9 @@ pub fn script_mod(vm: &mut ScriptVm) { - _ if prop == id!(path) => { - let path = res.abs_path.clone(); - drop(resources); -- return vm -- .new_string_with(|_vm, s| { -- s.push_str(&path); -- }) -- .into(); -+ // Charged like any string: an isolate under a heap -+ // cap refuses the unbounded builder outright. -+ return vm.bx.heap.new_string_from_str(&path).into(); - } - _ if prop == id!(is_loaded) => { - return matches!(res.data, CxScriptResourceData::Loaded(_)).into() -@@ -1205,11 +1203,7 @@ pub fn script_mod(vm: &mut ScriptVm) { - if let CxScriptResourceData::Error(ref e) = res.data { - let err = e.clone(); - drop(resources); -- return vm -- .new_string_with(|_vm, s| { -- s.push_str(&err); -- }) -- .into(); -+ return vm.bx.heap.new_string_from_str(&err).into(); - } - return NIL; - } -@@ -1340,7 +1334,7 @@ pub fn script_mod(vm: &mut ScriptVm) { - let heap_key = vm.bx.heap.heap_key(); - // Artwork is a way out of the isolate too: a card with no - // network grant was seen fetching nine images through here. -- if !makepad_script_std::script_url_allowed(heap_key, &url_string) { -+ if !makepad_script_std::script_media_url_allowed(heap_key, &url_string) { - // Logged as well as raised: the widget that asked draws - // nothing, and a silent blank is the hardest bug to find. - crate::log!("Script resource refused by the host's allowlist: {url_string}"); -diff --git a/widgets/src/camera_preview.rs b/widgets/src/camera_preview.rs -new file mode 100644 -index 000000000..72bccb9fb ---- /dev/null -+++ b/widgets/src/camera_preview.rs -@@ -0,0 +1,637 @@ -+//! The device camera inside a script app: a live preview drawn natively, and -+//! capture and control from script. -+//! -+//! ```text -+//! cam := CameraPreview{width: Fill height: Fill facing: "back" -+//! on_capture: || show(ui.cam.last()) // app-relative path of the new file -+//! on_error: || say(ui.cam.error())} -+//! ui.cam.start() ui.cam.stop() ui.cam.switch() ui.cam.facing() -+//! ui.cam.capture() // a JPEG into the app's storage -+//! ui.cam.record_start() ui.cam.record_stop() // an MP4 -+//! ui.cam.set_zoom(2.0) ui.cam.set_flash("off"|"on"|"auto"|"torch") -+//! ui.cam.set_exposure(-1.0) ui.cam.focus(x, y) // x, y 0..1 in the preview -+//! ui.cam.set_aspect(16 / 9) // the viewfinder shape; reopens the camera -+//! ``` -+//! -+//! A tap on the preview focuses there and a pinch zooms, natively -+//! (`tap_to_focus`, `pinch_zoom`, both on by default); `on_zoom` hears the -+//! new ratio once a pinch ends. -+//! -+//! Frames never pass through script: the preview is a platform texture, and -+//! script hears one call per finished capture. What an app under a policy -+//! gets: `camera` to open it at all; captures land in its own storage jail; -+//! `microphone` adds sound to a recording; `library` also offers each capture -+//! to the system photo library. -+//! -+//! The camera is a device the person can see is on, so it is released as soon -+//! as it is not needed: when the preview has not drawn for a second (its tile -+//! left the screen), when the app closes ([`release_isolate_devices`], which -+//! the host calls), and when its isolate is collected. -+use crate::{ -+ makepad_derive_widget::*, -+ makepad_draw::*, -+ makepad_script::ScriptFnRef, -+ video::*, -+ view::View, -+ widget::*, -+ widget_async::{CxWidgetToScriptCallExt, ScriptAsyncResult}, -+}; -+use crate::makepad_draw::makepad_platform::permission::{Permission, PermissionStatus}; -+use crate::makepad_draw::makepad_platform::video::{ -+ CameraCaptureEvent, CameraCaptureRequest, CameraCaptureResult, CameraControl, CameraFlashMode, VideoFormat, -+ VideoFormatId, VideoInputId, VideoInputsEvent, VideoPixelFormat, -+}; -+use crate::gesture_view::PinchTracker; -+use std::collections::HashMap; -+ -+script_mod! { -+ use mod.prelude.widgets_internal.* -+ use mod.widgets.* -+ -+ mod.widgets.CameraPreviewBase = #(CameraPreview::register_widget(vm)) -+ mod.widgets.CameraPreview = set_type_default() do mod.widgets.CameraPreviewBase{ -+ width: Fill -+ height: Fill -+ flow: Overlay -+ preview := Video{width: Fill height: Fill autoplay: false show_controls: false} -+ } -+} -+ -+const WATCHDOG_SECONDS: f64 = 0.5; -+/// How long a camera may be without a permission answer before it opens -+/// anyway: a bare desktop binary never gets the prompt's completion, and the -+/// capture backend asks for access itself. -+const PERMISSION_GRACE_SECONDS: f64 = 2.0; -+ -+thread_local! { -+ /// heap key -> the preview videos that heap's cameras opened. -+ static OPEN: std::cell::RefCell>> = Default::default(); -+} -+ -+/// Stop every camera an isolate opened. Called from the isolate GC, and by a -+/// host when the app it runs closes, which can be long before the GC. -+pub fn release_cameras(cx: &mut Cx, heap_key: usize) { -+ let videos = OPEN.with(|o| o.borrow_mut().remove(&heap_key)).unwrap_or_default(); -+ for video in videos { -+ video.stop_and_cleanup_resources(cx); -+ } -+} -+ -+/// Everything a closing app holds that the person can see: its cameras and -+/// its web views. A host calls this when it closes an app. -+pub fn release_isolate_devices(cx: &mut Cx, heap_key: usize) { -+ release_cameras(cx, heap_key); -+ crate::web_reader::gc_web_readers(cx, &[heap_key]); -+} -+ -+#[derive(Clone, Debug)] -+struct CameraChoice { -+ input_id: VideoInputId, -+ front: bool, -+ formats: Vec, -+} -+ -+impl CameraChoice { -+ /// The best preview profile for a viewfinder aspect: nearest aspect, then -+ /// the most pixels within 1080p, then the better pixel format. -+ fn format_for(&self, aspect: f64) -> Option<(VideoFormatId, usize, usize)> { -+ let usable = |f: &&VideoFormat| { -+ matches!(f.pixel_format, VideoPixelFormat::NV12 | VideoPixelFormat::YUY2 | VideoPixelFormat::YUV420) -+ && f.width <= 1920 -+ && f.height <= 1080 -+ && f.height > 0 -+ }; -+ let pixel = |f: &VideoFormat| match f.pixel_format { -+ VideoPixelFormat::NV12 => 3, -+ VideoPixelFormat::YUY2 => 2, -+ VideoPixelFormat::YUV420 => 1, -+ _ => 0, -+ }; -+ let key = |f: &VideoFormat| { -+ let d = ((f.width as f64 / f.height as f64) - aspect).abs(); -+ ((d * 50.0).round() as i64, -((f.width * f.height) as i64), -pixel(f)) -+ }; -+ let best = self.formats.iter().filter(usable).min_by(|a, b| key(a).cmp(&key(b)))?; -+ Some((best.format_id, best.width, best.height)) -+ } -+} -+ -+/// Which camera a platform name describes: front when it says so, or the -+/// second of several that does not say it is the back one. -+fn is_front(name: &str, index: usize, count: usize) -> bool { -+ let name = name.to_lowercase(); -+ name.contains("front") || name.contains("facetime") || name.contains("user") || (count > 1 && index == 1 && !name.contains("back")) -+} -+ -+fn flash_mode(name: &str) -> Option { -+ match name { -+ "off" => Some(CameraFlashMode::Off), -+ "on" => Some(CameraFlashMode::On), -+ "auto" => Some(CameraFlashMode::Auto), -+ "torch" => Some(CameraFlashMode::Torch), -+ _ => None, -+ } -+} -+ -+#[derive(Clone, Copy, Debug, Default, PartialEq)] -+enum State { -+ #[default] -+ Idle, -+ Starting, -+ Running, -+ Stopping, -+} -+ -+#[derive(Script, ScriptHook, Widget)] -+pub struct CameraPreview { -+ #[deref] -+ view: View, -+ /// `"back"` or `"front"`. -+ #[live] -+ facing: String, -+ /// Width over height of the viewfinder: 4:3 for photos, 16:9 for video. -+ #[live(1.3333)] -+ aspect: f64, -+ #[live] -+ on_capture: ScriptFnRef, -+ #[live] -+ on_error: ScriptFnRef, -+ #[live] -+ on_zoom: ScriptFnRef, -+ #[live(true)] -+ tap_to_focus: bool, -+ #[live(true)] -+ pinch_zoom: bool, -+ -+ #[rust] -+ pinch: PinchTracker, -+ #[rust] -+ pinch_base: f32, -+ /// The app asked for a preview (`start()`), and has not stopped it. -+ #[rust] -+ wanted: bool, -+ #[rust] -+ state: State, -+ #[rust] -+ cameras: Vec, -+ #[rust] -+ input: Option, -+ #[rust] -+ open_front: bool, -+ #[rust] -+ open_aspect: f64, -+ #[rust] -+ permission: Option, -+ #[rust] -+ asked_at: f64, -+ #[rust] -+ timer: Option, -+ #[rust] -+ drawn: bool, -+ #[rust] -+ registered: bool, -+ #[rust] -+ recording: Option, -+ #[rust] -+ last: String, -+ #[rust] -+ last_error: String, -+ #[rust] -+ zoom: f32, -+} -+ -+impl CameraPreview { -+ fn heap_key(&self) -> usize { -+ self.view.source.heap_key() -+ } -+ -+ fn granted(&self, grant: &str) -> bool { -+ crate::splash_policy::service_allowed(self.heap_key(), grant).is_ok() -+ } -+ -+ fn fail(&mut self, cx: &mut Cx, why: String) { -+ log!("camera preview: {why}"); -+ self.last_error = why; -+ if self.on_error.as_object() != ScriptObject::ZERO { -+ cx.widget_to_script_call(self.widget_uid(), NIL, self.view.source.clone(), self.on_error.clone(), &[]); -+ } -+ } -+ -+ fn video(&self, cx: &Cx) -> VideoRef { -+ self.view.video(cx, ids!(preview)) -+ } -+ -+ fn start(&mut self, cx: &mut Cx) -> bool { -+ if !self.granted("camera.preview") { -+ self.fail(cx, "this app was not granted the camera".into()); -+ return false; -+ } -+ self.wanted = true; -+ if self.permission.is_none() { -+ self.asked_at = cx.seconds_since_app_start(); -+ cx.request_permission(Permission::Camera); -+ } -+ if self.cameras.is_empty() { -+ // The platform publishes its list once; a preview mounted later -+ // asks again. The frame callback opens the capture session. -+ cx.video_input(0, |_frame| {}); -+ cx.refresh_video_inputs(); -+ } -+ if self.timer.is_none() { -+ self.timer = Some(cx.start_interval(WATCHDOG_SECONDS)); -+ } -+ self.drawn = true; -+ self.drive(cx); -+ true -+ } -+ -+ fn stop(&mut self, cx: &mut Cx) { -+ self.wanted = false; -+ self.halt(cx); -+ if let Some(timer) = self.timer.take() { -+ cx.stop_timer(timer); -+ } -+ } -+ -+ /// Close the camera, keeping whether the app wants it: the watchdog -+ /// reopens it when the preview draws again. -+ fn halt(&mut self, cx: &mut Cx) { -+ if matches!(self.state, State::Starting | State::Running) { -+ if self.recording.take().is_some() { -+ if let Some(input) = self.input { -+ cx.camera_capture(input, CameraCaptureRequest::StopVideo); -+ } -+ } -+ self.state = State::Stopping; -+ self.video(cx).stop_and_cleanup_resources(cx); -+ } -+ } -+ -+ /// One step towards what the app wants, driven by the platform's events. -+ fn drive(&mut self, cx: &mut Cx) { -+ let want_front = self.facing == "front"; -+ match self.state { -+ State::Running if want_front != self.open_front || (self.aspect - self.open_aspect).abs() > 0.05 => { -+ if self.cameras.iter().any(|c| c.front == want_front) { -+ self.halt(cx); -+ } -+ } -+ State::Idle if self.wanted => { -+ match self.permission { -+ Some(PermissionStatus::Granted) => {} -+ Some(status) => { -+ self.wanted = false; -+ self.fail(cx, format!("camera permission {status:?}")); -+ return; -+ } -+ None if cx.seconds_since_app_start() - self.asked_at < PERMISSION_GRACE_SECONDS => return, -+ None => self.permission = Some(PermissionStatus::Granted), -+ } -+ let Some(choice) = self.cameras.iter().find(|c| c.front == want_front).or(self.cameras.first()).cloned() else { -+ return; -+ }; -+ let Some((format_id, _, _)) = choice.format_for(self.aspect) else { return }; -+ let video = self.video(cx); -+ video.set_camera_preview_mode(cx, VideoCameraPreviewMode::Texture); -+ video.set_source_camera(cx, choice.input_id, format_id); -+ video.begin_playback(cx); -+ if !self.registered { -+ self.registered = true; -+ OPEN.with(|o| o.borrow_mut().entry(self.heap_key()).or_default().push(video.clone())); -+ } -+ self.state = State::Starting; -+ self.input = Some(choice.input_id); -+ self.open_front = choice.front; -+ self.open_aspect = self.aspect; -+ } -+ _ => {} -+ } -+ } -+ -+ fn capture_path(&self, cx: &Cx, kind: &str, ext: &str) -> Option<(String, String)> { -+ let now = std::time::SystemTime::now() -+ .duration_since(std::time::UNIX_EPOCH) -+ .map(|d| d.as_millis()) -+ .unwrap_or(0); -+ let relative = format!("DCIM/{kind}_{now}.{ext}"); -+ // A policed app's captures are its own: they land in its jail. The -+ // host's own surfaces write next to the host's data. -+ let real = if crate::splash_policy::is_enforced(self.heap_key()) { -+ if !crate::splash_storage::heap_has_room(self.heap_key()) { -+ return None; -+ } -+ crate::splash_policy::local_path_for_heap(self.heap_key(), &relative)? -+ } else { -+ format!("{}/{relative}", cx.get_data_dir()?) -+ }; -+ if let Some(parent) = std::path::Path::new(&real).parent() { -+ let _ = std::fs::create_dir_all(parent); -+ } -+ Some((relative, real)) -+ } -+ -+ fn capture(&mut self, cx: &mut Cx) -> bool { -+ let (Some(input), State::Running) = (self.input, self.state) else { -+ self.fail(cx, "the camera is not running".into()); -+ return false; -+ }; -+ let Some((relative, real)) = self.capture_path(cx, "IMG", "jpg") else { -+ self.fail(cx, "this app's storage is full or missing".into()); -+ return false; -+ }; -+ self.last = relative; -+ let library = self.granted("library.write"); -+ cx.camera_capture(input, CameraCaptureRequest::Photo { path: real, library }); -+ true -+ } -+ -+ fn record_start(&mut self, cx: &mut Cx) -> bool { -+ let (Some(input), State::Running) = (self.input, self.state) else { -+ self.fail(cx, "the camera is not running".into()); -+ return false; -+ }; -+ if self.recording.is_some() { -+ return true; -+ } -+ let Some((relative, real)) = self.capture_path(cx, "VID", "mp4") else { -+ self.fail(cx, "this app's storage is full or missing".into()); -+ return false; -+ }; -+ let audio = self.granted("microphone.record"); -+ if audio { -+ cx.request_permission(Permission::AudioInput); -+ } -+ self.recording = Some(relative); -+ let library = self.granted("library.write"); -+ cx.camera_capture(input, CameraCaptureRequest::StartVideo { path: real, audio, library }); -+ true -+ } -+ -+ fn record_stop(&mut self, cx: &mut Cx) { -+ if let (Some(input), Some(_)) = (self.input, self.recording.as_ref()) { -+ cx.camera_capture(input, CameraCaptureRequest::StopVideo); -+ } -+ } -+ -+ fn control(&mut self, cx: &mut Cx, control: CameraControl) { -+ if let (Some(input), State::Running) = (self.input, self.state) { -+ cx.camera_control(input, control); -+ } -+ } -+ -+ fn on_capture(&mut self, cx: &mut Cx, result: &CameraCaptureResult) { -+ match result { -+ CameraCaptureResult::Photo { .. } => self.notify_capture(cx), -+ CameraCaptureResult::VideoStopped { .. } => { -+ if let Some(relative) = self.recording.take() { -+ self.last = relative; -+ } -+ self.notify_capture(cx); -+ } -+ CameraCaptureResult::Failed { what, error } => { -+ if what == "video" { -+ self.recording = None; -+ } -+ self.fail(cx, format!("{what} failed: {error}")); -+ } -+ _ => {} -+ } -+ } -+ -+ fn notify_capture(&mut self, cx: &mut Cx) { -+ if self.on_capture.as_object() != ScriptObject::ZERO { -+ cx.widget_to_script_call(self.widget_uid(), NIL, self.view.source.clone(), self.on_capture.clone(), &[]); -+ } -+ } -+} -+ -+impl CameraPreview { -+ fn handle_gestures(&mut self, cx: &mut Cx, event: &Event) { -+ let area = self.view.area(); -+ let rect = area.rect(cx); -+ if let (true, Event::TouchUpdate(touches)) = (self.pinch_zoom, event) { -+ use crate::gesture_view::PinchStep; -+ match self.pinch.update(touches, area.clipped_rect(cx)) { -+ PinchStep::Began(_) => self.pinch_base = if self.zoom > 0.0 { self.zoom } else { 1.0 }, -+ PinchStep::Moved(scale, _) => { -+ let zoom = (self.pinch_base * scale as f32).clamp(1.0, 10.0); -+ if (zoom - self.zoom).abs() >= 0.02 { -+ self.zoom = zoom; -+ self.control(cx, CameraControl::ZoomRatio(zoom)); -+ } -+ } -+ PinchStep::Ended => { -+ if self.on_zoom.as_object() != ScriptObject::ZERO { -+ cx.widget_to_script_call(self.widget_uid(), NIL, self.view.source.clone(), self.on_zoom.clone(), &[(self.zoom as f64).into()]); -+ } -+ } -+ PinchStep::Nothing => {} -+ } -+ } -+ if !self.tap_to_focus || rect.size.x <= 0.0 || rect.size.y <= 0.0 { -+ return; -+ } -+ // A tap the controls drawn over the preview did not claim. -+ if let Hit::FingerUp(e) = event.hits_with_capture_overload(cx, area, true) { -+ let child_claimed = cx.fingers.digit_capture_areas(e.digit_id).into_iter().any(|a| a != area && a.rect(cx).is_inside_of(rect) && a.rect(cx).size != rect.size); -+ if e.is_over && e.was_tap() && !self.pinch.pinching() && !child_claimed { -+ let x = (e.abs.x - rect.pos.x) / rect.size.x; -+ let y = (e.abs.y - rect.pos.y) / rect.size.y; -+ self.control(cx, CameraControl::FocusPoint { x: x.clamp(0.0, 1.0), y: y.clamp(0.0, 1.0) }); -+ } -+ } -+ } -+} -+ -+fn arg_number(vm: &mut ScriptVm, args: ScriptValue, index: usize) -> Option { -+ let args_obj = args.as_object()?; -+ let trap = vm.bx.threads.cur().trap.pass(); -+ vm.bx.heap.vec_value(args_obj, index, trap).as_number() -+} -+ -+fn arg_string(vm: &mut ScriptVm, args: ScriptValue, index: usize) -> Option { -+ let args_obj = args.as_object()?; -+ let trap = vm.bx.threads.cur().trap.pass(); -+ let value = vm.bx.heap.vec_value(args_obj, index, trap); -+ vm.bx.heap.cast_to_owned_string(value, "copying a camera argument") -+} -+ -+impl Widget for CameraPreview { -+ fn script_call(&mut self, vm: &mut ScriptVm, method: LiveId, args: ScriptValue) -> ScriptAsyncResult { -+ let ret = |v: ScriptValue| ScriptAsyncResult::Return(v); -+ match method { -+ m if m == live_id!(start) => ret(vm.with_cx_mut(|cx| self.start(cx)).into()), -+ m if m == live_id!(stop) => { -+ vm.with_cx_mut(|cx| self.stop(cx)); -+ ret(NIL) -+ } -+ m if m == live_id!(switch) => { -+ self.facing = if self.facing == "front" { "back".into() } else { "front".into() }; -+ vm.with_cx_mut(|cx| self.drive(cx)); -+ ret(NIL) -+ } -+ m if m == live_id!(set_facing) => { -+ if let Some(facing) = arg_string(vm, args, 0) { -+ self.facing = facing; -+ vm.with_cx_mut(|cx| self.drive(cx)); -+ } -+ ret(NIL) -+ } -+ m if m == live_id!(set_aspect) => { -+ if let Some(aspect) = arg_number(vm, args, 0).filter(|a| *a > 0.2 && *a < 5.0) { -+ self.aspect = aspect; -+ vm.with_cx_mut(|cx| self.drive(cx)); -+ } -+ ret(NIL) -+ } -+ m if m == live_id!(facing) => ret(vm.bx.heap.new_string_from_str(if self.facing == "front" { "front" } else { "back" })), -+ m if m == live_id!(capture) => ret(vm.with_cx_mut(|cx| self.capture(cx)).into()), -+ m if m == live_id!(record_start) => ret(vm.with_cx_mut(|cx| self.record_start(cx)).into()), -+ m if m == live_id!(record_stop) => { -+ vm.with_cx_mut(|cx| self.record_stop(cx)); -+ ret(NIL) -+ } -+ m if m == live_id!(is_recording) => ret(self.recording.is_some().into()), -+ m if m == live_id!(is_running) => ret((self.state == State::Running).into()), -+ m if m == live_id!(set_zoom) => { -+ if let Some(zoom) = arg_number(vm, args, 0) { -+ self.zoom = zoom.clamp(0.5, 20.0) as f32; -+ let zoom = self.zoom; -+ vm.with_cx_mut(|cx| self.control(cx, CameraControl::ZoomRatio(zoom))); -+ } -+ ret(NIL) -+ } -+ m if m == live_id!(zoom) => ret((if self.zoom > 0.0 { self.zoom as f64 } else { 1.0 }).into()), -+ m if m == live_id!(set_flash) => { -+ if let Some(mode) = arg_string(vm, args, 0).as_deref().and_then(flash_mode) { -+ vm.with_cx_mut(|cx| self.control(cx, CameraControl::Flash(mode))); -+ } -+ ret(NIL) -+ } -+ m if m == live_id!(set_exposure) => { -+ if let Some(ev) = arg_number(vm, args, 0) { -+ vm.with_cx_mut(|cx| self.control(cx, CameraControl::ExposureBias(ev.clamp(-4.0, 4.0) as f32))); -+ } -+ ret(NIL) -+ } -+ m if m == live_id!(focus) => { -+ if let (Some(x), Some(y)) = (arg_number(vm, args, 0), arg_number(vm, args, 1)) { -+ vm.with_cx_mut(|cx| { -+ self.control(cx, CameraControl::FocusPoint { x: x.clamp(0.0, 1.0), y: y.clamp(0.0, 1.0) }) -+ }); -+ } -+ ret(NIL) -+ } -+ m if m == live_id!(last) => ret(vm.bx.heap.new_string_from_str(&self.last)), -+ m if m == live_id!(error) => ret(vm.bx.heap.new_string_from_str(&self.last_error)), -+ _ => self.view.script_call(vm, method, args), -+ } -+ } -+ -+ fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) { -+ self.view.handle_event(cx, event, scope); -+ if self.timer.is_some_and(|t| t.is_event(event).is_some()) { -+ if self.drawn { -+ self.drawn = false; -+ if self.state == State::Idle { -+ self.drive(cx); -+ } -+ self.view.redraw(cx); -+ } else if matches!(self.state, State::Starting | State::Running) { -+ // Not drawn since the last tick: the preview left the screen. -+ self.halt(cx); -+ } -+ } -+ if self.state == State::Running { -+ self.handle_gestures(cx, event); -+ } -+ match event { -+ Event::PermissionResult(result) if result.permission == Permission::Camera => { -+ self.permission = Some(result.status); -+ self.drive(cx); -+ } -+ Event::VideoInputs(VideoInputsEvent { descs }) => { -+ let count = descs.len(); -+ self.cameras = descs -+ .iter() -+ .enumerate() -+ .map(|(i, d)| CameraChoice { input_id: d.input_id, front: is_front(&d.name, i, count), formats: d.formats.clone() }) -+ .filter(|c| c.format_for(4.0 / 3.0).is_some()) -+ .collect(); -+ self.drive(cx); -+ } -+ Event::VideoPlaybackPrepared(_) => { -+ if self.state == State::Starting { -+ self.state = State::Running; -+ } -+ } -+ Event::VideoPlaybackResourcesReleased(_) => { -+ if self.state == State::Stopping { -+ self.state = State::Idle; -+ } -+ } -+ Event::VideoDecodingError(e) => { -+ if matches!(self.state, State::Starting | State::Running) { -+ self.state = State::Idle; -+ let why = format!("the camera stopped: {}", e.error); -+ self.fail(cx, why); -+ } -+ } -+ Event::Actions(actions) => { -+ for action in actions { -+ let Some(capture) = action.downcast_ref::() else { continue }; -+ if Some(capture.input_id) == self.input { -+ let result = capture.result.clone(); -+ self.on_capture(cx, &result); -+ } -+ } -+ } -+ _ => {} -+ } -+ } -+ -+ fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep { -+ self.drawn = true; -+ self.view.draw_walk(cx, scope, walk) -+ } -+} -+ -+#[cfg(test)] -+mod tests { -+ use super::*; -+ -+ fn format(width: usize, height: usize, pixel_format: VideoPixelFormat, id: u64) -> VideoFormat { -+ VideoFormat { format_id: VideoFormatId(LiveId(id)), width, height, frame_rate: Some(30.0), pixel_format } -+ } -+ -+ #[test] -+ fn the_preview_profile_matches_the_viewfinder_then_takes_the_most_pixels() { -+ let choice = CameraChoice { -+ input_id: VideoInputId(LiveId(1)), -+ front: false, -+ formats: vec![ -+ format(640, 480, VideoPixelFormat::NV12, 1), -+ format(1440, 1080, VideoPixelFormat::NV12, 2), -+ format(1920, 1080, VideoPixelFormat::NV12, 3), -+ format(4032, 3024, VideoPixelFormat::NV12, 4), -+ ], -+ }; -+ assert_eq!(choice.format_for(4.0 / 3.0).map(|f| (f.1, f.2)), Some((1440, 1080)), "4:3, and within 1080p"); -+ assert_eq!(choice.format_for(16.0 / 9.0).map(|f| (f.1, f.2)), Some((1920, 1080))); -+ } -+ -+ #[test] -+ fn a_camera_is_front_when_it_says_so_or_is_the_unnamed_second() { -+ assert!(is_front("Front Camera", 0, 2)); -+ assert!(is_front("FaceTime HD Camera", 0, 1)); -+ assert!(!is_front("Back Camera", 1, 2)); -+ assert!(is_front("camera1", 1, 2)); -+ assert!(!is_front("camera0", 0, 2)); -+ } -+ -+ #[test] -+ fn flash_names_map_and_unknown_ones_are_ignored() { -+ assert_eq!(flash_mode("torch"), Some(CameraFlashMode::Torch)); -+ assert_eq!(flash_mode("strobe"), None); -+ } -+} -diff --git a/widgets/src/gesture_view.rs b/widgets/src/gesture_view.rs -new file mode 100644 -index 000000000..35d1a0dd9 ---- /dev/null -+++ b/widgets/src/gesture_view.rs -@@ -0,0 +1,653 @@ -+//! Gestures for script apps: a View whose taps, long presses, swipes, drags -+//! and pinches call the app's script, and a bottom sheet that rests at three -+//! heights and follows the finger between them. -+//! -+//! Recognition stays native. A pinch or a drag is dozens of touch updates a -+//! second; handing each to script would spend an installed app's instruction -+//! budget on arithmetic and stutter on an old phone. `GestureView` calls the -+//! script once per recognized gesture, and for the continuous ones (a drag, a -+//! pinch) at most once a frame and only when the value moved. `SheetView` -+//! needs no script at all while it moves; it tells the app where it came to -+//! rest. -+//! -+//! Neither widget grants anything: they are vocabulary, like a Button, and -+//! every isolate has them. -+//! -+//! ```text -+//! GestureView{ -+//! on_tap: |x, y| open_at(x, y) -+//! on_double_tap: |x, y| toggle_zoom() -+//! on_long_press: |x, y| show_menu(x, y) -+//! on_swipe: |dx, dy| if dx < 0 { next() } else { previous() } -+//! on_pan: |dx, dy, phase| drag(dx, dy, phase) // phase 0 begin, 1 move, 2 end -+//! on_pinch: |scale, x, y, phase| zoom(scale, phase) // scale relative to the pinch start -+//! Image{...} -+//! } -+//! SheetView{ on_detent: |detent| sheet_moved(detent) ...children } // 0 peek, 1 half, 2 full -+//! ``` -+//! Coordinates are relative to the view's top left. -+use crate::{ -+ makepad_derive_widget::*, -+ makepad_draw::*, -+ makepad_script::ScriptFnRef, -+ view::View, -+ widget::*, -+ widget_async::CxWidgetToScriptCallExt, -+}; -+use crate::makepad_draw::makepad_platform::event::{DigitId, TouchState, TouchUpdateEvent}; -+ -+script_mod! { -+ use mod.prelude.widgets_internal.* -+ use mod.widgets.* -+ -+ mod.widgets.GestureViewBase = #(GestureView::register_widget(vm)) -+ mod.widgets.GestureView = set_type_default() do mod.widgets.GestureViewBase{ -+ width: Fill -+ height: Fit -+ } -+ -+ mod.widgets.SheetViewBase = #(SheetView::register_widget(vm)) -+ mod.widgets.SheetView = set_type_default() do mod.widgets.SheetViewBase{ -+ width: Fill -+ height: Fill -+ flow: Down -+ } -+} -+ -+/// Movement under this is still a tap: a finger is never perfectly still. -+const TAP_SLOP: f64 = 10.0; -+/// A second tap this soon after the first, this close to it, is a double tap. -+const DOUBLE_TAP_SECONDS: f64 = 0.3; -+const DOUBLE_TAP_DISTANCE: f64 = 24.0; -+/// A release this fast after this much travel along one axis is a swipe. -+const SWIPE_DISTANCE: f64 = 60.0; -+const SWIPE_SECONDS: f64 = 0.45; -+/// A pinch reports again once its scale has moved this much. -+const PINCH_STEP: f64 = 0.02; -+ -+/// A swipe's direction, when the travel was one: the dominant axis carries -+/// at least 1.5 times the other, and far enough, fast enough. -+fn swipe(dx: f64, dy: f64, seconds: f64) -> Option<(f64, f64)> { -+ if seconds > SWIPE_SECONDS { -+ return None; -+ } -+ if dx.abs() >= SWIPE_DISTANCE && dx.abs() >= dy.abs() * 1.5 { -+ return Some((dx, 0.0)); -+ } -+ if dy.abs() >= SWIPE_DISTANCE && dy.abs() >= dx.abs() * 1.5 { -+ return Some((0.0, dy)); -+ } -+ None -+} -+ -+#[derive(Clone, Copy, Debug)] -+struct Contact { -+ uid: u64, -+ pos: DVec2, -+} -+ -+/// Two fingers down: the spread they started at. -+#[derive(Clone, Copy, Debug)] -+struct Spread { -+ first: u64, -+ second: u64, -+ distance: f64, -+} -+ -+/// What one touch update did to a pinch. -+#[derive(Clone, Copy, Debug, Default, PartialEq)] -+pub(crate) enum PinchStep { -+ #[default] -+ Nothing, -+ Began(DVec2), -+ Moved(f64, DVec2), -+ Ended, -+} -+ -+/// Follows the fingers that started inside the view and reports a pinch. -+#[derive(Clone, Debug, Default)] -+pub(crate) struct PinchTracker { -+ contacts: Vec, -+ spread: Option, -+} -+ -+impl PinchTracker { -+ pub(crate) fn update(&mut self, event: &TouchUpdateEvent, bounds: Rect) -> PinchStep { -+ for touch in &event.touches { -+ match touch.state { -+ TouchState::Start => { -+ if bounds.contains(touch.abs) && !self.contacts.iter().any(|c| c.uid == touch.uid) { -+ self.contacts.push(Contact { uid: touch.uid, pos: touch.abs }); -+ } -+ } -+ TouchState::Stop => self.contacts.retain(|c| c.uid != touch.uid), -+ TouchState::Move | TouchState::Stable => { -+ if let Some(contact) = self.contacts.iter_mut().find(|c| c.uid == touch.uid) { -+ contact.pos = touch.abs; -+ } -+ } -+ } -+ } -+ let contact = |uid: u64| self.contacts.iter().find(|c| c.uid == uid).copied(); -+ match self.spread { -+ None if self.contacts.len() >= 2 => { -+ let (first, second) = (self.contacts[0], self.contacts[1]); -+ self.spread = Some(Spread { -+ first: first.uid, -+ second: second.uid, -+ // A zero spread would make every later ratio infinite. -+ distance: first.pos.distance(&second.pos).max(1.0), -+ }); -+ PinchStep::Began((first.pos + second.pos) * 0.5) -+ } -+ Some(spread) => match (contact(spread.first), contact(spread.second)) { -+ (Some(first), Some(second)) => PinchStep::Moved( -+ first.pos.distance(&second.pos) / spread.distance, -+ (first.pos + second.pos) * 0.5, -+ ), -+ _ => { -+ self.spread = None; -+ PinchStep::Ended -+ } -+ }, -+ None => PinchStep::Nothing, -+ } -+ } -+ -+ pub(crate) fn pinching(&self) -> bool { -+ self.spread.is_some() -+ } -+ -+ /// Whether any finger that started a pinch is still down: its release -+ /// must not read as a tap. -+ fn holding(&self) -> bool { -+ !self.contacts.is_empty() -+ } -+} -+ -+/// One finger followed from the raw touch stream (see GestureView's -+/// handle_event): where and when it went down, and what it has become. -+#[derive(Clone, Copy, Debug)] -+struct TouchPress { -+ uid: u64, -+ start: DVec2, -+ time: f64, -+ panning: bool, -+ /// Spent on a long press: its release is no tap. -+ spent: bool, -+ last_sent: DVec2, -+} -+ -+#[derive(Script, ScriptHook, Widget)] -+pub struct GestureView { -+ #[deref] -+ view: View, -+ #[live] -+ on_tap: ScriptFnRef, -+ #[live] -+ on_double_tap: ScriptFnRef, -+ #[live] -+ on_long_press: ScriptFnRef, -+ #[live] -+ on_swipe: ScriptFnRef, -+ #[live] -+ on_pan: ScriptFnRef, -+ #[live] -+ on_pinch: ScriptFnRef, -+ -+ /// This press is ours: no child had already captured it. -+ #[rust] -+ live: bool, -+ /// The press moved past the tap slop: a drag, not a tap. -+ #[rust] -+ panning: bool, -+ /// Last drag deltas sent, so a still finger sends nothing. -+ #[rust] -+ pan_sent: DVec2, -+ #[rust] -+ last_tap: Option<(f64, DVec2)>, -+ #[rust] -+ pinch: PinchTracker, -+ #[rust] -+ touch: Option, -+ #[rust] -+ pinch_sent: f64, -+ /// A pinch happened during this press: the fingers' release is not a -+ /// tap, a swipe or the end of a drag. -+ #[rust] -+ pinched: bool, -+} -+ -+impl GestureView { -+ fn call(&self, cx: &mut Cx, script_fn: &ScriptFnRef, args: &[ScriptValue]) { -+ if script_fn.as_object() == ScriptObject::ZERO { -+ return; -+ } -+ cx.widget_to_script_call(self.widget_uid(), NIL, self.view.source.clone(), script_fn.clone(), args); -+ } -+ -+ fn local(&self, cx: &Cx, abs: DVec2) -> DVec2 { -+ abs - self.view.area().rect(cx).pos -+ } -+ -+ fn handle_touches(&mut self, cx: &mut Cx, event: &TouchUpdateEvent) { -+ let area = self.view.area(); -+ let bounds = area.clipped_rect(cx); -+ for t in &event.touches { -+ match t.state { -+ TouchState::Start => { -+ if self.touch.is_some() || !bounds.contains(t.abs) { -+ continue; -+ } -+ let claimed = t.handled.get(); -+ if !claimed.is_empty() && claimed != area && is_inside(cx, claimed, area) { -+ continue; -+ } -+ if !self.pinch.holding() || self.pinch.contacts.len() <= 1 { -+ self.pinched = false; -+ } -+ self.touch = Some(TouchPress { uid: t.uid, start: t.abs, time: t.time, panning: false, spent: false, last_sent: DVec2::default() }); -+ } -+ TouchState::Move | TouchState::Stable => { -+ let Some(mut press) = self.touch.filter(|p| p.uid == t.uid) else { continue }; -+ if self.pinch.pinching() || self.pinched { -+ continue; -+ } -+ let delta = t.abs - press.start; -+ if !press.panning && delta.length() > TAP_SLOP { -+ press.panning = true; -+ self.call(cx, &self.on_pan.clone(), &[0.0.into(), 0.0.into(), 0.0.into()]); -+ } -+ if press.panning && (delta - press.last_sent).length() >= 1.0 { -+ press.last_sent = delta; -+ self.call(cx, &self.on_pan.clone(), &[delta.x.into(), delta.y.into(), 1.0.into()]); -+ } -+ self.touch = Some(press); -+ } -+ TouchState::Stop => { -+ let Some(press) = self.touch.filter(|p| p.uid == t.uid) else { continue }; -+ self.touch = None; -+ if self.pinched { -+ continue; -+ } -+ let delta = t.abs - press.start; -+ if press.panning { -+ self.call(cx, &self.on_pan.clone(), &[delta.x.into(), delta.y.into(), 2.0.into()]); -+ if let Some((dx, dy)) = swipe(delta.x, delta.y, t.time - press.time) { -+ self.call(cx, &self.on_swipe.clone(), &[dx.into(), dy.into()]); -+ } -+ } else if !press.spent && delta.length() <= TAP_SLOP && bounds.contains(t.abs) { -+ self.tap(cx, t.abs, t.time); -+ } -+ } -+ } -+ } -+ } -+ -+ fn tap(&mut self, cx: &mut Cx, abs: DVec2, time: f64) { -+ let at = self.local(cx, abs); -+ self.call(cx, &self.on_tap.clone(), &[at.x.into(), at.y.into()]); -+ let double = self -+ .last_tap -+ .is_some_and(|(t, pos)| time - t <= DOUBLE_TAP_SECONDS && pos.distance(&abs) <= DOUBLE_TAP_DISTANCE); -+ if double { -+ self.last_tap = None; -+ self.call(cx, &self.on_double_tap.clone(), &[at.x.into(), at.y.into()]); -+ } else { -+ self.last_tap = Some((time, abs)); -+ } -+ } -+} -+ -+/// Whether `inner` is a widget drawn inside `outer` (not `outer`'s own -+/// rect, and not one around it). -+fn is_inside(cx: &Cx, inner: Area, outer: Area) -> bool { -+ let (rect, own) = (inner.rect(cx), outer.rect(cx)); -+ rect.is_inside_of(own) && rect.size != own.size -+} -+ -+impl Widget for GestureView { -+ fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep { -+ self.view.draw_walk(cx, scope, walk) -+ } -+ -+ fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) { -+ self.view.handle_event(cx, event, scope); -+ if !self.view.visible { -+ return; -+ } -+ -+ if let Event::TouchUpdate(touches) = event { -+ let bounds = self.view.area().clipped_rect(cx); -+ match self.pinch.update(touches, bounds) { -+ PinchStep::Began(center) => { -+ self.pinched = true; -+ self.pinch_sent = 1.0; -+ if self.panning { -+ // The drag a first finger started gives way to the pinch. -+ let at = self.pan_sent; -+ self.call(cx, &self.on_pan.clone(), &[at.x.into(), at.y.into(), 2.0.into()]); -+ self.panning = false; -+ } -+ let at = self.local(cx, center); -+ self.call(cx, &self.on_pinch.clone(), &[1.0.into(), at.x.into(), at.y.into(), 0.0.into()]); -+ } -+ PinchStep::Moved(scale, center) => { -+ if (scale / self.pinch_sent - 1.0).abs() >= PINCH_STEP { -+ self.pinch_sent = scale; -+ let at = self.local(cx, center); -+ self.call(cx, &self.on_pinch.clone(), &[scale.into(), at.x.into(), at.y.into(), 1.0.into()]); -+ } -+ } -+ PinchStep::Ended => { -+ let scale = self.pinch_sent; -+ self.call(cx, &self.on_pinch.clone(), &[scale.into(), 0.0.into(), 0.0.into(), 2.0.into()]); -+ } -+ PinchStep::Nothing => {} -+ } -+ } -+ -+ // Touch is read from the raw stream and never claimed. Claiming it, -+ // even with a capture overload, marks the touch handled, and the -+ // scroll view around this one then never starts its drag: a list of -+ // GestureView rows would not scroll. A child that claimed the touch -+ // (a Button inside) still keeps it. -+ match event { -+ Event::TouchUpdate(touches) => { -+ self.handle_touches(cx, touches); -+ return; -+ } -+ Event::LongPress(press) => { -+ let fire = match self.touch.as_mut() { -+ Some(p) if p.uid == press.uid && !p.panning && !p.spent && !self.pinched => { -+ p.spent = true; -+ true -+ } -+ _ => false, -+ }; -+ if fire { -+ let at = self.local(cx, press.abs); -+ self.call(cx, &self.on_long_press.clone(), &[at.x.into(), at.y.into()]); -+ } -+ return; -+ } -+ _ => {} -+ } -+ -+ match event.hits_with_capture_overload(cx, self.view.area(), true) { -+ Hit::FingerDown(e) => { -+ // A child that already owns this press (a Button) keeps it. A -+ // scroll view around us captures every press too; that one is -+ // not a child, and the tap is still ours. -+ self.live = !child_owns_press(cx, e.digit_id, self.view.area()); -+ self.panning = false; -+ self.pan_sent = DVec2::default(); -+ if !self.pinch.holding() { -+ self.pinched = false; -+ } -+ } -+ Hit::FingerMove(e) if self.live && !self.pinch.pinching() && !self.pinched => { -+ let delta = e.abs - e.abs_start; -+ if !self.panning && delta.length() > TAP_SLOP { -+ self.panning = true; -+ self.call(cx, &self.on_pan.clone(), &[0.0.into(), 0.0.into(), 0.0.into()]); -+ } -+ if self.panning && (delta - self.pan_sent).length() >= 1.0 { -+ self.pan_sent = delta; -+ self.call(cx, &self.on_pan.clone(), &[delta.x.into(), delta.y.into(), 1.0.into()]); -+ } -+ } -+ Hit::FingerLongPress(e) if self.live && !self.panning && !self.pinched => { -+ let at = self.local(cx, e.abs); -+ // The press is spent on the long press: its release is no tap. -+ self.live = false; -+ self.call(cx, &self.on_long_press.clone(), &[at.x.into(), at.y.into()]); -+ } -+ Hit::FingerUp(e) if self.live => { -+ self.live = false; -+ if self.pinched { -+ return; -+ } -+ let delta = e.abs - e.abs_start; -+ if self.panning { -+ self.panning = false; -+ self.call(cx, &self.on_pan.clone(), &[delta.x.into(), delta.y.into(), 2.0.into()]); -+ if let Some((dx, dy)) = swipe(delta.x, delta.y, e.time - e.capture_time) { -+ self.call(cx, &self.on_swipe.clone(), &[dx.into(), dy.into()]); -+ } -+ return; -+ } -+ if e.is_over && delta.length() <= TAP_SLOP && !e.has_long_press_occurred { -+ self.tap(cx, e.abs, e.time); -+ } -+ } -+ _ => {} -+ } -+ } -+} -+ -+/// Whether a widget inside `area` (not `area` itself, not one around it) -+/// captured this press. -+fn child_owns_press(cx: &Cx, digit_id: DigitId, area: Area) -> bool { -+ cx.fingers -+ .digit_capture_areas(digit_id) -+ .into_iter() -+ .any(|captured| captured != area && is_inside(cx, captured, area)) -+} -+ -+/// Of the sheet's own height: the peek, half and full resting heights. -+const PEEK_HEIGHT: f64 = 132.0; -+const HALF_FRACTION: f64 = 0.45; -+/// What full height leaves above the sheet, so a search bar stays reachable. -+const FULL_TOP_GAP: f64 = 96.0; -+/// Pixels a second: faster than this at release is a flick to the next rest. -+const FLICK_VELOCITY: f64 = 600.0; -+ -+/// The three resting heights in a viewport this tall, rising. In a short -+/// viewport they close up rather than cross. -+fn sheet_heights(viewport_h: f64) -> [f64; 3] { -+ let half = viewport_h * HALF_FRACTION; -+ [PEEK_HEIGHT.min(half), half, (viewport_h - FULL_TOP_GAP).max(half)] -+} -+ -+/// Where a released drag comes to rest: the nearest height, or for a flick -+/// the next one the way the finger was going (velocity is downwards, px/s). -+fn sheet_rest(at: f64, velocity: f64, viewport_h: f64) -> usize { -+ let heights = sheet_heights(viewport_h); -+ if velocity <= -FLICK_VELOCITY { -+ heights.iter().position(|h| *h > at + 0.5).unwrap_or(2) -+ } else if velocity >= FLICK_VELOCITY { -+ heights.iter().rposition(|h| *h < at - 0.5).unwrap_or(0) -+ } else { -+ let distance = |i: &usize| (heights[*i] - at).abs(); -+ (0..3).min_by(|a, b| distance(a).total_cmp(&distance(b))).unwrap_or(0) -+ } -+} -+ -+/// A bottom sheet: fills its parent, draws its children in a panel along the -+/// bottom edge at the current detent's height, follows a drag that starts on -+/// the panel, and eases to a detent on release. The app hears only where it -+/// came to rest (`on_detent`), and can move it with `detent: 0|1|2`. -+#[derive(Script, ScriptHook, Widget)] -+pub struct SheetView { -+ #[deref] -+ view: View, -+ /// The detent to rest at: 0 peek, 1 half, 2 full. Script may set it. -+ #[live(0.0)] -+ detent: f64, -+ #[live] -+ on_detent: ScriptFnRef, -+ -+ #[rust] -+ height: f64, -+ /// While a finger is on the panel: the height it started at, and the -+ /// last (time, height) for the release velocity. -+ #[rust] -+ drag: Option<(f64, f64, f64)>, -+ #[rust] -+ prev: Option<(f64, f64)>, -+ #[rust] -+ viewport_h: f64, -+ #[rust] -+ next_frame: NextFrame, -+} -+ -+impl SheetView { -+ fn target(&self) -> f64 { -+ sheet_heights(self.viewport_h)[self.detent.clamp(0.0, 2.0) as usize] -+ } -+} -+ -+impl Widget for SheetView { -+ fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep { -+ let outer = cx.peek_walk_turtle(walk); -+ if outer.size.y > 0.0 { -+ self.viewport_h = outer.size.y; -+ } -+ if self.height <= 0.0 { -+ self.height = self.target(); -+ } -+ let mut inner = walk; -+ inner.height = Size::Fixed(self.height.max(0.0)); -+ inner.abs_pos = Some(dvec2(outer.pos.x, outer.pos.y + outer.size.y - self.height)); -+ cx.walk_turtle(walk); -+ self.view.draw_walk(cx, scope, inner) -+ } -+ -+ fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) { -+ self.view.handle_event(cx, event, scope); -+ if let Some(ne) = self.next_frame.is_event(event) { -+ let target = self.target(); -+ if self.drag.is_none() { -+ // Ease a fixed share of the way each frame: settles in ~200 ms. -+ let step = (target - self.height) * 0.25; -+ if step.abs() < 0.5 { -+ self.height = target; -+ } else { -+ self.height += step; -+ self.next_frame = cx.new_next_frame(); -+ } -+ let _ = ne; -+ self.view.redraw(cx); -+ } -+ } -+ match event.hits_with_capture_overload(cx, self.view.area(), true) { -+ Hit::FingerDown(e) => { -+ if !child_owns_press(cx, e.digit_id, self.view.area()) { -+ self.drag = Some((self.height, e.time, self.height)); -+ self.prev = None; -+ } -+ } -+ Hit::FingerMove(e) => { -+ if let Some((from, _, _)) = self.drag { -+ let [lowest, _, highest] = sheet_heights(self.viewport_h); -+ let now = (from - (e.abs.y - e.abs_start.y)).clamp(lowest, highest); -+ self.prev = self.drag.map(|(_, t, h)| (t, h)); -+ self.drag = Some((from, e.time, now)); -+ self.height = now; -+ self.view.redraw(cx); -+ } -+ } -+ Hit::FingerUp(e) => { -+ if let Some((_, time, at)) = self.drag.take() { -+ // Downwards velocity: the sheet shrinks as the finger descends. -+ let velocity = match self.prev { -+ Some((t0, h0)) if time > t0 => -(at - h0) / (time - t0), -+ _ => 0.0, -+ }; -+ let _ = e; -+ let rest = sheet_rest(at, velocity, self.viewport_h) as f64; -+ let moved = rest != self.detent.clamp(0.0, 2.0).floor(); -+ self.detent = rest; -+ self.next_frame = cx.new_next_frame(); -+ if moved && self.on_detent.as_object() != ScriptObject::ZERO { -+ cx.widget_to_script_call( -+ self.widget_uid(), -+ NIL, -+ self.view.source.clone(), -+ self.on_detent.clone(), -+ &[rest.into()], -+ ); -+ } -+ } -+ } -+ _ => {} -+ } -+ } -+} -+ -+#[cfg(test)] -+mod tests { -+ use super::*; -+ use crate::makepad_draw::makepad_platform::event::TouchPoint; -+ use std::cell::Cell; -+ -+ fn touches(points: &[(u64, TouchState, f64, f64)]) -> TouchUpdateEvent { -+ TouchUpdateEvent { -+ time: 1.0, -+ window_id: WindowId(0, 0), -+ modifiers: KeyModifiers::default(), -+ touches: points -+ .iter() -+ .map(|&(uid, state, x, y)| TouchPoint { -+ uid, -+ state, -+ abs: dvec2(x, y), -+ time: 1.0, -+ rotation_angle: 0.0, -+ force: 1.0, -+ radius: dvec2(1.0, 1.0), -+ handled: Cell::new(Area::Empty), -+ sweep_lock: Cell::new(Area::Empty), -+ }) -+ .collect(), -+ } -+ } -+ -+ #[test] -+ fn a_swipe_is_fast_and_along_one_axis() { -+ assert_eq!(swipe(-120.0, 10.0, 0.2), Some((-120.0, 0.0))); -+ assert_eq!(swipe(5.0, 90.0, 0.2), Some((0.0, 90.0))); -+ assert_eq!(swipe(-120.0, 100.0, 0.2), None, "a diagonal is no swipe"); -+ assert_eq!(swipe(-120.0, 0.0, 1.0), None, "a slow drag is no swipe"); -+ assert_eq!(swipe(-30.0, 0.0, 0.1), None, "nor is a short one"); -+ } -+ -+ #[test] -+ fn two_fingers_inside_the_view_pinch_relative_to_their_start() { -+ let bounds = Rect { pos: dvec2(0.0, 0.0), size: dvec2(400.0, 400.0) }; -+ let mut pinch = PinchTracker::default(); -+ assert_eq!(pinch.update(&touches(&[(1, TouchState::Start, 100.0, 200.0)]), bounds), PinchStep::Nothing); -+ assert_eq!( -+ pinch.update(&touches(&[(2, TouchState::Start, 300.0, 200.0)]), bounds), -+ PinchStep::Began(dvec2(200.0, 200.0)) -+ ); -+ assert_eq!( -+ pinch.update(&touches(&[(2, TouchState::Move, 500.0, 200.0)]), bounds), -+ PinchStep::Moved(2.0, dvec2(300.0, 200.0)) -+ ); -+ assert_eq!(pinch.update(&touches(&[(1, TouchState::Stop, 100.0, 200.0)]), bounds), PinchStep::Ended); -+ assert!(pinch.holding(), "a finger is still down: its release is not a tap"); -+ pinch.update(&touches(&[(2, TouchState::Stop, 500.0, 200.0)]), bounds); -+ assert!(!pinch.holding()); -+ } -+ -+ #[test] -+ fn a_finger_that_starts_outside_the_view_is_not_part_of_its_pinch() { -+ let bounds = Rect { pos: dvec2(0.0, 0.0), size: dvec2(100.0, 100.0) }; -+ let mut pinch = PinchTracker::default(); -+ pinch.update(&touches(&[(1, TouchState::Start, 50.0, 50.0)]), bounds); -+ assert_eq!(pinch.update(&touches(&[(2, TouchState::Start, 500.0, 50.0)]), bounds), PinchStep::Nothing); -+ } -+ -+ #[test] -+ fn the_sheet_rests_at_the_nearest_detent_or_flicks_to_the_next() { -+ assert_eq!(sheet_heights(800.0), [132.0, 360.0, 704.0]); -+ assert_eq!(sheet_rest(200.0, 0.0, 800.0), 0); -+ assert_eq!(sheet_rest(300.0, 0.0, 800.0), 1); -+ assert_eq!(sheet_rest(200.0, -900.0, 800.0), 1, "an upward flick goes to the next detent up"); -+ assert_eq!(sheet_rest(650.0, 900.0, 800.0), 1, "a downward flick to the next one down"); -+ let [peek, half, full] = sheet_heights(200.0); -+ assert!(peek <= half && half <= full && full <= 200.0); -+ } -+} -diff --git a/widgets/src/lib.rs b/widgets/src/lib.rs -index 4c6418776..211cf4086 100644 ---- a/widgets/src/lib.rs -+++ b/widgets/src/lib.rs -@@ -33,6 +33,9 @@ - - // Modules ordered to match script_mod calls - pub mod rubber_view; -+pub mod gesture_view; -+pub mod web_reader; -+pub mod camera_preview; - pub mod scroll_bar; - pub mod scroll_bars; - pub mod scroll_motion; -@@ -363,6 +366,8 @@ pub fn widgets_mod(vm: &mut ScriptVm) { - crate::view_ui::script_mod(vm); - crate::grid::script_mod(vm); - crate::rubber_view::script_mod(vm); -+ crate::gesture_view::script_mod(vm); -+ crate::web_reader::script_mod(vm); - - crate::label::script_mod(vm); - crate::link_label::script_mod(vm); -@@ -444,6 +449,7 @@ pub fn widgets_mod(vm: &mut ScriptVm) { - crate::callout_tooltip::script_mod(vm); - crate::popup_notification::script_mod(vm); - crate::video::script_mod(vm); -+ crate::camera_preview::script_mod(vm); - crate::page_flip::script_mod(vm); - crate::hosted_view::script_mod(vm); - crate::file_tree::script_mod(vm); -diff --git a/widgets/src/map/archive.rs b/widgets/src/map/archive.rs -index baef27a1f..3d9b67fef 100644 ---- a/widgets/src/map/archive.rs -+++ b/widgets/src/map/archive.rs -@@ -468,6 +468,10 @@ struct CachedHttpRange { - /// HTTP `.mkmap` source using one whole-root GET and strict shard ranges. - pub struct HttpRangeByteSource { - root_url: String, -+ /// The script heap this source fetches for (0 = the host's own): every -+ /// request answers to that heap's allowlist, like the rest of the network -+ /// paths a card can reach. -+ heap_key: usize, - disk_cache: Option, - requests: HashMap, - queued: Vec, -@@ -496,6 +500,7 @@ fn new_with_disk_cache( - ) -> Self { - Self { - root_url: root_url.into().trim_end_matches('/').to_string(), -+ heap_key: 0, - disk_cache, - requests: HashMap::new(), - queued: Vec::new(), -@@ -575,6 +580,17 @@ fn issue(&mut self, cx: &mut Cx, mut pending: HttpNetworkRead) { - (url, Some(format!("bytes={offset}-{end}")), len) - } - }; -+ if !crate::splash_policy::url_allowed(self.heap_key, &url) { -+ crate::log!("map archive fetch refused by the host's allowlist: {url}"); -+ for waiter in &pending.waiters { -+ self.ready.push_back(ReadCompletion { -+ token: waiter.token, -+ result: Err("refused by the host's allowlist".to_string()), -+ }); -+ } -+ cx.redraw_all(); -+ return; -+ } - #[cfg(target_arch = "wasm32")] - let url = format!( - "{url}#makepad-http=archive&tiles={tile_label}&priority={priority}&bytes={max_body}" -@@ -2023,6 +2039,14 @@ pub fn http(root_url: impl Into, workers: ArchiveWorkerPool) -> Self { - )) - } - -+ /// [`Self::http`] on behalf of a script heap, whose allowlist every -+ /// request then answers to. -+ pub fn http_for_heap(root_url: impl Into, workers: ArchiveWorkerPool, heap_key: usize) -> Self { -+ let mut source = HttpRangeByteSource::new(root_url); -+ source.heap_key = heap_key; -+ Self::Http(TileArchive::new(source, workers)) -+ } -+ - pub fn request_tile( - &mut self, - cx: &mut Cx, -diff --git a/widgets/src/map/view.rs b/widgets/src/map/view.rs -index 4581c70af..7bc1d7167 100644 ---- a/widgets/src/map/view.rs -+++ b/widgets/src/map/view.rs -@@ -4312,6 +4312,10 @@ struct LabelLists { - - // --- MapView widget --- - -+/// How many Overpass mirrors a tile request tries before it gives up on the -+/// ones an app's policy does not list. -+const OVERPASS_MIRROR_TRIES: u8 = 8; -+ - #[derive(Script, Widget)] - pub struct MapView { - /// The @cam readout in the corner: the exact command to recreate this -@@ -7542,7 +7546,8 @@ fn install_overlay_sources(&mut self, cx: &mut Cx) { - .overlays - .iter() - .map(|overlay| { -- let (archive, local_mbtiles_path, filter) = match &overlay.source { -+ let source = self.jailed_tile_source(overlay.source.clone()); -+ let (archive, local_mbtiles_path, filter) = match &source { - TileSourceConfig::LocalArchive { mbtiles_path, .. } => { - let (path, filter) = split_overlay_option(mbtiles_path); - if is_mkmap_path_shape(path) { -@@ -7558,7 +7563,7 @@ fn install_overlay_sources(&mut self, cx: &mut Cx) { - TileSourceConfig::HttpArchive { root_url, .. } => { - let (root_url, filter) = split_overlay_option(root_url); - ( -- Some(MapTileArchive::http(root_url, workers.clone())), -+ Some(MapTileArchive::http_for_heap(root_url, workers.clone(), self.source.heap_key())), - None, - filter, - ) -@@ -7575,7 +7580,33 @@ fn install_overlay_sources(&mut self, cx: &mut Cx) { - self.apply_archive_cache_budgets(cx); - } - -+ /// A local archive config as this map's script heap may read it: the -+ /// paths of a card under a policy resolve inside its storage jail, and a -+ /// path that cannot (no jail, or one that climbs out) reads nothing. -+ fn jailed_tile_source(&self, config: TileSourceConfig) -> TileSourceConfig { -+ let heap_key = self.source.heap_key(); -+ let jail = |path: &str| { -+ if path.is_empty() { -+ return String::new(); -+ } -+ crate::splash_policy::local_path_for_heap(heap_key, path).unwrap_or_default() -+ }; -+ match config { -+ TileSourceConfig::LocalArchive { mbtiles_path, detail_mbtiles_path, bridge_dz_path } => { -+ TileSourceConfig::LocalArchive { -+ mbtiles_path: jail(&mbtiles_path), -+ detail_mbtiles_path: jail(&detail_mbtiles_path), -+ bridge_dz_path: jail(&bridge_dz_path), -+ } -+ } -+ TileSourceConfig::HttpArchive { root_url, detail_root_url, bridge_dz_path } => { -+ TileSourceConfig::HttpArchive { root_url, detail_root_url, bridge_dz_path: jail(&bridge_dz_path) } -+ } -+ } -+ } -+ - fn install_archive_source(&mut self, cx: &mut Cx, config: TileSourceConfig) { -+ let config = self.jailed_tile_source(config); - self.archive_generation = self.archive_generation.wrapping_add(1).max(1); - self.style_epoch = self.style_epoch.wrapping_add(1).max(1); - if let Some(archive) = self.base_archive.as_mut() { -@@ -7610,9 +7641,9 @@ fn install_archive_source(&mut self, cx: &mut Cx, config: TileSourceConfig) { - self.mbtiles_path.clear(); - self.detail_mbtiles_path = detail_root_url.clone(); - self.bridge_dz_mbtiles_path = bridge_dz_path.clone(); -- self.base_archive = Some(MapTileArchive::http(root_url, workers.clone())); -+ self.base_archive = Some(MapTileArchive::http_for_heap(root_url, workers.clone(), self.source.heap_key())); - self.detail_archive = needs_separate_detail_archive(&config) -- .then(|| MapTileArchive::http(detail_root_url, workers)); -+ .then(|| MapTileArchive::http_for_heap(detail_root_url, workers, self.source.heap_key())); - self.use_local_mbtiles = true; - self.use_network = false; - } -@@ -9389,7 +9420,18 @@ fn request_tile( - } - - let query = overpass_query(tile_key); -- let endpoint = overpass_endpoint(tile_key, attempts, cx.seconds_since_app_start() as u64); -+ // The endpoint rotates across mirrors per tile and attempt. An app -+ // under a policy may list only some of them: use the next one it may -+ // reach rather than failing the tile on the mirror it may not. -+ let now = cx.seconds_since_app_start() as u64; -+ let heap_key = self.source.heap_key(); -+ let Some(endpoint) = (0..OVERPASS_MIRROR_TRIES) -+ .map(|k| overpass_endpoint(tile_key, attempts.wrapping_add(k), now)) -+ .find(|endpoint| crate::splash_policy::url_allowed(heap_key, endpoint)) -+ else { -+ self.mark_tile_failed(tile_key, "refused by the host's allowlist"); -+ return false; -+ }; - let mut request = HttpRequest::new(endpoint.to_string(), HttpMethod::POST); - request.set_header("Content-Type".to_string(), "text/plain".to_string()); - request.set_header("Accept".to_string(), "application/json".to_string()); -@@ -13557,7 +13599,7 @@ fn nav_update(&mut self, cx: &mut Cx, rect: Rect) { - // Where the DEVICE is: the last fix, else the card's declared - // centre (a fact it observed), else nothing — the camera does not - // move on its own. -- let fix = crate::makepad_draw::makepad_platform::gps::last_gps_fix() -+ let fix = crate::splash_policy::gps_fix_for_heap(self.source.heap_key()) - .map(|f| (f.lat, f.lon)) - .filter(|(lat, lon)| is_a_place(*lat, *lon)) - .or(Some((self.center_lat, self.center_lon))) -diff --git a/widgets/src/splash.rs b/widgets/src/splash.rs -index 2262d0f2b..53db34c04 100644 ---- a/widgets/src/splash.rs -+++ b/widgets/src/splash.rs -@@ -281,9 +281,13 @@ fn eval_styled_body(&mut self,cx:&mut Cx,preserve:bool) { - crate::desktop_style::apply_widgets(vm); - crate::script_eval!(vm, {mod.res = nil}); - crate::widget_async::apply_splash_isolate_mods(vm); -+ crate::widget_async::forget_granted_isolate_mods(heap_key); - }); - } - } -+ // Vocabulary this app's grants entitle it to, now that its policy -+ // is seated (see `register_splash_isolate_mod_for`). -+ crate::widget_async::apply_granted_isolate_mods(vm); - // Everything on `mod` that is not the body's own; whatever the run - // adds beyond this is the body's. - let mut known = module_keys(vm); -@@ -742,7 +746,7 @@ pub fn call_script_fn_with_strings(&mut self, cx: &mut Cx, name: LiveId, args: & - } - let vals: Vec = args - .iter() -- .map(|s| vm.new_string_with(|_vm, out| out.push_str(s))) -+ .map(|s| vm.bx.heap.new_string_from_str(s)) - .collect(); - vm.with_instruction_limit(WIDGET_SCRIPT_INSTRUCTION_LIMIT, |vm| { - vm.call(fnval, &vals); -@@ -1234,6 +1238,14 @@ pub fn register_agent_module(vm: &mut ScriptVm) { - let mut payload = String::new(); - vm.bx.heap.to_json_inner(payload_value, &mut payload); - -+ // A notify carries no sender, so the host acts on it as if its own -+ // card sent it. An app under a policy may only post one when it -+ // was granted `agent`; anything else would let an installed app -+ // drive the host's agent. -+ if let Err(reason) = crate::splash_policy::service_allowed(vm.bx.heap.heap_key(), "agent.notify") { -+ log!("splash: refused agent.notify {event_id:?}: {reason}"); -+ return NIL; -+ } - Cx::post_action(SplashAction::Notify { event_id, payload }); - NIL - }, -@@ -1565,7 +1577,7 @@ pub fn register_agent_module(vm: &mut ScriptVm) { - let field_v = script_value!(vm, args.field); - let mut field = String::new(); - vm.bx.heap.cast_to_string(field_v, &mut field); -- let url = geocode_url(&name); -+ let url = geocode_url(&name, vm.bx.heap.heap_key()); - let out = match vm.host.cx_mut().script_data_fetch(&url) { - Some(bytes) => geocode_pluck(&bytes, field.trim()) - .unwrap_or_else(|| "—".to_string()), -@@ -1617,7 +1629,7 @@ pub fn register_agent_module(vm: &mut ScriptVm) { - let field_v = script_value!(vm, args.field); - let mut field = String::new(); - vm.bx.heap.cast_to_string(field_v, &mut field); -- let url = geocode_url(&name); -+ let url = geocode_url(&name, vm.bx.heap.heap_key()); - let key = if field.trim() == "lon" { "lon" } else { "lat" }; - let n = vm - .host -@@ -3054,7 +3066,7 @@ pub fn register_agent_module(vm: &mut ScriptVm) { - let field_v = script_value!(vm, args.field); - let mut field = String::new(); - vm.bx.heap.cast_to_string(field_v, &mut field); -- let out = pref_at(field.trim()).unwrap_or_else(|| "\u{2014}".to_string()); -+ let out = pref_at(vm.bx.heap.heap_key(), field.trim()).unwrap_or_else(|| "\u{2014}".to_string()); - vm.bx.heap.new_string_from_str(&out) - }, - ); -@@ -3079,7 +3091,7 @@ pub fn register_agent_module(vm: &mut ScriptVm) { - let field_v = script_value!(vm, args.field); - let mut field = String::new(); - vm.bx.heap.cast_to_string(field_v, &mut field); -- let Some(id) = collection_at("reading", index) else { -+ let Some(id) = collection_at(vm.bx.heap.heap_key(), "reading", index) else { - return vm.bx.heap.new_string_from_str("—"); - }; - let key = match field.trim().to_ascii_lowercase().as_str() { -@@ -3335,8 +3347,8 @@ pub fn register_agent_module(vm: &mut ScriptVm) { - let mut ticker = String::new(); - vm.bx.heap.cast_to_string(ticker_v, &mut ticker); - let ticker = ticker.trim(); -- let held = (0..collection_len("watchlist")) -- .filter_map(|i| collection_at("watchlist", i)) -+ let held = (0..collection_len_for(vm.bx.heap.heap_key(), "watchlist")) -+ .filter_map(|i| collection_at(vm.bx.heap.heap_key(), "watchlist", i)) - .any(|t| t == ticker); - vm.bx.heap.new_string_from_str(if held { "1" } else { "0" }) - }, -@@ -3360,7 +3372,7 @@ pub fn register_agent_module(vm: &mut ScriptVm) { - let field_v = script_value!(vm, args.field); - let mut field = String::new(); - vm.bx.heap.cast_to_string(field_v, &mut field); -- let Some(name) = collection_at("topics", index) else { -+ let Some(name) = collection_at(vm.bx.heap.heap_key(), "topics", index) else { - return vm.bx.heap.new_string_from_str("—"); - }; - // json_pluck walks a dot-path from the ROOT (it is not a text -@@ -3411,7 +3423,11 @@ pub fn register_agent_module(vm: &mut ScriptVm) { - id_lut!(link), - script_args_def!(field = NIL), - |vm, _args| { -- let url = LINK.read().map(|s| s.clone()).unwrap_or_default(); -+ let url = if crate::splash_policy::profile_allowed(vm.bx.heap.heap_key()) { -+ LINK.read().map(|s| s.clone()).unwrap_or_default() -+ } else { -+ String::new() -+ }; - vm.bx.heap.new_string_from_str(&url) - }, - ); -@@ -3725,7 +3741,9 @@ pub fn register_agent_module(vm: &mut ScriptVm) { - let field_v = script_value!(vm, args.field); - let mut field = String::new(); - vm.bx.heap.cast_to_string(field_v, &mut field); -- let fix = crate::makepad_draw::makepad_platform::gps::last_gps_fix(); -+ // Without the `location` grant this reads as "no fix yet", the -+ // state every card already handles. -+ let fix = crate::splash_policy::gps_fix_for_heap(vm.bx.heap.heap_key()); - match field.trim().to_ascii_lowercase().as_str() { - "ok" => ScriptValue::from_f64(if fix.is_some() { 1.0 } else { 0.0 }), - "lon" => ScriptValue::from_f64(fix.map(|f| f.lon).unwrap_or(-9999.0)), -@@ -3756,7 +3774,7 @@ pub fn register_agent_module(vm: &mut ScriptVm) { - let field_v = script_value!(vm, args.field); - let mut field = String::new(); - vm.bx.heap.cast_to_string(field_v, &mut field); -- let Some(ticker) = collection_at("watchlist", index) else { -+ let Some(ticker) = collection_at(vm.bx.heap.heap_key(), "watchlist", index) else { - // Past the end of the list. Empty rather than an em dash: a row - // that does not exist is not a row whose value failed to load. - return vm.bx.heap.new_string_from_str(""); -@@ -3819,14 +3837,14 @@ pub fn register_agent_module(vm: &mut ScriptVm) { - } else { - vm.bx.heap.cast_to_string(unit_value, &mut unit); - } -- let Some(name) = collection_at("cities", index) else { -+ let Some(name) = collection_at(vm.bx.heap.heap_key(), "cities", index) else { - return vm.bx.heap.new_string_from_str(""); - }; - // The one field that needs no network: it IS what was stored. - if field.trim() == "name" { - return vm.bx.heap.new_string_from_str(&name); - } -- let geo = geocode_url(&name); -+ let geo = geocode_url(&name, vm.bx.heap.heap_key()); - let (lat, lon) = match vm.host.cx_mut().script_data_fetch(&geo) { - None => { - let placeholder = vm.host.cx_mut().script_data_placeholder(&geo); -@@ -3879,7 +3897,7 @@ pub fn register_agent_module(vm: &mut ScriptVm) { - sys, - id_lut!(citiesnum), - script_args_def!(), -- |_vm, _args| ScriptValue::from_f64(collection_len("cities") as f64), -+ |vm, _args| ScriptValue::from_f64(collection_len_for(vm.bx.heap.heap_key(), "cities") as f64), - ); - - vm.add_method( -@@ -3959,7 +3977,7 @@ pub fn register_agent_module(vm: &mut ScriptVm) { - sys, - id_lut!(watchlistnum), - script_args_def!(), -- |_vm, _args| ScriptValue::from_f64(collection_len("watchlist") as f64), -+ |vm, _args| ScriptValue::from_f64(collection_len_for(vm.bx.heap.heap_key(), "watchlist") as f64), - ); - - vm.set_injected_global(id!(sys), sys.into()); -@@ -4017,7 +4035,10 @@ fn locale_field(field: &str) -> String { - } - } - --fn pref_at(field: &str) -> Option { -+fn pref_at(heap_key: usize, field: &str) -> Option { -+ if !crate::splash_policy::profile_allowed(heap_key) { -+ return None; -+ } - PREFS.read().ok()?.as_ref()?.get(field).cloned() - } - -@@ -4048,7 +4069,20 @@ pub fn collection_len(name: &str) -> usize { - .unwrap_or(0) - } - --fn collection_at(name: &str, index: usize) -> Option { -+/// [`collection_len`] as a given isolate may see it: empty without the -+/// `profile` grant, like a user who saved nothing. -+fn collection_len_for(heap_key: usize, name: &str) -> usize { -+ if crate::splash_policy::profile_allowed(heap_key) { -+ collection_len(name) -+ } else { -+ 0 -+ } -+} -+ -+fn collection_at(heap_key: usize, name: &str, index: usize) -> Option { -+ if !crate::splash_policy::profile_allowed(heap_key) { -+ return None; -+ } - COLLECTIONS - .read() - .ok()? -@@ -5502,7 +5536,7 @@ fn nav_step_field(route: &ParsedNavRoute, d: f64, field: &str) -> String { - /// card's. Detecting CJK by codepoint range is enough: the alternative is asking - /// the generating model to romanise names, which is another thing for it to get - /// silently wrong. --fn geocode_url(name: &str) -> String { -+fn geocode_url(name: &str, heap_key: usize) -> String { - let name = name.trim(); - // An EMPTY name means "where the device is" — the weather exemplar's - // `state city { shape: text, initial: "" }` is documented as exactly -@@ -5516,7 +5550,9 @@ fn geocode_url(name: &str) -> String { - // search, which resolves to nothing and keeps the placeholder path: - // never invent a place. - if name.is_empty() { -- if let Some(fix) = crate::makepad_draw::makepad_platform::gps::last_gps_fix() { -+ // The fix only for an app granted `location`: a blank name must not -+ // become a way to learn where the device is without asking. -+ if let Some(fix) = crate::splash_policy::gps_fix_for_heap(heap_key) { - return format!( - "https://photon.komoot.io/reverse?lat={:.2}&lon={:.2}&lang=en", - fix.lat, fix.lon -@@ -5894,3 +5930,45 @@ pub(crate) fn sim_clock_secs() -> f64 { - static START: OnceLock = OnceLock::new(); - START.get_or_init(std::time::Instant::now).elapsed().as_secs_f64() - } -+ -+#[cfg(test)] -+mod policy_gate_tests { -+ use super::*; -+ -+ /// `sys.gps(...)`, `sys.watchlistnum()` and `agent.notify` run inside a -+ /// real isolate: first unpoliced (the host's own card), then under a -+ /// policy that lacks each grant, then with the grants. -+ #[test] -+ fn a_policed_isolate_needs_grants_for_location_the_users_lists_and_the_agent() { -+ let mut cx = Cx::new(Box::new(|_, _| {})); -+ cx.with_vm(crate::script_mod); -+ crate::makepad_draw::makepad_platform::gps::set_gps_fix(52.37, 4.89, 10.0); -+ set_collections(std::collections::BTreeMap::from([( -+ "watchlist".to_string(), -+ vec!["NVDA".to_string(), "AAPL".to_string()], -+ )])); -+ let vm_id = cx.alloc_splash_vm(); -+ let read = |cx: &mut Cx| { -+ cx.with_script_vm_id(vm_id, |vm| { -+ register_agent_module(vm); -+ let ok = vm.eval(crate::makepad_script::script! { sys.gps("ok") }).as_number(); -+ let lists = vm.eval(crate::makepad_script::script! { sys.watchlistnum() }).as_number(); -+ (ok, lists) -+ }) -+ }; -+ let heap = cx.with_script_vm_id(vm_id, |vm| vm.bx.heap.heap_key()); -+ -+ assert_eq!(read(&mut cx), (Some(1.0), Some(2.0)), "the host's own card sees both"); -+ -+ crate::splash_policy::set_policy_for_heap(heap, vec!["net".into()], vec![], None); -+ assert_eq!(read(&mut cx), (Some(0.0), Some(0.0)), "no grant reads as no fix and no saved lists"); -+ assert!(crate::splash_policy::service_allowed(heap, "agent.notify").is_err()); -+ -+ crate::splash_policy::set_policy_for_heap(heap, vec!["location".into(), "profile".into()], vec![], None); -+ assert_eq!(read(&mut cx), (Some(1.0), Some(2.0))); -+ -+ crate::splash_policy::gc_policies(&[heap]); -+ set_collections(Default::default()); -+ cx.free_splash_vm(vm_id); -+ } -+} -diff --git a/widgets/src/splash_host.rs b/widgets/src/splash_host.rs -index d1c7a0c25..81529af28 100644 ---- a/widgets/src/splash_host.rs -+++ b/widgets/src/splash_host.rs -@@ -136,6 +136,18 @@ pub fn take_splash_host_requests() -> Vec { - BRIDGE.with(|b| std::mem::take(&mut b.borrow_mut().queue)) - } - -+/// Drains only the requests from these isolates, leaving every other host's -+/// queued: a host that runs some isolates (an app runner) must not swallow -+/// the requests of isolates another host answers. -+pub fn take_splash_host_requests_for(heap_keys: &[usize]) -> Vec { -+ BRIDGE.with(|b| { -+ let mut b = b.borrow_mut(); -+ let (mine, rest): (Vec<_>, Vec<_>) = std::mem::take(&mut b.queue).into_iter().partition(|r| heap_keys.contains(&r.heap_key)); -+ b.queue = rest; -+ mine -+ }) -+} -+ - /// Answers one request: `Ok(json)` becomes `{ok: true, data: }`, - /// `Err(msg)` becomes `{ok: false, error: msg}`. Returns what happened, so a - /// host can log undeliverable answers instead of wondering why an app hangs. -@@ -171,7 +183,7 @@ pub fn splash_host_respond( - let data = parser.read_json(json, &mut vm.bx.heap); - (true, data, NIL) - } -- Err(msg) => (false, NIL, vm.new_string_with(|_vm, s| s.push_str(msg))), -+ Err(msg) => (false, NIL, vm.bx.heap.new_string_from_str(msg)), - }; - let obj = vm.bx.heap.new_object(); - let trap = vm.bx.threads.cur().trap.pass(); -@@ -198,6 +210,11 @@ pub fn splash_host_respond( - } - }); - }); -+ // What the callback asked of its widgets (`ui.list.render()`) is queued, -+ // and the queue is worked on the next event. An answer delivered after -+ // the host handled its event would otherwise wait for the person to -+ // touch the screen. -+ SignalToUI::set_ui_signal(); - if !contained { - // The callback panicked; the panic was contained and that isolate is - // suspect, but the host app carries on. -@@ -262,7 +279,7 @@ pub fn script_mod(vm: &mut ScriptVm) { - return match callback { - Some(callback) => { - let obj = vm.bx.heap.new_object(); -- let error = vm.new_string_with(|_vm, s| s.push_str(&reason)); -+ let error = vm.bx.heap.new_string_from_str(&reason); - let trap = vm.bx.threads.cur().trap.pass(); - vm.bx.heap.set_value(obj, id!(is_ok).into(), false.into(), trap); - vm.bx.heap.set_value(obj, id!(data).into(), NIL, trap); -@@ -371,6 +388,19 @@ fn take_drains_and_gc_drops_dead_state() { - }); - } - -+ #[test] -+ fn a_host_takes_only_its_own_isolates_requests() { -+ let _ = take_splash_host_requests(); -+ queue_raw(31, 1, "mail.list"); -+ queue_raw(32, 2, "weather.get"); -+ let mine = take_splash_host_requests_for(&[31]); -+ assert_eq!(mine.len(), 1); -+ assert_eq!(mine[0].service, "mail.list"); -+ let rest = take_splash_host_requests(); -+ assert_eq!(rest.len(), 1, "the other host's request stays queued"); -+ assert_eq!(rest[0].heap_key, 32); -+ } -+ - #[test] - fn tags_and_caps_are_per_heap() { - set_tag_for_heap(1, Some("one".into())); -diff --git a/widgets/src/splash_policy.rs b/widgets/src/splash_policy.rs -index 9bb895860..04cf78b36 100644 ---- a/widgets/src/splash_policy.rs -+++ b/widgets/src/splash_policy.rs -@@ -8,7 +8,13 @@ - //! - //! - [`service_allowed`] — before a `host.request` is queued (ADR 0002 §2); - //! - [`url_allowed`] — from every network path, through the gate installed --//! in `makepad-script-std` (ADR 0002 §3); -+//! in `makepad-script-std` (ADR 0002 §3): `net.http_request`, -+//! `net.web_socket`, `sys.*` data fetches, artwork, map tiles, web cards -+//! and a `Video`'s network source; -+//! - [`sockets_allowed`] — before `net.http_server` listens or -+//! `net.socket_stream` connects. Neither names a URL a host list could -+//! judge, and no capability covers them (`net` is requests to the listed -+//! hosts), so a policed isolate gets neither; - //! - [`charge`] — after every evaluation and callback, against a cumulative - //! instruction budget (ADR 0002 §4). - //! -@@ -42,6 +48,9 @@ pub struct HeapPolicy { - /// Set (or replace) the policy for a heap. Enforcement starts here: a heap - /// that never had this called is not enforced. - pub fn set_policy_for_heap(heap_key: usize, capabilities: Vec, hosts: Vec, instruction_budget: Option) { -+ // A policy nothing consults is not a policy: whoever sets one, the gates -+ // that read it are in place from here on. -+ install_url_gate(); - POLICIES.with(|p| { - let mut p = p.borrow_mut(); - let used = p.get(&heap_key).map(|old| old.instructions_used).unwrap_or(0); -@@ -110,6 +119,139 @@ pub fn url_allowed(heap_key: usize, url: &str) -> bool { - }) - } - -+/// May this heap open a listening server or a raw socket? Only when it is -+/// not policed. A server answers whoever connects and a raw stream speaks any -+/// protocol to any port; a contained app's way out is a request to a listed -+/// host, and nothing wider. -+pub fn sockets_allowed(heap_key: usize) -> bool { -+ !is_enforced(heap_key) -+} -+ -+/// May this heap know where the device is? Location is a service family -+/// like any other, granted as `location`; an unpoliced heap keeps the old -+/// behaviour. Every reader of the platform fix on a script's behalf asks -+/// here: `sys.gps`, the blank-name geocode fallback, the map's follow camera. -+pub fn location_allowed(heap_key: usize) -> bool { -+ service_allowed(heap_key, "location.get").is_ok() -+} -+ -+/// The device's last GPS fix as this heap may see it: `None` without the -+/// `location` grant, exactly as if the device had no fix yet, so a card's -+/// no-fix path is also its no-permission path. -+pub fn gps_fix_for_heap(heap_key: usize) -> Option { -+ if location_allowed(heap_key) { -+ crate::makepad_draw::makepad_platform::gps::last_gps_fix() -+ } else { -+ None -+ } -+} -+ -+/// May this heap read what the user keeps in the host: saved lists -+/// (`sys.watchlist`, cities, reading, topics), stored preferences and the page -+/// open in the reader. These are published process-wide for the host's own -+/// cards; an app under a policy needs the `profile` grant to see them. -+pub fn profile_allowed(heap_key: usize) -> bool { -+ service_allowed(heap_key, "profile.read").is_ok() -+} -+ -+/// A file a widget was told to read, as this heap may read it. An unpoliced -+/// heap reads the path as given. A policed heap's paths are app-visible paths -+/// inside its storage jail, so a card cannot point a widget (a map archive, -+/// say) at a file of the host's; with no jail, it reads nothing. -+pub fn local_path_for_heap(heap_key: usize, path: &str) -> Option { -+ if !is_enforced(heap_key) { -+ return Some(path.to_string()); -+ } -+ let root = crate::splash_storage::root_for_heap(heap_key)?; -+ let real = crate::splash_storage::resolve_jailed(&root, path).ok()?; -+ crate::splash_storage::verify_no_symlinks(&root, &real).ok()?; -+ Some(real.to_string_lossy().into_owned()) -+} -+ -+/// The hosts a policed heap may reach, or `None` for an unpoliced heap. For -+/// a surface the gate cannot stand in front of — a system WebView fetches on -+/// its own — so it can be told the same list. -+pub fn hosts_for_heap(heap_key: usize) -> Option> { -+ POLICIES.with(|p| p.borrow().get(&heap_key).map(|policy| policy.hosts.clone())) -+} -+ -+/// The host of `url` when it is https and names a public host: not loopback, -+/// private, link-local, shared or unspecified, not a single-label or -+/// `.local`/`.internal`/`.localhost` name, and not an IP written in a form -+/// only some resolvers read as one (`0x7f.1`). What a grant to reach "any -+/// public page" may reach, and nothing on the device's own network. -+pub fn public_https_host(url: &str) -> Result { -+ if !url.get(..8).is_some_and(|scheme| scheme.eq_ignore_ascii_case("https://")) { -+ return Err("only https:// URLs are allowed".into()); -+ } -+ let host = makepad_script_std::url_host(url).ok_or("missing host")?; -+ let bare = host.trim_start_matches('[').trim_end_matches(']'); -+ if let Ok(ip) = bare.parse::() { -+ return if is_public_ip(ip) { Ok(host) } else { Err(format!("host not permitted (private/internal): {host}")) }; -+ } -+ let last = host.rsplit('.').next().unwrap_or(""); -+ let numeric = last.bytes().all(|b| b.is_ascii_digit()) || last.starts_with("0x"); -+ if numeric || !host.contains('.') || host.starts_with('[') { -+ return Err(format!("host not permitted (not a public name): {host}")); -+ } -+ if host == "localhost" || [".localhost", ".internal", ".local", ".lan", ".home.arpa"].iter().any(|s| host.ends_with(s)) { -+ return Err(format!("host not permitted (private/internal): {host}")); -+ } -+ Ok(host) -+} -+ -+fn is_public_ip(ip: std::net::IpAddr) -> bool { -+ use std::net::IpAddr; -+ match ip { -+ IpAddr::V4(v4) => { -+ let [a, b, ..] = v4.octets(); -+ !(v4.is_loopback() -+ || v4.is_private() -+ || v4.is_link_local() -+ || v4.is_unspecified() -+ || v4.is_broadcast() -+ || v4.is_documentation() -+ || v4.is_multicast() -+ || a == 0 -+ || (a == 100 && (64..128).contains(&b))) -+ } -+ IpAddr::V6(v6) => { -+ if let Some(v4) = v6.to_ipv4_mapped() { -+ return is_public_ip(IpAddr::V4(v4)); -+ } -+ let first = v6.segments()[0]; -+ !(v6.is_loopback() -+ || v6.is_unspecified() -+ || v6.is_multicast() -+ || (first & 0xfe00) == 0xfc00 -+ || (first & 0xffc0) == 0xfe80) -+ } -+ } -+} -+ -+/// May this heap load `url` as media (an image, artwork)? Its host list -+/// answers first; the `images` grant adds any public https host, for an app -+/// that shows pictures from wherever its content links (a feed reader's -+/// thumbnails). A load is still a request, so the grant is its own consent. -+pub fn media_allowed(heap_key: usize, url: &str) -> bool { -+ url_allowed(heap_key, url) -+ || (is_enforced(heap_key) -+ && may_run(heap_key) -+ && service_allowed(heap_key, "images.any").is_ok() -+ && public_https_host(url).is_ok()) -+} -+ -+/// May this heap open `url` as a page in the system WebView? Its host list -+/// answers first; the `web` grant adds any public https page, for a reader. -+/// Such a page gets no bridge into the app. -+pub fn page_allowed(heap_key: usize, url: &str) -> bool { -+ url_allowed(heap_key, url) -+ || (is_enforced(heap_key) -+ && may_run(heap_key) -+ && service_allowed(heap_key, "web.open").is_ok() -+ && public_https_host(url).is_ok()) -+} -+ - /// Record `instructions` run by this heap. Returns false once the budget is - /// spent, and stays false: the heap is exhausted from then on. - pub fn charge(heap_key: usize, instructions: u64) -> bool { -@@ -143,6 +285,8 @@ pub(crate) fn install_url_gate() { - INSTALLED.with(|installed| { - if !installed.get() { - makepad_script_std::set_script_url_gate(Some(url_allowed)); -+ makepad_script_std::set_script_media_gate(Some(media_allowed)); -+ makepad_script_std::set_script_socket_gate(Some(sockets_allowed)); - installed.set(true); - } - }); -@@ -226,6 +370,95 @@ fn replacing_a_policy_keeps_what_was_already_spent() { - gc_policies(&[905]); - } - -+ #[test] -+ fn location_needs_the_location_grant() { -+ gc_policies(&[908]); -+ assert!(location_allowed(908), "an unpoliced heap reads the fix as before"); -+ set_policy_for_heap(908, vec!["net".into()], vec![], None); -+ assert!(!location_allowed(908)); -+ assert!(gps_fix_for_heap(908).is_none(), "no grant reads as no fix"); -+ set_policy_for_heap(908, vec!["location".into()], vec![], None); -+ assert!(location_allowed(908)); -+ assert!(!profile_allowed(908), "location does not cover the user's saved lists"); -+ set_policy_for_heap(908, vec!["profile".into()], vec![], None); -+ assert!(profile_allowed(908)); -+ gc_policies(&[908]); -+ } -+ -+ #[test] -+ fn a_policed_heap_reads_local_files_only_inside_its_jail() { -+ gc_policies(&[909]); -+ assert_eq!(local_path_for_heap(909, "/etc/hosts").as_deref(), Some("/etc/hosts")); -+ set_policy_for_heap(909, vec![], vec![], None); -+ assert!(local_path_for_heap(909, "maps/world.mkmap").is_none(), "no jail, no files"); -+ crate::splash_storage::set_root_for_heap(909, Some("/jail/app".into())); -+ assert_eq!( -+ local_path_for_heap(909, "maps/world.mkmap").as_deref(), -+ Some("/jail/app/maps/world.mkmap") -+ ); -+ assert!(local_path_for_heap(909, "../../etc/hosts").is_none(), "no climbing out"); -+ crate::splash_storage::set_root_for_heap(909, None); -+ gc_policies(&[909]); -+ } -+ -+ #[test] -+ fn a_public_host_is_https_and_off_the_devices_network() { -+ for ok in ["https://news.ycombinator.com/", "https://CDN.Example.org:8443/a.jpg", "https://8.8.8.8/", "https://[2606:4700::1111]/"] { -+ assert!(public_https_host(ok).is_ok(), "{ok}"); -+ } -+ for bad in [ -+ "http://example.com/", -+ "https://localhost/", -+ "https://127.0.0.1/", -+ "https://10.0.0.8/", -+ "https://172.20.1.1/", -+ "https://192.168.1.1/", -+ "https://169.254.169.254/latest/meta-data", -+ "https://100.64.0.1/", -+ "https://0.0.0.0/", -+ "https://[::1]/", -+ "https://[fd00::1]/", -+ "https://[fe80::1]/", -+ "https://[::ffff:127.0.0.1]/", -+ "https://0x7f.1/", -+ "https://2130706433/", -+ "https://router/", -+ "https://printer.local/", -+ "https://nas.home.arpa/", -+ "https://evil.com@127.0.0.1/", -+ ] { -+ assert!(public_https_host(bad).is_err(), "{bad}"); -+ } -+ } -+ -+ #[test] -+ fn images_and_web_grants_open_public_https_only() { -+ set_policy_for_heap(910, vec!["net".into()], vec!["hn.algolia.com".into()], None); -+ assert!(media_allowed(910, "https://hn.algolia.com/logo.png"), "its own hosts, as before"); -+ assert!(!media_allowed(910, "https://cdn.example.org/a.jpg")); -+ assert!(!page_allowed(910, "https://example.org/story")); -+ set_policy_for_heap(910, vec!["net".into(), "images".into(), "web".into()], vec!["hn.algolia.com".into()], None); -+ assert!(media_allowed(910, "https://cdn.example.org/a.jpg")); -+ assert!(page_allowed(910, "https://example.org/story")); -+ assert!(!media_allowed(910, "https://192.168.1.1/a.jpg"), "never the device's network"); -+ assert!(!page_allowed(910, "http://example.org/story"), "never plain http"); -+ assert!(!url_allowed(910, "https://cdn.example.org/a.jpg"), "neither grant widens requests"); -+ gc_policies(&[910]); -+ assert!(media_allowed(910, "https://anything.example/"), "an unpoliced heap, as before"); -+ } -+ -+ #[test] -+ fn a_policed_heap_opens_no_server_and_no_raw_socket_whatever_it_holds() { -+ gc_policies(&[911]); -+ assert!(sockets_allowed(911), "an unpoliced heap, as before"); -+ set_policy_for_heap(911, vec!["net".into(), "storage".into(), "web".into()], vec!["127.0.0.1".into()], None); -+ assert!(!sockets_allowed(911), "no grant and no listed host covers a socket"); -+ assert!(!makepad_script_std::script_sockets_allowed(911), "setting a policy installs the gate"); -+ assert!(makepad_script_std::script_url_allowed(911, "ws://127.0.0.1:9/"), "the url gate is in too"); -+ gc_policies(&[911]); -+ assert!(makepad_script_std::script_sockets_allowed(911)); -+ } -+ - #[test] - fn gc_forgets_a_heap_entirely() { - set_policy_for_heap(906, vec![], vec![], Some(1)); -diff --git a/widgets/src/splash_storage.rs b/widgets/src/splash_storage.rs -index 4b91cb6bf..778e6e745 100644 ---- a/widgets/src/splash_storage.rs -+++ b/widgets/src/splash_storage.rs -@@ -111,6 +111,19 @@ pub(crate) fn gc_roots(dead_heaps: &[usize]) { - }); - } - -+/// Whether a heap's jail is under its byte quota: what a native writer -+/// into the jail (the camera) checks before it adds a file. -+pub(crate) fn heap_has_room(heap_key: usize) -> bool { -+ let Some(root) = root_for_heap(heap_key) else { return false }; -+ let quota = JAIL_QUOTAS.with(|q| q.borrow().get(&heap_key).copied()).unwrap_or(MAX_TOTAL_BYTES); -+ jail_usage(&root).0 < quota -+} -+ -+/// The sandbox root assigned to a heap, if any. -+pub(crate) fn root_for_heap(heap_key: usize) -> Option { -+ SANDBOX_ROOTS.with(|r| r.borrow().get(&heap_key).cloned()) -+} -+ - fn root_for_vm(vm: &ScriptVm) -> Option { - let heap_key = vm.bx.heap.heap_key(); - SANDBOX_ROOTS.with(|r| r.borrow().get(&heap_key).cloned()) -@@ -155,7 +168,7 @@ pub fn resolve_jailed(root: &Path, virtual_path: &str) -> Result Result<(), String> { -+pub(crate) fn verify_no_symlinks(root: &Path, real: &Path) -> Result<(), String> { - let mut cursor = root.to_path_buf(); - let rel = real.strip_prefix(root).map_err(|_| "path escapes the app's storage")?; - for comp in rel.components() { -@@ -233,7 +246,21 @@ pub fn script_mod(vm: &mut ScriptVm) { - let path = script_value!(vm, args.path); - match target(vm, path) { - Ok((_root, real)) => match std::fs::read_to_string(&real) { -- Ok(text) => vm.new_string_with(|_vm, s| s.push_str(&text)).into(), -+ Ok(text) => vm.bx.heap.new_string_from_str(&text).into(), -+ Err(_) => script_err_io!(vm.trap(), "file not found"), -+ }, -+ Err(e) => script_err_io!(vm.trap(), "{}", e), -+ } -+ }); -+ -+ // `fs.read_bytes(path)`: a file as a byte array, for what is not text (a -+ // photo the camera took, to hand to an Image). Charged to the heap like -+ // any allocation. -+ vm.add_method(fs, id_lut!(read_bytes), script_args_def!(path = NIL), |vm, args| { -+ let path = script_value!(vm, args.path); -+ match target(vm, path) { -+ Ok((_root, real)) => match std::fs::read(&real) { -+ Ok(bytes) => vm.bx.heap.new_array_from_vec_u8(bytes).into(), - Err(_) => script_err_io!(vm.trap(), "file not found"), - }, - Err(e) => script_err_io!(vm.trap(), "{}", e), -diff --git a/widgets/src/text_input.rs b/widgets/src/text_input.rs -index dc0829f7f..5b60547f9 100644 ---- a/widgets/src/text_input.rs -+++ b/widgets/src/text_input.rs -@@ -525,6 +525,9 @@ - } - } - -+/// What a refused secret field shows instead of its placeholder. -+const SECRET_REFUSED: &str = "Apps can't ask for passwords"; -+ - #[derive(Script, Widget, Animator)] - pub struct TextInput { - #[uid] -@@ -890,6 +893,21 @@ pub fn empty_text(&self) -> &str { - &self.empty_text - } - -+ /// A contained app may not collect a secret. A password field (or one -+ /// that asks the keyboard for a password or a one-time code, which also -+ /// invites autofill) inside a policed isolate takes no input: secrets are -+ /// typed on the host's own sheet and go to the service that needs them, -+ /// never through the app. Host-owned isolates (a service's sheet) and the -+ /// shell's own UI are not policed and keep working. -+ pub(crate) fn refuses_secret(&self) -> bool { -+ let secret = self.is_password -+ || matches!( -+ self.content_type, -+ TextInputContentType::Password | TextInputContentType::NewPassword | TextInputContentType::OneTimeCode -+ ); -+ secret && crate::splash_policy::is_enforced(self.source.heap_key()) -+ } -+ - /// Text presented by the input, including its placeholder and masking. - pub fn display_text(&self) -> String { - if self.text.is_empty() { -@@ -1084,7 +1102,10 @@ fn layout_text(&mut self, cx: &mut Cx2d) { - - fn draw_text(&mut self, cx: &mut Cx2d) -> Rect { - let inner_walk = self.inner_walk(); -- let text_rect = if self.text.is_empty() { -+ let text_rect = if self.refuses_secret() { -+ self.draw_text -+ .draw_walk(cx, inner_walk, self.label_align, SECRET_REFUSED) -+ } else if self.text.is_empty() { - self.draw_text - .draw_walk(cx, inner_walk, self.label_align, &self.empty_text) - } else { -@@ -2484,6 +2505,14 @@ fn disabled(&self, cx: &Cx) -> bool { - } - - fn handle_event(&mut self, cx: &mut Cx, event: &Event, _scope: &mut Scope) { -+ if self.refuses_secret() { -+ // Never focused, so no keyboard opens and nothing is typed. -+ if !self.text.is_empty() { -+ self.text.clear(); -+ self.draw_bg.redraw(cx); -+ } -+ return; -+ } - if self.animator_handle_event(cx, event).must_redraw() { - self.draw_bg.redraw(cx); - self.draw_cursor.redraw(cx); -diff --git a/widgets/src/video.rs b/widgets/src/video.rs -index 1fee14b5e..55574072b 100644 ---- a/widgets/src/video.rs -+++ b/widgets/src/video.rs -@@ -1375,9 +1375,27 @@ fn maybe_prepare_playback(&mut self, cx: &mut Cx) { - return; - } - } -- VideoDataSource::Network { url } => VideoSource::Network(url.to_string()), -+ // The platform player fetches and reads on its own, so the -+ // isolate's policy is asked here, before it is handed a -+ // source: a network URL answers to the host list like any -+ // request, a path to the app's storage jail. -+ VideoDataSource::Network { url } => { -+ let heap_key = self.source_ref.heap_key(); -+ if !crate::splash_policy::url_allowed(heap_key, url) { -+ error!("Video source refused by the host's allowlist: {url}"); -+ self.should_prepare_playback = false; -+ return; -+ } -+ VideoSource::Network(url.to_string()) -+ } - VideoDataSource::Filesystem { path } => { -- VideoSource::Filesystem(path.to_string()) -+ let heap_key = self.source_ref.heap_key(); -+ let Some(path) = crate::splash_policy::local_path_for_heap(heap_key, path) else { -+ error!("Video source refused: {path} is outside this app's storage"); -+ self.should_prepare_playback = false; -+ return; -+ }; -+ VideoSource::Filesystem(path) - } - VideoDataSource::Camera { - input_id, -diff --git a/widgets/src/view.rs b/widgets/src/view.rs -index d02cf4902..ad333ec3e 100644 ---- a/widgets/src/view.rs -+++ b/widgets/src/view.rs -@@ -139,6 +139,10 @@ pub struct View { - script_async: ScriptAsyncCalls, - #[rust] - applying_style_render: bool, -+ /// An `on_render` result is being applied: its children are built fresh -+ /// rather than applied over the previous render's (see `on_after_apply`). -+ #[rust] -+ applying_render: bool, - #[rust] - item_tap_live: bool, - -@@ -214,6 +218,7 @@ fn on_after_apply( - if !apply.is_eval() { - if let Some(obj) = value.as_object() { - let mut anon_index = 0usize; -+ let rendering = self.applying_render; - vm.vec_with(obj, |vm, vec| { - for kv in vec { - // Determine the id: use prefixed id if available, otherwise use numbered id for anonymous children -@@ -240,7 +245,19 @@ fn on_after_apply( - if let Some((_, node)) = - self.children.iter_mut().find(|(id2, _)| *id2 == id) - { -- node.script_apply(vm, apply, scope, kv.value); -+ // An `on_render` result is built fresh, as the first -+ // render was. Applied over the previous render's -+ // instance, a child keeps what the new object does not -+ // restate: a slot that held a card with a picture keeps -+ // the picture's named child, and a label re-laid-out -+ // with longer text lost its wrap width. A render is a -+ // function of the app's state, not an edit of the -+ // last one. -+ if rendering { -+ *node = WidgetRef::script_from_value_scoped(vm, scope, kv.value); -+ } else { -+ node.script_apply(vm, apply, scope, kv.value); -+ } - } else { - let widget = - WidgetRef::script_from_value_scoped(vm, scope, kv.value); -@@ -854,6 +871,28 @@ fn script_call( - method: LiveId, - args: ScriptValue, - ) -> ScriptAsyncResult { -+ // `ui.panel.set_visible(false)`: show and hide from script, so an app -+ // can keep a stateful child (a reader's web view) instead of -+ // re-rendering it away. -+ if method == live_id!(set_visible) { -+ let mut visible = None; -+ if let Some(args_obj) = args.as_object() { -+ let trap = vm.bx.threads.cur().trap.pass(); -+ visible = vm.bx.heap.vec_value(args_obj, 0, trap).as_bool(); -+ } -+ if let Some(visible) = visible { -+ if visible != self.visible { -+ self.visible = visible; -+ // A view that was hidden has no drawn area to invalidate, -+ // so its own redraw would repaint nothing. -+ vm.with_cx_mut(|cx| cx.redraw_all()); -+ } -+ } -+ return ScriptAsyncResult::Return(NIL); -+ } -+ if method == live_id!(is_visible) { -+ return ScriptAsyncResult::Return(self.visible.into()); -+ } - if method == live_id!(render) || method == live_id!(render_style) { - // `me` protos off `self.source`, and the caller's `args` object - // travels into the VM that owns `on_render` — both are heap values, -@@ -920,8 +959,10 @@ fn script_result(&mut self, vm: &mut ScriptVm, id: ScriptAsyncId, result: Script - let declaration = self.source.clone(); - let style=call.method()==id!(render_style); - self.applying_style_render=style; -+ self.applying_render=!style; - self.script_apply(vm, &if style {Apply::ScriptReapply}else{Apply::Reload}, &mut Scope::empty(), me_obj.into()); - self.applying_style_render=false; -+ self.applying_render=false; - self.source = declaration; - self.redraw(vm.cx_mut()); - } -diff --git a/widgets/src/web_card.rs b/widgets/src/web_card.rs -index 952bbfa3f..7a1dd6b16 100644 ---- a/widgets/src/web_card.rs -+++ b/widgets/src/web_card.rs -@@ -95,42 +95,7 @@ fn inject_widget_kit(html: &str) -> String { - /// adds the allowlist) and `download` (which doesn't — its bytes land in the - /// sandbox, so the SSRF block is the protection that matters). - fn check_https_public(url: &str) -> Result { -- let rest = url -- .strip_prefix("https://") -- .ok_or_else(|| "only https:// URLs are allowed".to_string())?; -- let host = rest -- .split(|c| c == '/' || c == '?' || c == '#') -- .next() -- .unwrap_or("") -- .split('@') -- .next_back() -- .unwrap_or("") -- .split(':') -- .next() -- .unwrap_or("") -- .to_ascii_lowercase(); -- if host.is_empty() { -- return Err("missing host".into()); -- } -- let blocked_prefix = ["127.", "10.", "192.168.", "169.254.", "0."]; -- let private_172 = host.starts_with("172.") -- && host -- .split('.') -- .nth(1) -- .and_then(|o| o.parse::().ok()) -- .map(|o| (16..=31).contains(&o)) -- .unwrap_or(false); -- if host == "localhost" -- || host == "::1" -- || host.ends_with(".localhost") -- || host.ends_with(".internal") -- || host.ends_with(".local") -- || blocked_prefix.iter().any(|p| host.starts_with(p)) -- || private_172 -- { -- return Err(format!("host not permitted (private/internal): {}", host)); -- } -- Ok(host) -+ crate::splash_policy::public_https_host(url) - } - - /// Enforce the `http.fetch` capability gate: SSRF guard + the host must be on the -@@ -148,6 +113,54 @@ fn fetch_host_allowed(url: &str) -> Result<(), String> { - } - } - -+/// The bridge tool a card under a policy may call, and the grant it needs. -+/// `None` = never, under a policy: the WebView bridge's files and downloads -+/// live outside the app's jail, and its share sheet and notices are the host's -+/// to raise. An unpoliced card (the host's own) keeps every tool. -+fn policed_tool_grant(tool: &str) -> Option<&'static str> { -+ match tool { -+ "ping" => Some(""), -+ "http.fetch" => Some("net.fetch"), -+ "clipboard.write" => Some("clipboard.write"), -+ _ => None, -+ } -+} -+ -+/// A Content-Security-Policy that holds a policed card's own document to its -+/// host list. The WebView fetches for itself, out of reach of the script -+/// gate; this is how the same allowlist follows it there. Inline script and -+/// style stay allowed (the kit and the card are inline); nothing may frame, -+/// and nothing loads from a host the manifest does not name. -+fn policy_csp(hosts: &[String]) -> String { -+ let mut sources = String::new(); -+ for host in hosts { -+ // A host list entry is a bare host (any port) or host:port. -+ if host.chars().all(|c| c.is_ascii_alphanumeric() || matches!(c, '.' | '-' | ':' | '[' | ']')) { -+ sources.push_str(" https://"); -+ sources.push_str(host); -+ } -+ } -+ format!( -+ "default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'{sources}; \ -+ img-src data: blob:{sources}; media-src blob:{sources}; font-src data:{sources}; \ -+ connect-src{none}{sources}; frame-src 'none'; form-action 'none'; base-uri 'none'", -+ none = if sources.is_empty() { " 'none'" } else { "" }, -+ ) -+} -+ -+/// Put `csp` at the very start of the head, ahead of anything that could -+/// fetch, so it governs the whole document. -+fn inject_csp(html: &str, csp: &str) -> String { -+ let tag = format!(""); -+ match html.find("") { -+ Some(i) => { -+ let at = i + "".len(); -+ format!("{}{}{}", &html[..at], tag, &html[at..]) -+ } -+ None => format!("{tag}{html}"), -+ } -+} -+ - script_mod! { - use mod.prelude.widgets_internal.* - -@@ -350,6 +363,11 @@ fn load_settled(&mut self, cx: &mut Cx) { - // inline HTML — used to isolate loadHTMLString from the overlay path. - if let Some(url) = self.html.trim().strip_prefix("URLTEST:") { - let url = url.trim().to_string(); -+ if !crate::splash_policy::page_allowed(self.source.heap_key(), &url) { -+ log!("web_card navigation refused by the host's allowlist: {url}"); -+ self.loaded_html = self.html.clone(); -+ return; -+ } - if !self.spawned { - cx.system_browser(id).spawn(&url); - self.spawned = true; -@@ -366,7 +384,10 @@ fn load_settled(&mut self, cx: &mut Cx) { - cx.system_browser(id).spawn("about:blank"); - self.spawned = true; - } -- let html = inject_widget_kit(&self.html); -+ let mut html = inject_widget_kit(&self.html); -+ if let Some(hosts) = crate::splash_policy::hosts_for_heap(self.source.heap_key()) { -+ html = inject_csp(&html, &policy_csp(&hosts)); -+ } - let base = self.base_url_or_default().to_string(); - cx.system_browser(id).set_html(&html, &base); - self.loaded_html = self.html.clone(); -@@ -396,6 +417,19 @@ fn reject(&mut self, cx: &mut Cx, call_id: i64, msg: &str) { - - /// Dispatch one `octos.invoke(tool, args)`. `args` is a JSON string from the card. - fn handle_invoke(&mut self, cx: &mut Cx, call_id: i64, tool: &str, args: &str) { -+ let heap_key = self.source.heap_key(); -+ if crate::splash_policy::is_enforced(heap_key) { -+ let allowed = match policed_tool_grant(tool) { -+ Some("") => Ok(()), -+ Some(grant) => crate::splash_policy::service_allowed(heap_key, grant), -+ None => Err(format!("{tool} is not available to installed apps")), -+ }; -+ if let Err(why) = allowed { -+ log!("web_card {tool} DENIED: {why}"); -+ self.reject(cx, call_id, &format!("{tool} denied: {why}")); -+ return; -+ } -+ } - match tool { - // Round-trip probe: echo the args object back untouched. - "ping" => { -@@ -407,7 +441,19 @@ fn handle_invoke(&mut self, cx: &mut Cx, call_id: i64, tool: &str, args: &str) { - // a host allowlist + SSRF guard before any request leaves the device. - "http.fetch" => match FetchArgs::deserialize_json(args) { - Ok(a) => { -- if let Err(why) = fetch_host_allowed(&a.url) { -+ let allowed = if crate::splash_policy::is_enforced(heap_key) { -+ // The app's own host list, still https and public. -+ check_https_public(&a.url).and_then(|_| { -+ if crate::splash_policy::url_allowed(heap_key, &a.url) { -+ Ok(()) -+ } else { -+ Err("host not on this app's allowlist".to_string()) -+ } -+ }) -+ } else { -+ fetch_host_allowed(&a.url) -+ }; -+ if let Err(why) = allowed { - log!("web_card http.fetch DENIED: {} ({})", a.url, why); - self.reject(cx, call_id, &format!("http.fetch denied: {}", why)); - } else { -@@ -715,6 +761,18 @@ fn draw_walk(&mut self, cx: &mut Cx2d, _scope: &mut Scope, walk: Walk) -> DrawSt - - // A DSL-set `url` (no inline html) navigates once on first draw, so a - // runsplash card can embed a live web pane via `WebCard{ url: "…" }`. -+ if self.html.is_empty() -+ && !self.url.is_empty() -+ && self.loaded_html != self.url -+ && !crate::splash_policy::page_allowed(self.source.heap_key(), &self.url) -+ { -+ // A card under a policy opens only pages on its host list, or -+ // any public https page with the `web` grant. The top-level -+ // document is what the gate can see; the page's own subresources -+ // are the page's, as in any browser, and it gets no bridge. -+ log!("web_card navigation refused by the host's allowlist: {}", self.url); -+ self.loaded_html = self.url.clone(); -+ } - if self.html.is_empty() && !self.url.is_empty() && self.loaded_html != self.url { - let id = self.browser_id(); - if !self.spawned { -@@ -774,3 +832,27 @@ pub fn detach(&self, cx: &mut Cx) { - } - } - } -+ -+#[cfg(test)] -+mod policy_tests { -+ use super::*; -+ -+ #[test] -+ fn a_policed_document_may_reach_only_its_hosts() { -+ let csp = policy_csp(&["api.example.com".into(), "127.0.0.1:5000".into(), "evil.com; script-src *".into()]); -+ assert!(csp.contains("connect-src https://api.example.com https://127.0.0.1:5000;")); -+ assert!(!csp.contains("evil.com"), "a malformed host cannot extend the policy"); -+ assert!(policy_csp(&[]).contains("connect-src 'none';")); -+ let html = inject_csp("x", &policy_csp(&[])); -+ assert!(html.starts_with(" the web views that heap's readers spawned. -+ static OPENED: std::cell::RefCell>> = Default::default(); -+} -+ -+/// Close every web view opened by readers in these (dead) heaps. -+pub(crate) fn gc_web_readers(cx: &mut Cx, dead_heaps: &[usize]) { -+ let ids: Vec = OPENED.with(|o| { -+ let mut o = o.borrow_mut(); -+ dead_heaps.iter().filter_map(|heap| o.remove(heap)).flatten().collect() -+ }); -+ for id in ids { -+ let mut browser = cx.system_browser(id); -+ browser.detach(); -+ browser.close(); -+ } -+} -+ -+#[derive(Script, ScriptHook, Widget)] -+pub struct WebReader { -+ #[deref] -+ view: View, -+ /// Called when a page is refused or fails to load; `error()` says why. -+ #[live] -+ on_error: ScriptFnRef, -+ -+ #[rust] -+ last_error: String, -+ #[rust] -+ url: Option, -+ #[rust] -+ spawned: bool, -+ #[rust] -+ open: bool, -+ #[rust] -+ overlay_visible: bool, -+ #[rust] -+ timer: Option, -+ #[rust] -+ drawn: bool, -+ #[rust] -+ failed: bool, -+} -+ -+impl WebReader { -+ /// Each reader owns its web view: its uid is unique in the process. -+ fn browser_id(&self) -> SystemBrowserId { -+ SystemBrowserId(LiveId(self.widget_uid().0)) -+ } -+ -+ fn heap_key(&self) -> usize { -+ self.view.source.heap_key() -+ } -+ -+ fn report(&mut self, cx: &mut Cx, why: String) { -+ log!("web reader: {why}"); -+ self.last_error = why; -+ if self.on_error.as_object() != ScriptObject::ZERO { -+ cx.widget_to_script_call(self.widget_uid(), NIL, self.view.source.clone(), self.on_error.clone(), &[]); -+ } -+ } -+ -+ /// Open `url`, when this app may. Returns whether it opened. -+ pub fn open(&mut self, cx: &mut Cx, url: &str) -> bool { -+ let heap = self.heap_key(); -+ if !crate::splash_policy::page_allowed(heap, url) { -+ self.report(cx, format!("refused {url}: not on this app's host list, and no `web` grant")); -+ return false; -+ } -+ // Navigable only for an app that may reach any public page: its -+ // links and redirects stay inside what it was granted. -+ let navigable = !crate::splash_policy::is_enforced(heap) -+ || crate::splash_policy::service_allowed(heap, "web.open").is_ok(); -+ let id = self.browser_id(); -+ if navigable { -+ cx.system_browser(id).spawn_navigable(url); -+ } else { -+ cx.system_browser(id).spawn(url); -+ } -+ if !self.spawned { -+ OPENED.with(|o| o.borrow_mut().entry(heap).or_default().push(id)); -+ } -+ self.spawned = true; -+ self.failed = false; -+ self.url = Some(url.to_string()); -+ self.open = true; -+ self.overlay_visible = true; -+ self.drawn = true; -+ if self.timer.is_none() { -+ self.timer = Some(cx.start_interval(WATCHDOG_SECONDS)); -+ } -+ self.view.redraw(cx); -+ true -+ } -+ -+ pub fn close(&mut self, cx: &mut Cx) { -+ if !self.open { -+ return; -+ } -+ self.open = false; -+ self.hide_overlay(cx); -+ if let Some(timer) = self.timer.take() { -+ cx.stop_timer(timer); -+ } -+ self.view.redraw(cx); -+ } -+ -+ fn hide_overlay(&mut self, cx: &mut Cx) { -+ if self.spawned && self.overlay_visible { -+ let area = self.view.area(); -+ cx.system_browser(self.browser_id()).update(area, false); -+ } -+ self.overlay_visible = false; -+ self.drawn = false; -+ } -+} -+ -+impl Widget for WebReader { -+ fn script_call(&mut self, vm: &mut ScriptVm, method: LiveId, args: ScriptValue) -> ScriptAsyncResult { -+ if method == live_id!(open) { -+ let mut url = None; -+ if let Some(args_obj) = args.as_object() { -+ let trap = vm.bx.threads.cur().trap.pass(); -+ let value = vm.bx.heap.vec_value(args_obj, 0, trap); -+ url = vm.bx.heap.cast_to_owned_string(value, "copying a reader url"); -+ } -+ let Some(url) = url else { return ScriptAsyncResult::Return(FALSE) }; -+ let opened = vm.with_cx_mut(|cx| self.open(cx, url.trim())); -+ return ScriptAsyncResult::Return(opened.into()); -+ } -+ if method == live_id!(close) { -+ vm.with_cx_mut(|cx| self.close(cx)); -+ return ScriptAsyncResult::Return(NIL); -+ } -+ if method == live_id!(is_open) { -+ return ScriptAsyncResult::Return(self.open.into()); -+ } -+ if method == live_id!(error) { -+ return ScriptAsyncResult::Return(vm.bx.heap.new_string_from_str(&self.last_error)); -+ } -+ if method == live_id!(url) { -+ let url = vm.bx.heap.new_string_from_str(self.url.as_deref().unwrap_or("")); -+ return ScriptAsyncResult::Return(url); -+ } -+ self.view.script_call(vm, method, args) -+ } -+ -+ fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) { -+ self.view.handle_event(cx, event, scope); -+ if self.timer.is_some_and(|timer| timer.is_event(event).is_some()) { -+ // Drawn since the last tick: ask for the redraw the next one -+ // looks for. Not drawn: the tile is off screen; hide. -+ if self.drawn { -+ self.drawn = false; -+ self.view.redraw(cx); -+ } else { -+ self.hide_overlay(cx); -+ } -+ } -+ if let Event::Actions(actions) = event { -+ for action in actions { -+ if let Some(err) = action.downcast_ref::() { -+ if err.browser_id == self.browser_id().0.get_value() && self.open { -+ self.failed = true; -+ self.hide_overlay(cx); -+ let why = format!("could not load {}: {}", self.url.as_deref().unwrap_or(""), err.description); -+ self.report(cx, why); -+ } -+ } -+ } -+ } -+ } -+ -+ fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep { -+ let step = self.view.draw_walk(cx, scope, walk); -+ if step.is_done() && self.spawned && (self.open || self.overlay_visible) { -+ let show = self.open && !self.failed; -+ let area = self.view.area(); -+ cx.system_browser(self.browser_id()).update(area, show); -+ if show { -+ self.overlay_visible = true; -+ self.drawn = true; -+ } -+ } -+ step -+ } -+} -diff --git a/widgets/src/widget.rs b/widgets/src/widget.rs -index 09209b1cb..b6e496a1b 100644 ---- a/widgets/src/widget.rs -+++ b/widgets/src/widget.rs -@@ -787,6 +787,28 @@ pub fn script_call( - if method == live_id!(visible) { - return ScriptAsyncResult::Return(self.visible().into()); - } -+ // Text likewise: a widget with `Widget::set_text` (an `Html` view's -+ // body, say) takes it from script without a `script_call` of its own. -+ if method == live_id!(set_text) { -+ let Some(args_obj) = args.as_object() else { -+ return ScriptAsyncResult::Return(NIL); -+ }; -+ let trap = vm.bx.threads.cur().trap.pass(); -+ let value = vm.bx.heap.vec_value(args_obj, 0, trap); -+ let Some(text) = vm.bx.heap.cast_to_owned_string(value, "copying widget text") else { -+ return ScriptAsyncResult::Return(script_err_wrong_value!( -+ vm.trap(), -+ "set_text expects a string, got {:?}", -+ value.value_type() -+ )); -+ }; -+ vm.with_cx_mut(|cx| self.set_text(cx, &text)); -+ return ScriptAsyncResult::Return(NIL); -+ } -+ if method == live_id!(text) { -+ let text = self.text(); -+ return ScriptAsyncResult::Return(vm.bx.heap.new_string_from_str(&text)); -+ } - ScriptAsyncResult::MethodNotFound - } - -diff --git a/widgets/src/widget_async.rs b/widgets/src/widget_async.rs -index 63acffe8c..ffa296b59 100644 ---- a/widgets/src/widget_async.rs -+++ b/widgets/src/widget_async.rs -@@ -95,6 +95,58 @@ pub fn register_splash_isolate_mod(f: fn(&mut ScriptVm)) { - HOST_ISOLATE_MODS.with(|g| g.borrow_mut().push(f)); - } - -+thread_local! { -+ /// Host mods that only isolates holding a grant receive, with the grant. -+ static GRANTED_ISOLATE_MODS: RefCell> = const { RefCell::new(Vec::new()) }; -+ /// heap key -> indices into `GRANTED_ISOLATE_MODS` already installed there. -+ static GRANTED_INSTALLED: RefCell>> = RefCell::new(HashMap::new()); -+} -+ -+/// Install a script mod only into the isolates granted `grant`. -+/// -+/// [`register_splash_isolate_mod`] reaches every isolate, so vocabulary a host -+/// registers for its own cards also lands in every installed app. A mod -+/// registered here is installed into an isolate under a policy only when that -+/// policy grants `grant` (a capability family such as `agent`, or an exact -+/// service name), and into an unpoliced isolate (the host's own surfaces) -+/// always. -+/// -+/// The grant is known only once the host has seated the policy, which is -+/// after allocation, so these mods install when a Splash first evaluates its -+/// body, before the body runs. A later policy that drops the grant does not -+/// remove a mod already installed: set the policy before the body loads. -+pub fn register_splash_isolate_mod_for(grant: &'static str, f: fn(&mut ScriptVm)) { -+ GRANTED_ISOLATE_MODS.with(|g| g.borrow_mut().push((grant, f))); -+} -+ -+/// Install the granted mods this isolate is entitled to and does not yet -+/// have. Cheap when there is nothing new to install. -+pub(crate) fn apply_granted_isolate_mods(vm: &mut ScriptVm) { -+ let heap_key = vm.bx.heap.heap_key(); -+ let mods = GRANTED_ISOLATE_MODS.with(|mods| mods.borrow().clone()); -+ for (index, (grant, install)) in mods.into_iter().enumerate() { -+ let installed = GRANTED_INSTALLED.with(|i| i.borrow().get(&heap_key).is_some_and(|done| done.contains(&index))); -+ if installed { -+ continue; -+ } -+ let entitled = !crate::splash_policy::is_enforced(heap_key) -+ || crate::splash_policy::service_allowed(heap_key, grant).is_ok(); -+ if entitled { -+ install(vm); -+ GRANTED_INSTALLED.with(|i| i.borrow_mut().entry(heap_key).or_default().push(index)); -+ } -+ } -+} -+ -+/// Forget which granted mods a heap has, so the next -+/// [`apply_granted_isolate_mods`] installs them again: a theme rebuild -+/// replaces the modules they bound into. -+pub(crate) fn forget_granted_isolate_mods(heap_key: usize) { -+ GRANTED_INSTALLED.with(|i| { -+ i.borrow_mut().remove(&heap_key); -+ }); -+} -+ - /// Reinstall trusted host vocabulary after a theme rebuild replaces mod.widgets. - /// Clone first so an installer can register another module without borrowing the list. - pub(crate) fn apply_splash_isolate_mods(vm: &mut ScriptVm) { -@@ -156,6 +208,13 @@ pub fn gc_dead_splash_isolates(cx: &mut Cx) { - crate::splash_storage::gc_roots(&dead_heaps); - crate::splash_host::gc_bridge(&dead_heaps); - crate::splash_policy::gc_policies(&dead_heaps); -+ for heap in &dead_heaps { -+ forget_granted_isolate_mods(*heap); -+ } -+ // Web views and cameras an app opened do not outlive it. -+ for heap in &dead_heaps { -+ crate::camera_preview::release_isolate_devices(cx, *heap); -+ } - crate::desktop_style::gc_heaps(cx,&dead_heaps); - // And the resource cache, which is keyed by heap ADDRESS: dropping a heap - // frees that address for the next isolate, and a leftover entry would hand -@@ -494,7 +553,8 @@ fn alloc_splash_vm_with_network(&mut self, network_enabled: bool) -> SplashVmId - // loads) and the network (web_url/http resources) without going - // through the gated net runtime. Raw sockets are gated separately: - // the stdlib's `net.socket_stream` errors when no net runtime is -- // configured, same as `net.http_request`. -+ // configured, same as `net.http_request`, and under a policy it -+ // and `net.http_server` are refused outright (splash_policy). - // `cx.quit` would let any mini-app close the whole host process. - let strip = crate::makepad_script::script! { - mod.fs = nil -@@ -1794,6 +1854,135 @@ fn install_probe(vm: &mut ScriptVm) { - cx.free_splash_vm(third); - } - -+ #[test] -+ fn a_granted_mod_reaches_only_the_isolates_holding_its_grant() { -+ fn install_granted_probe(vm: &mut ScriptVm) { -+ vm.eval(crate::makepad_script::script! { -+ mod.granted_probe = 7 -+ }); -+ } -+ let mut cx = Cx::new(Box::new(|_, _| {})); -+ cx.with_vm(crate::script_mod); -+ register_splash_isolate_mod_for("agent", install_granted_probe); -+ -+ let has_probe = |cx: &mut Cx, vm_id| { -+ cx.with_script_vm_id(vm_id, |vm| { -+ apply_granted_isolate_mods(vm); -+ !vm.eval(crate::makepad_script::script! { mod.granted_probe }).is_err() -+ }) -+ }; -+ let heap = |cx: &mut Cx, vm_id| cx.with_script_vm_id(vm_id, |vm| vm.bx.heap.heap_key()); -+ -+ let host_own = cx.alloc_splash_vm(); -+ let denied = cx.alloc_splash_vm(); -+ let granted = cx.alloc_splash_vm(); -+ let denied_heap = heap(&mut cx, denied); -+ let granted_heap = heap(&mut cx, granted); -+ crate::splash_policy::set_policy_for_heap(denied_heap, vec!["net".into()], vec![], None); -+ crate::splash_policy::set_policy_for_heap(granted_heap, vec!["agent".into()], vec![], None); -+ -+ assert!(has_probe(&mut cx, host_own), "an unpoliced isolate is the host's own"); -+ assert!(!has_probe(&mut cx, denied), "a policy without the grant never sees it"); -+ assert!(has_probe(&mut cx, granted)); -+ -+ crate::splash_policy::gc_policies(&[denied_heap, granted_heap]); -+ cx.free_splash_vm(host_own); -+ cx.free_splash_vm(denied); -+ cx.free_splash_vm(granted); -+ } -+ -+ /// A probe found `net.web_socket`, `net.socket_stream` and -+ /// `net.http_server` skipping the host list that `net.http_request` -+ /// answers to. Each is tried from a host-owned isolate and from a -+ /// contained app holding `net` and a listed host. -+ #[test] -+ fn a_contained_app_opens_sockets_and_servers_only_as_its_policy_allows() { -+ let run = |cx: &mut Cx, vm_id, code: String| { -+ cx.with_script_vm_id(vm_id, |vm| { -+ let script_mod = crate::makepad_script::ScriptMod { -+ cargo_manifest_path: String::new(), -+ module_path: "net_policy_probe".into(), -+ file: "net_policy_probe".into(), -+ line: 0, -+ column: 0, -+ code, -+ values: vec![], -+ }; -+ !vm.eval(script_mod).is_err() -+ }) -+ }; -+ let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); -+ let port = listener.local_addr().unwrap().port(); -+ let web_socket = |host: &str| format!( -+ "use mod.net\nnet.web_socket(\"ws://{host}:{port}/\" net.WebSocketEvents{{}})" -+ ); -+ let raw_socket = format!( -+ "use mod.net\nnet.socket_stream(net.SocketStreamOptions{{host: \"127.0.0.1\" port: \"{port}\"}})" -+ ); -+ let server = "use mod.net\nnet.http_server(net.HttpServerOptions{listen: \"127.0.0.1:0\"} net.HttpServerEvents{})".to_string(); -+ -+ let mut cx = Cx::new(Box::new(|_, _| {})); -+ cx.with_vm(crate::script_mod); -+ let host_own = cx.alloc_splash_vm_with_network(true); -+ let app = cx.alloc_splash_vm_with_network(true); -+ let app_heap = cx.with_script_vm_id(app, |vm| vm.bx.heap.heap_key()); -+ crate::splash_policy::set_policy_for_heap(app_heap, vec!["net".into()], vec!["127.0.0.1".into()], None); -+ -+ assert!(run(&mut cx, host_own, web_socket("127.0.0.1")), "a host-owned isolate opens a web socket"); -+ assert!(run(&mut cx, host_own, web_socket("elsewhere.example")), "to any host, as before"); -+ assert!(run(&mut cx, host_own, raw_socket.clone()), "and a raw socket"); -+ assert!(run(&mut cx, host_own, server.clone()), "and listens"); -+ -+ assert!(run(&mut cx, app, web_socket("127.0.0.1")), "an app reaches its listed host over a web socket"); -+ assert!(!run(&mut cx, app, web_socket("elsewhere.example")), "and no other"); -+ assert!(!run(&mut cx, app, raw_socket.clone()), "a raw socket is refused even to a listed host"); -+ assert!(!run(&mut cx, app, server.clone()), "and so is a listening server"); -+ -+ crate::splash_policy::set_policy_for_heap(app_heap, vec!["net".into()], vec![], None); -+ assert!(!run(&mut cx, app, web_socket("127.0.0.1")), "an empty host list reaches nothing"); -+ -+ crate::splash_policy::gc_policies(&[app_heap]); -+ cx.free_splash_vm(host_own); -+ cx.free_splash_vm(app); -+ drop(listener); -+ } -+ -+ #[test] -+ fn a_contained_app_cannot_draw_a_password_field() { -+ let mut cx = Cx::new(Box::new(|_, _| {})); -+ cx.with_vm(crate::script_mod); -+ let field = |cx: &mut Cx, vm_id| { -+ cx.with_script_vm_id(vm_id, |vm| { -+ let v = vm.eval(crate::makepad_script::script! { -+ use mod.prelude.widgets.* -+ View{ -+ secret := TextInput{is_password: true} -+ plain := TextInput{} -+ code := TextInput{content_type: TextInputContentType.OneTimeCode} -+ } -+ }); -+ WidgetRef::script_from_value(vm, v) -+ }) -+ }; -+ let refuses = |cx: &Cx, view: &WidgetRef, id| view.widget(cx, &[id]).borrow::().unwrap().refuses_secret(); -+ let host_own = cx.alloc_splash_vm(); -+ let app = cx.alloc_splash_vm(); -+ let app_heap = cx.with_script_vm_id(app, |vm| vm.bx.heap.heap_key()); -+ crate::splash_policy::set_policy_for_heap(app_heap, vec!["storage".into()], vec![], None); -+ -+ let sheet = field(&mut cx, host_own); -+ assert!(!refuses(&cx, &sheet, live_id!(secret)), "a host-owned sheet collects the password"); -+ let contained = field(&mut cx, app); -+ assert!(refuses(&cx, &contained, live_id!(secret)), "an app's password field takes nothing"); -+ assert!(refuses(&cx, &contained, live_id!(code)), "nor a one-time-code field, which invites autofill"); -+ assert!(!refuses(&cx, &contained, live_id!(plain))); -+ -+ drop((sheet, contained)); -+ crate::splash_policy::gc_policies(&[app_heap]); -+ cx.free_splash_vm(host_own); -+ cx.free_splash_vm(app); -+ } -+ - #[test] - fn entering_an_isolate_makes_it_the_current_vm_and_leaving_restores_the_outer_one() { - let mut cx = Cx::new(Box::new(|_, _| {})); -@@ -2067,6 +2256,67 @@ fn pane_refresh_drops_inserted_host() { - assert_eq!(n, 2, "render after pane refresh did not commit"); - } - -+ #[test] -+ fn a_rerender_builds_each_child_from_its_new_template() { -+ const RERENDER: &str = r#" -+ let items = [] -+ let WithExtra = View{ height: Fit extra := Label{ text: "x" } } -+ let Plain = View{ height: Fit } -+ fn load(){ -+ host.request("t.items", {}, fn(r){ -+ items = r.data.items -+ ui.item_list.render() -+ }) -+ } -+ item_list := View{ height: Fit, on_render: || { -+ for it in items { -+ if it.extra { WithExtra{} } else { Plain{} } -+ } -+ } } -+"#; -+ let mut cx = Cx::new(Box::new(|_, _| {})); -+ let template = cx.with_vm(|vm| { -+ crate::script_mod(vm); -+ let v = vm.eval(crate::makepad_script::script! { -+ use mod.prelude.widgets.* -+ use mod.widgets.* -+ View{ splash := Splash{} } -+ }); -+ vm.bx.heap.new_object_ref(v.as_object().unwrap()) -+ }); -+ let pane = cx.with_vm(|vm| { -+ let v = vm.eval(crate::makepad_script::script! { -+ use mod.prelude.widgets.* -+ View{ height: Fit } -+ }); -+ WidgetRef::script_from_value(vm, v) -+ }); -+ set_ui_root(&mut cx, &pane); -+ let host = cx.with_vm(|vm| WidgetRef::script_from_value(vm, template.as_object().into())); -+ cx.widget_tree_insert_child_deep(pane.widget_uid(), live_id!(apphost), host.clone()); -+ host.widget(&cx, &[live_id!(splash)]).set_text(&mut cx, RERENDER); -+ let item_list = host.widget(&cx, &[live_id!(item_list)]); -+ assert!(!item_list.is_empty()); -+ let splash = host.widget(&cx, &[live_id!(splash)]); -+ let render = |cx: &mut Cx, json: &str| { -+ assert!(splash.borrow_mut::().unwrap().call_script_fn(cx, live_id!(load), &[])); -+ pump_widget_async(cx); -+ let reqs = crate::splash_host::take_splash_host_requests(); -+ assert_eq!(reqs.len(), 1); -+ crate::splash_host::splash_host_respond(cx, reqs[0].heap_key, reqs[0].req_id, Ok(json)); -+ pump_widget_async(cx); -+ let first = item_list.borrow::().unwrap().children.first().map(|(_, w)| w.clone()).unwrap(); -+ !first.widget(cx, &[live_id!(extra)]).is_empty() -+ }; -+ assert!(render(&mut cx, r#"{"items":[{"extra":true}]}"#), "the first render has the extra child"); -+ assert!( -+ !render(&mut cx, r#"{"items":[{"extra":false}]}"#), -+ "a slot whose template changed keeps nothing of the old one" -+ ); -+ drop(host); -+ gc_dead_splash_isolates(&mut cx); -+ } -+ - #[test] - fn second_isolate_render_commits() { - let mut cx = Cx::new(Box::new(|_, _| {})); diff --git a/runtime-patches.lock.json b/runtime-patches.lock.json index 3c1db06b..bf00d958 100644 --- a/runtime-patches.lock.json +++ b/runtime-patches.lock.json @@ -1,17 +1,3 @@ { - "schema_version": 1, - "makepad": { - "base_revision": "1d3d383e84a66dbb18a4a860f505430c9d5b20f4", - "source_commit": "d7a834c74783e2032bc9e2cb9e693eaa2927b1d9", - "patch": "patches/runtime/makepad-contained-apps.patch", - "sha256": "70419d98644af0820816f8bbc480365fa214c71f96eea0dba4b541c7ec9d59a2", - "tree": "3889a3496226def432ecdf8131a9d12eb822e59b", - "product_fixes": [ - "Contained script apps (OctoSense-org/makepad#30): every path out of an isolate answers to its policy; images/web grants; WebReader, GestureView, SheetView, CameraPreview; parse_feed and fs.read_bytes.", - "Host services: a host takes only its own isolates' requests, and an asynchronous answer wakes the UI.", - "A contained app cannot collect a secret: password and one-time-code fields are inert in a policed isolate.", - "Text: a run's trailing space keeps its width unless the row wraps there; every widget takes text/set_text from script.", - "Sockets and servers: net.web_socket answers to the app's host list like net.http_request; net.socket_stream and net.http_server are refused in a policed isolate; a Video's network and file sources answer to the host list and the storage jail." - ] - } + "schema_version": 1 } diff --git a/tools/setup-native.py b/tools/setup-native.py index 1afa1b50..555162a0 100644 --- a/tools/setup-native.py +++ b/tools/setup-native.py @@ -4,9 +4,9 @@ Beside this repository, in one workspace directory: octoscript-makepad/ the release native-runtime.lock.json selects - makepad/, octoscript/ at the revisions that release's runtime.json pins; - makepad carries the reviewed product patch - runtime-patches.lock.json names (contained apps) + makepad/, octoscript/ at the revisions that release's runtime.json pins, + plus any reviewed patch runtime-patches.lock.json + names (none today) OctoSense-System-Apps/ at the revision native-apps.lock.json pins: the system app bundles system-apps.json selects, the Mail host service and the AppCard assistant From c86e58b9aa2c370159fba44fd2f6e3e3d27f4337 Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Sat, 26 Sep 2026 13:30:12 -0700 Subject: [PATCH 25/42] Pin Rinx saved-answer streaming fix --- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 513202c7..e502c2a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -356,7 +356,7 @@ dependencies = [ [[package]] name = "article-core" version = "0.1.0" -source = "git+https://github.com/hagency-org/Rinx.git?rev=ba5cd50e320742339806712d48dea2901dff3318#ba5cd50e320742339806712d48dea2901dff3318" +source = "git+https://github.com/hagency-org/Rinx.git?rev=d2469eabd7b2a582fab68192202af540f40f1168#d2469eabd7b2a582fab68192202af540f40f1168" dependencies = [ "blake3", "image", @@ -373,7 +373,7 @@ dependencies = [ [[package]] name = "article-makepad" version = "0.1.0" -source = "git+https://github.com/hagency-org/Rinx.git?rev=ba5cd50e320742339806712d48dea2901dff3318#ba5cd50e320742339806712d48dea2901dff3318" +source = "git+https://github.com/hagency-org/Rinx.git?rev=d2469eabd7b2a582fab68192202af540f40f1168#d2469eabd7b2a582fab68192202af540f40f1168" dependencies = [ "article-core", "libc", @@ -8369,7 +8369,7 @@ dependencies = [ [[package]] name = "rinx" version = "1.0.0-beta.1" -source = "git+https://github.com/hagency-org/Rinx.git?rev=ba5cd50e320742339806712d48dea2901dff3318#ba5cd50e320742339806712d48dea2901dff3318" +source = "git+https://github.com/hagency-org/Rinx.git?rev=d2469eabd7b2a582fab68192202af540f40f1168#d2469eabd7b2a582fab68192202af540f40f1168" dependencies = [ "anyhow", "arboard", @@ -8441,7 +8441,7 @@ dependencies = [ [[package]] name = "rinx-miniapp-core" version = "0.1.0" -source = "git+https://github.com/hagency-org/Rinx.git?rev=ba5cd50e320742339806712d48dea2901dff3318#ba5cd50e320742339806712d48dea2901dff3318" +source = "git+https://github.com/hagency-org/Rinx.git?rev=d2469eabd7b2a582fab68192202af540f40f1168#d2469eabd7b2a582fab68192202af540f40f1168" dependencies = [ "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index a2f70eb3..350d653b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,7 +71,7 @@ octosense-app-hub-app = { git = "https://github.com/OctoSense-org/OctoSense-App- octosense-mail-service = { path = "../OctoSense-System-Apps/apps/mail/host-service", optional = true } # Rinx (the Matrix client with Moments and articles) as an in-process module. # Built without its standalone window entry point. -rinx = { git = "https://github.com/hagency-org/Rinx.git", rev = "ba5cd50e320742339806712d48dea2901dff3318", default-features = false, features = ["octosense-module"], optional = true } +rinx = { git = "https://github.com/hagency-org/Rinx.git", rev = "d2469eabd7b2a582fab68192202af540f40f1168", default-features = false, features = ["octosense-module"], optional = true } # The assistant as a module, seated in the pane in-process: the web build's # only chat, a desktop option. Without the engine feature: no model runtime, # NoModel and the tool console (the child process keeps the real models). From 6b1be4d012576cf290ed47abd3e2671c4940924e Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Sat, 26 Sep 2026 15:00:29 -0700 Subject: [PATCH 26/42] Pin Rinx editor controls and hosted resource packaging fixes --- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e502c2a8..731db620 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -356,7 +356,7 @@ dependencies = [ [[package]] name = "article-core" version = "0.1.0" -source = "git+https://github.com/hagency-org/Rinx.git?rev=d2469eabd7b2a582fab68192202af540f40f1168#d2469eabd7b2a582fab68192202af540f40f1168" +source = "git+https://github.com/hagency-org/Rinx.git?rev=c85e6b4b0056d8cb2cedbc8269b952786c61e4c7#c85e6b4b0056d8cb2cedbc8269b952786c61e4c7" dependencies = [ "blake3", "image", @@ -373,7 +373,7 @@ dependencies = [ [[package]] name = "article-makepad" version = "0.1.0" -source = "git+https://github.com/hagency-org/Rinx.git?rev=d2469eabd7b2a582fab68192202af540f40f1168#d2469eabd7b2a582fab68192202af540f40f1168" +source = "git+https://github.com/hagency-org/Rinx.git?rev=c85e6b4b0056d8cb2cedbc8269b952786c61e4c7#c85e6b4b0056d8cb2cedbc8269b952786c61e4c7" dependencies = [ "article-core", "libc", @@ -8369,7 +8369,7 @@ dependencies = [ [[package]] name = "rinx" version = "1.0.0-beta.1" -source = "git+https://github.com/hagency-org/Rinx.git?rev=d2469eabd7b2a582fab68192202af540f40f1168#d2469eabd7b2a582fab68192202af540f40f1168" +source = "git+https://github.com/hagency-org/Rinx.git?rev=c85e6b4b0056d8cb2cedbc8269b952786c61e4c7#c85e6b4b0056d8cb2cedbc8269b952786c61e4c7" dependencies = [ "anyhow", "arboard", @@ -8441,7 +8441,7 @@ dependencies = [ [[package]] name = "rinx-miniapp-core" version = "0.1.0" -source = "git+https://github.com/hagency-org/Rinx.git?rev=d2469eabd7b2a582fab68192202af540f40f1168#d2469eabd7b2a582fab68192202af540f40f1168" +source = "git+https://github.com/hagency-org/Rinx.git?rev=c85e6b4b0056d8cb2cedbc8269b952786c61e4c7#c85e6b4b0056d8cb2cedbc8269b952786c61e4c7" dependencies = [ "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 350d653b..316623b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,7 +71,7 @@ octosense-app-hub-app = { git = "https://github.com/OctoSense-org/OctoSense-App- octosense-mail-service = { path = "../OctoSense-System-Apps/apps/mail/host-service", optional = true } # Rinx (the Matrix client with Moments and articles) as an in-process module. # Built without its standalone window entry point. -rinx = { git = "https://github.com/hagency-org/Rinx.git", rev = "d2469eabd7b2a582fab68192202af540f40f1168", default-features = false, features = ["octosense-module"], optional = true } +rinx = { git = "https://github.com/hagency-org/Rinx.git", rev = "c85e6b4b0056d8cb2cedbc8269b952786c61e4c7", default-features = false, features = ["octosense-module"], optional = true } # The assistant as a module, seated in the pane in-process: the web build's # only chat, a desktop option. Without the engine feature: no model runtime, # NoModel and the tool console (the child process keeps the real models). From 04c52e2368ee5a0026aec70d5bdf67bee1bd5ae3 Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Sat, 26 Sep 2026 15:06:01 -0700 Subject: [PATCH 27/42] Make AppCard strictly opt-in (--features app-appcard) AppCard (octos-app from OctoSense-System-Apps apps/appcard) interferes with the other apps, so it is not shipped for now. The shell no longer links it by default, through `mobile-apps`, or on Android/iOS (the target-specific dependency and the target cfg in linked_modules are gone). `--features app-appcard` still builds it on any target, so it can return with one flag. Without it there is no AppCard module, tile or launcher entry, and octos-core/octos-app are out of the build graph. - apps.rs: only `feature = "app-appcard"` links APPCARD_MODULE; the bundled catalog test expects appcard only with the feature; the isolate test is gated on it. - CI: check mobile-apps without octos (host and aarch64-linux-android cargo tree), and check the opt-in workspace build. - README (en, zh-CN) and the Android AppCard recipe: AppCard is opt-in and what it needs. Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/workflows/runtime.yml | 9 ++++++++- Cargo.toml | 11 ++++++++--- README.md | 18 +++++++++++------- README.zh-CN.md | 18 +++++++++++------- docs/android-appcard-build.md | 4 ++++ src/apps.rs | 14 ++++++++++---- 6 files changed, 52 insertions(+), 22 deletions(-) diff --git a/.github/workflows/runtime.yml b/.github/workflows/runtime.yml index af94a948..898a3cb8 100644 --- a/.github/workflows/runtime.yml +++ b/.github/workflows/runtime.yml @@ -19,7 +19,14 @@ jobs: - name: Compile with the shared runtime working-directory: workspace/OctoSense run: | - cargo check --locked --workspace --features mobile-apps + # AppCard (octos) is opt-in: the default and mobile-apps builds must + # not link it, and the opt-in must still build. + cargo check --locked --features mobile-apps + for target in "" "--target aarch64-linux-android"; do + out=$(cargo tree --locked --features mobile-apps $target -i octos-core 2>&1 || true) + grep -q "did not match any packages" <<<"$out" || { echo "::error::octos-core is linked without app-appcard ($target)"; echo "$out"; exit 1; } + done + cargo check --locked --workspace --features mobile-apps,app-appcard - name: Reject divergent runtime sources working-directory: workspace/OctoSense run: | diff --git a/Cargo.toml b/Cargo.toml index 6c2a036e..3336a673 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,6 +56,9 @@ makepad-photos = { git = "https://github.com/OctoSense-org/makepad.git", rev = " octosense-reference = { path = "apps/reference", optional = true } # The whole Octoscript-AppCard app hosted in a tile, in-process (Phase B of # the AppCard port): octos-app's AppShell — routing brain, cards, kernel agent. +# Not shipped for now: strictly opt-in with `--features app-appcard`, on +# every target (it is in neither `default`, `mobile-apps`, nor the native +# mobile dependencies below). octosense-appcard = { path = "apps/appcard", optional = true } # App Hub, as every OctoSense shell links it (OctoSense-App-Hub # crates/app-hub-app): the native store module (`apphub`), the Card runner @@ -83,7 +86,6 @@ makepad-aichat = { git = "https://github.com/OctoSense-org/makepad.git", rev = " # available modules into native mobile builds without requiring CLI flags. [target.'cfg(any(target_os = "android", target_os = "ios"))'.dependencies] octosense-reference = { path = "apps/reference" } -octosense-appcard = { path = "apps/appcard" } makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "cd812acde910e9c4523f83509356450eb8edf908", default-features = false } [features] @@ -93,7 +95,8 @@ default = ["app-hub"] # Also usable on desktop for testing the same embedded apps with --module. # Photos is the system app here; `app-photos` links Makepad's native Photos # module instead, for comparison (a linked module of the same id wins). -mobile-apps = ["app-reference", "app-sheets", "app-appcard", "app-hub"] +# AppCard is not among them: it ships only with `--features app-appcard`. +mobile-apps = ["app-reference", "app-sheets", "app-hub"] # App Hub and the host services its system apps call. app-hub = ["dep:octosense-app-hub-app", "dep:octosense-mail-service"] app-reference = ["dep:octosense-reference"] @@ -101,7 +104,9 @@ app-reference = ["dep:octosense-reference"] app-sheets = ["dep:makepad-sheets"] # Photos (the picture wall) linked in as a module, the same way. app-photos = ["dep:makepad-photos"] -# The AppCard module (one L0 weather card in a tile) linked in. +# The AppCard module (the assistant in a tile, with the octos kernel) linked +# in. Opt-in only, on desktop and native mobile alike: AppCard is not shipped +# by default for now (it interfered with the other apps). app-appcard = ["dep:octosense-appcard"] # The aichat module linked in (the pane seats it in-process where there are # no processes, or when `aichat` is switched to module hosting). diff --git a/README.md b/README.md index 9df7fa52..ecb67045 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ OctoSense-Desktop is the desktop shell of [OctoSense](https://github.com/OctoSen | [OctoScript-App-Design-Flow](https://github.com/OctoSense-org/OctoScript-App-Design-Flow) | Where apps are designed, built and published to the App Hub. | | [OctoScript-Makepad](https://github.com/OctoSense-org/OctoScript-Makepad) | The runtime release that pins Makepad and OctoScript. Pinned sibling checkout. | | [makepad (OctoSense fork)](https://github.com/OctoSense-org/makepad) | The framework. Pinned sibling checkout. | -| [octos](https://github.com/octos-org/octos) | The agent kernel behind the AppCard assistant. Git dependency, one revision (`18fcd3f1`). | +| [octos](https://github.com/octos-org/octos) | The agent kernel behind the AppCard assistant. Git dependency, one revision (`18fcd3f1`), linked only with `app-appcard`. | ## Repository layout @@ -24,7 +24,7 @@ OctoSense-Desktop is the desktop shell of [OctoSense](https://github.com/OctoSen | `src/shell/` | Bar, launcher, menus, notifications, AI pane, gallery. | | `src/octosense/` | OctoSense-specific parts: app catalog loading, state paths, styles, Makepad source resolution. | | `apps/reference/` | `octosense-reference`, a small counter/text-input app that runs both as a hosted process and as a linked module. | -| `apps/appcard/` | `octosense-appcard`, the module that mounts the AppCard assistant (`octos-app`) in a tile. | +| `apps/appcard/` | `octosense-appcard`, the module that mounts the AppCard assistant (`octos-app`) in a tile. Opt-in (`app-appcard`); not shipped for now. | | `config/apps.json` | The default developer-program catalog. `apps.makepad.json` is an identical copy for `--apps`; `apps.overlay.json` holds the adaptations applied when regenerating them. | | `system-apps.json` | Which system apps this build packs, and from where. | | `native-runtime.lock.json` | The OctoScript-Makepad release (and through it, Makepad and OctoScript). | @@ -107,10 +107,10 @@ A relocatable `.app`, installers and a Linux session compositor are not provided | `app-reference` | off | Links Reference as a module. | | `app-sheets` | off | Links Makepad's Sheets as a module. | | `app-photos` | off | Links Makepad's native Photos module; it replaces the Photos system app of the same id (for comparison). | -| `app-appcard` | off | Links the AppCard assistant module (`apps/appcard`). | +| `app-appcard` | off | Links the AppCard assistant module (`apps/appcard`) and octos. Opt-in on every target, phones included; not shipped for now. | | `app-aichat` | off | Links Makepad's AI chat as a module, without its model engine. | | `app-rinx` | off | Links [Rinx](https://github.com/upstreamlabs/Rinx), the Matrix client, as a module. | -| `mobile-apps` | off | `app-reference` + `app-sheets` + `app-appcard` + `app-hub`: the set phone builds link, for testing on desktop. | +| `mobile-apps` | off | `app-reference` + `app-sheets` + `app-hub`: the set phone builds link, for testing on desktop. Not AppCard. | A linked module opens with `--module ` (or a `: Module` line in `wm/apps.splash` under the state directory): @@ -219,7 +219,9 @@ python3 scripts/upstream.py catalog --apply # rewrite config/apps.json and apps ### The AppCard assistant -`apps/appcard` (feature `app-appcard`, always linked on phones) hosts the whole AppCard assistant in one tile: `octos-app` from `../OctoSense-System-Apps/apps/appcard/app/app`, built without its `standalone` feature. Routing, cards, sessions, the composer and the kernel agent run inside the tile's isolate; `ask` is the module's AI-bus tool. +AppCard is **not shipped for now**: it interfered with the other apps, so no build links it unless asked. Default, `mobile-apps`, Android and iOS builds leave it and octos out, and it has no tile, group or launcher entry. `--features app-appcard` brings it back on any target (for a phone, pass the feature to `cargo makepad`); it needs the `../OctoSense-System-Apps` sibling that `tools/setup-native.py` prepares and pulls octos at the revision below. + +`apps/appcard` (feature `app-appcard`, opt-in) hosts the whole AppCard assistant in one tile: `octos-app` from `../OctoSense-System-Apps/apps/appcard/app/app`, built without its `standalone` feature. Routing, cards, sessions, the composer and the kernel agent run inside the tile's isolate; `ask` is the module's AI-bus tool. ```sh cargo run --release --features app-appcard -- --module appcard @@ -282,7 +284,7 @@ With the Makepad Android toolchain installed and a device on ADB: cargo makepad android run -p octosense --release ``` -Phone builds always link Reference, Sheets and AppCard, and App Hub with the system apps through the default feature. The launcher label is **OctoSense**, application id `dev.makepad.octosense`. AppCard's Java features (GPS, notifications, share, intents) and the bundled `liboctos.so` kernel need the fork's buildtool and `MAKEPAD_ANDROID_EXTRA_LIBS`; see [docs/android-appcard-build.md](docs/android-appcard-build.md). The dedicated phone shell is OctoSense-ROM's `home/`. +Phone builds always link Reference and Sheets, and App Hub with the system apps through the default feature; AppCard only with `--features app-appcard`. The launcher label is **OctoSense**, application id `dev.makepad.octosense`. With `app-appcard`, AppCard's Java features (GPS, notifications, share, intents) and the bundled `liboctos.so` kernel need the fork's buildtool and `MAKEPAD_ANDROID_EXTRA_LIBS`; see [docs/android-appcard-build.md](docs/android-appcard-build.md). The dedicated phone shell is OctoSense-ROM's `home/`. ## Desktop styles and settings @@ -308,6 +310,8 @@ After any change: `python3 tools/setup-native.py --update`, `cargo update` as ne ```sh cargo test --locked --workspace cargo test --locked --workspace --features mobile-apps +cargo test --locked -p octosense --features mobile-apps,app-appcard appcard +cargo tree --locked --features mobile-apps -i octos-core # must not match: no octos without app-appcard python3 -m unittest discover -s scripts -p 'test_*.py' python3 scripts/upstream.py catalog python3 tools/setup-native.py --check --cargo-manifest Cargo.toml @@ -325,7 +329,7 @@ Results for each change are recorded in [docs/validation.md](docs/validation.md) ## CI -`.github/workflows/runtime.yml` runs on every push and pull request, on macOS 14: `setup-native.py`, `cargo check --locked --workspace --features mobile-apps`, and `setup-native.py --check --cargo-manifest Cargo.toml`. It does **not** run `cargo test`, the Python tests or the smoke tests; run those locally before opening a PR. +`.github/workflows/runtime.yml` runs on every push and pull request, on macOS 14: `setup-native.py`, `cargo check --locked --features mobile-apps`, a `cargo tree` check that octos is absent from the `mobile-apps` graph (host and Android), `cargo check --locked --workspace --features mobile-apps,app-appcard`, and `setup-native.py --check --cargo-manifest Cargo.toml`. It does **not** run `cargo test`, the Python tests or the smoke tests; run those locally before opening a PR. ## Known gaps diff --git a/README.zh-CN.md b/README.zh-CN.md index c075f8a9..4ca278a3 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -14,7 +14,7 @@ OctoSense-Desktop 是 [OctoSense](https://github.com/OctoSense-org)(运行在 | [OctoScript-App-Design-Flow](https://github.com/OctoSense-org/OctoScript-App-Design-Flow) | 设计、构建应用并发布到 App Hub 的地方。 | | [OctoScript-Makepad](https://github.com/OctoSense-org/OctoScript-Makepad) | 固定 Makepad 与 OctoScript 版本的运行时发行版。以固定版本的同级目录检出。 | | [makepad(OctoSense 分支)](https://github.com/OctoSense-org/makepad) | 框架。以固定版本的同级目录检出。 | -| [octos](https://github.com/octos-org/octos) | AppCard 助手背后的 Agent 内核。Git 依赖,只有一个版本(`18fcd3f1`)。 | +| [octos](https://github.com/octos-org/octos) | AppCard 助手背后的 Agent 内核。Git 依赖,只有一个版本(`18fcd3f1`),仅在启用 `app-appcard` 时链接。 | ## 仓库结构 @@ -24,7 +24,7 @@ OctoSense-Desktop 是 [OctoSense](https://github.com/OctoSense-org)(运行在 | `src/shell/` | 状态栏、launcher、菜单、通知、AI 面板、gallery。 | | `src/octosense/` | OctoSense 专有部分:应用目录加载、状态路径、样式、Makepad 源码定位。 | | `apps/reference/` | `octosense-reference`,一个计数器加文本输入的小应用,既能作为托管进程运行,也能作为链接模块运行。 | -| `apps/appcard/` | `octosense-appcard`,在一个 tile 中挂载 AppCard 助手(`octos-app`)的模块。 | +| `apps/appcard/` | `octosense-appcard`,在一个 tile 中挂载 AppCard 助手(`octos-app`)的模块。需显式启用(`app-appcard`);目前不随产品发布。 | | `config/apps.json` | 默认的开发者程序目录。`apps.makepad.json` 是供 `--apps` 使用的相同副本;`apps.overlay.json` 保存重新生成时应用的调整。 | | `system-apps.json` | 本次构建打包哪些系统应用,以及从哪里取。 | | `native-runtime.lock.json` | OctoScript-Makepad 发行版(并由它确定 Makepad 和 OctoScript)。 | @@ -107,10 +107,10 @@ cargo build --release --workspace | `app-reference` | 关 | 把 Reference 作为模块链接。 | | `app-sheets` | 关 | 把 Makepad 的 Sheets 作为模块链接。 | | `app-photos` | 关 | 链接 Makepad 的原生 Photos 模块;它会替换同 id 的相册系统应用(用于对比)。 | -| `app-appcard` | 关 | 链接 AppCard 助手模块(`apps/appcard`)。 | +| `app-appcard` | 关 | 链接 AppCard 助手模块(`apps/appcard`)及 octos。在所有目标平台(包括手机)上都需显式启用;目前不随产品发布。 | | `app-aichat` | 关 | 把 Makepad 的 AI chat 作为模块链接,不含模型引擎。 | | `app-rinx` | 关 | 把 Matrix 客户端 [Rinx](https://github.com/upstreamlabs/Rinx) 作为模块链接。 | -| `mobile-apps` | 关 | `app-reference` + `app-sheets` + `app-appcard` + `app-hub`:手机构建链接的同一组模块,用于在桌面上测试。 | +| `mobile-apps` | 关 | `app-reference` + `app-sheets` + `app-hub`:手机构建链接的同一组模块,用于在桌面上测试。不含 AppCard。 | 已链接的模块用 `--module ` 打开(或在状态目录下的 `wm/apps.splash` 中写一行 `: Module`): @@ -219,7 +219,9 @@ python3 scripts/upstream.py catalog --apply # rewrite config/apps.json and apps ### AppCard 助手 -`apps/appcard`(feature `app-appcard`,手机构建总是链接)在一个 tile 中托管完整的 AppCard 助手:来自 `../OctoSense-System-Apps/apps/appcard/app/app` 的 `octos-app`,构建时关闭其 `standalone` feature。路由、卡片、会话、输入框和内核 Agent 都在该 tile 的 isolate 中运行;`ask` 是该模块的 AI 总线工具。 +AppCard **目前不随产品发布**:它会干扰其他应用,因此除非显式要求,任何构建都不链接它。默认构建、`mobile-apps`、Android 和 iOS 构建都不包含它和 octos,也没有它的磁贴、分组或启动器条目。使用 `--features app-appcard` 可在任意目标平台上恢复它(手机构建请把该 feature 传给 `cargo makepad`);它需要 `tools/setup-native.py` 准备的 `../OctoSense-System-Apps` 同级目录,并按下文所述版本拉取 octos。 + +`apps/appcard`(feature `app-appcard`,需显式启用)在一个 tile 中托管完整的 AppCard 助手:来自 `../OctoSense-System-Apps/apps/appcard/app/app` 的 `octos-app`,构建时关闭其 `standalone` feature。路由、卡片、会话、输入框和内核 Agent 都在该 tile 的 isolate 中运行;`ask` 是该模块的 AI 总线工具。 ```sh cargo run --release --features app-appcard -- --module appcard @@ -282,7 +284,7 @@ Makepad 的 [`makepad_test`](https://github.com/OctoSense-org/makepad/tree/main/ cargo makepad android run -p octosense --release ``` -手机构建总是链接 Reference、Sheets 和 AppCard,并通过默认 feature 链接 App Hub 及系统应用。launcher 显示名为 **OctoSense**,应用 id 为 `dev.makepad.octosense`。AppCard 的 Java 功能(GPS、通知、分享、intent)以及打包的 `liboctos.so` 内核需要使用分支的 buildtool 和 `MAKEPAD_ANDROID_EXTRA_LIBS`;见 [docs/android-appcard-build.md](docs/android-appcard-build.md)。专门的手机 Shell 是 OctoSense-ROM 的 `home/`。 +手机构建总是链接 Reference 和 Sheets,并通过默认 feature 链接 App Hub 及系统应用;AppCard 仅在使用 `--features app-appcard` 时链接。launcher 显示名为 **OctoSense**,应用 id 为 `dev.makepad.octosense`。启用 `app-appcard` 时,AppCard 的 Java 功能(GPS、通知、分享、intent)以及打包的 `liboctos.so` 内核需要使用分支的 buildtool 和 `MAKEPAD_ANDROID_EXTRA_LIBS`;见 [docs/android-appcard-build.md](docs/android-appcard-build.md)。专门的手机 Shell 是 OctoSense-ROM 的 `home/`。 ## 桌面样式与设置 @@ -308,6 +310,8 @@ cargo makepad android run -p octosense --release ```sh cargo test --locked --workspace cargo test --locked --workspace --features mobile-apps +cargo test --locked -p octosense --features mobile-apps,app-appcard appcard +cargo tree --locked --features mobile-apps -i octos-core # 必须匹配不到:未启用 app-appcard 时没有 octos python3 -m unittest discover -s scripts -p 'test_*.py' python3 scripts/upstream.py catalog python3 tools/setup-native.py --check --cargo-manifest Cargo.toml @@ -325,7 +329,7 @@ python3 scripts/smoke.py --cargo-run --default-catalog ## CI -`.github/workflows/runtime.yml` 在每次 push 和 pull request 时于 macOS 14 上运行:`setup-native.py`、`cargo check --locked --workspace --features mobile-apps`,以及 `setup-native.py --check --cargo-manifest Cargo.toml`。它**不**运行 `cargo test`、Python 测试或冒烟测试;提交 PR 前请在本地运行。 +`.github/workflows/runtime.yml` 在每次 push 和 pull request 时于 macOS 14 上运行:`setup-native.py`、`cargo check --locked --features mobile-apps`、检查 `mobile-apps` 依赖图(主机和 Android)中没有 octos 的 `cargo tree`、`cargo check --locked --workspace --features mobile-apps,app-appcard`,以及 `setup-native.py --check --cargo-manifest Cargo.toml`。它**不**运行 `cargo test`、Python 测试或冒烟测试;提交 PR 前请在本地运行。 ## 已知不足 diff --git a/docs/android-appcard-build.md b/docs/android-appcard-build.md index 1fc233e9..2e7bb0f4 100644 --- a/docs/android-appcard-build.md +++ b/docs/android-appcard-build.md @@ -1,5 +1,9 @@ # Android build with AppCard's framework, buildtool and bundled kernel +> AppCard is not shipped for now: Android and iOS builds link it only with +> `--features app-appcard`. This recipe applies to such opt-in builds; a +> default APK needs no octos kernel. + OctoSense on the phone now runs the AppCard assistant module (`apps/appcard`, which mounts `octos-app` from the pinned OctoSense-System-Apps checkout, `../OctoSense-System-Apps/apps/appcard/app/app`) on the makepad fork's AppCard diff --git a/src/apps.rs b/src/apps.rs index 33e90a76..385f924a 100644 --- a/src/apps.rs +++ b/src/apps.rs @@ -56,7 +56,8 @@ fn linked_modules() -> Vec<&'static dyn AppModule> { // system app (`os.photos`), and a linked module of the same id wins. #[cfg(feature = "app-photos")] out.push(&makepad_photos::PHOTOS_MODULE); - #[cfg(any(feature = "app-appcard", target_os = "android", target_os = "ios"))] + // AppCard is opt-in on every target (not shipped by default for now). + #[cfg(feature = "app-appcard")] out.push(&octosense_appcard::APPCARD_MODULE); #[cfg(feature = "app-rinx")] out.push(&rinx::module::RINX_MODULE); @@ -341,8 +342,13 @@ mod tests { fn bundled_apps_open_without_catalog_files_or_child_processes() { use makepad_widgets::*; let catalog = bundled_catalog(); - assert_eq!(catalog.iter().map(|app| app.id.as_str()).collect::>(), - ["reference", "sheets", "appcard", "apphub", "news", "photos", "maps", "camera", "mail"]); + // AppCard is opt-in (`app-appcard`), not part of `mobile-apps`. + let expected: &[&str] = if cfg!(feature = "app-appcard") { + &["reference", "sheets", "appcard", "apphub", "news", "photos", "maps", "camera", "mail"] + } else { + &["reference", "sheets", "apphub", "news", "photos", "maps", "camera", "mail"] + }; + assert_eq!(catalog.iter().map(|app| app.id.as_str()).collect::>(), expected); assert!(catalog.iter().all(|app| app.manifest.is_none())); // The system apps have no native module: the Card runner hosts them, // launched by their manifest id (ADR 0004). @@ -434,7 +440,7 @@ mod tests { } } -#[cfg(all(test, feature = "mobile-apps"))] +#[cfg(all(test, feature = "app-appcard"))] mod appcard_isolate_tests { /// The app's cards are Splash widgets, each in an ISOLATE that is minted /// without the framework's `sys`/`agent` engine; the AppCard module must From 207fdc19205e50c53b7d21263290330d5fcdb77b Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Sat, 26 Sep 2026 15:36:38 -0700 Subject: [PATCH 28/42] README: app-builder signpost; try your own app from a local catalog Co-Authored-By: Claude Opus 5.5 (1M context) --- README.md | 14 ++++++++++++++ README.zh-CN.md | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/README.md b/README.md index ecb67045..18ece22b 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ English | [简体中文](README.zh-CN.md) OctoSense-Desktop is the desktop shell of [OctoSense](https://github.com/OctoSense-org), the agent shell on top of your operating system. It is one Makepad window that is the desktop: a launcher, a dock and tiles, hosting system apps and App Hub store apps as contained script programs, trusted native modules in-process, and Makepad developer programs as child processes. It gets its apps the same way the phone shell, OctoSense-ROM's `home/`, does. +**Building an OctoSense app?** You do not need this repository to build, check or publish one: start at the [OctoSense-org profile](https://github.com/OctoSense-org)'s reading list (OctoScript-App-Design-Flow's `AGENTS.md`, then `docs/QUICKSTART.md`). Build this shell only if you want to see your app in the desktop shell before it is published ([Try your own app](#try-your-own-app-before-it-is-published)). + ## Where it sits | Repository | Role for this repo | @@ -172,6 +174,18 @@ New app features that need a password, PIN or token belong in a host service and App Hub is on by default. Open **App Hub** from the launcher to browse the signed catalog and install apps; installed apps appear in the launcher without a restart. The catalog origin defaults to the App Hub repository and can be pointed elsewhere with `OCTOSENSE_HUB`. To build and publish an app, start from [OctoScript-App-Design-Flow](https://github.com/OctoSense-org/OctoScript-App-Design-Flow). +#### Try your own app before it is published + +Publish the bundle into a local catalog with a throwaway anchor (OctoScript-App-Design-Flow's [PUBLISHING §4](https://github.com/OctoSense-org/OctoScript-App-Design-Flow/blob/main/docs/PUBLISHING.md#4-rehearse-the-store-path-locally) gives the `hub keygen`/`certify`/`publish` commands), then point this shell at it: + +```sh +OCTOSENSE_HUB= OCTOSENSE_HUB_ANCHOR= \ + OCTOSENSE_HOME=/tmp/octosense-test OCTOSENSE_APP_DATA=/tmp/octosense-test-apps \ + cargo run --release +``` + +Open **App Hub**, choose the app, **Get**, scroll to **Install**, then **Open**: it runs in the Card runner under its manifest, as a store app would. Verified on macOS on 2026-09-26 with a new script app. The two `OCTOSENSE_*` state variables keep the test out of `~/.octosense`. + ### Choosing and overriding system apps `system-apps.json` names the apps and where their bundles are: diff --git a/README.zh-CN.md b/README.zh-CN.md index 4ca278a3..e9929d32 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -4,6 +4,8 @@ OctoSense-Desktop 是 [OctoSense](https://github.com/OctoSense-org)(运行在操作系统之上的 Agent 交互 Shell)的桌面端 Shell。它是一个 Makepad 窗口,这个窗口本身就是桌面:launcher、dock 和平铺窗格(tile)。系统应用和 App Hub 商店应用以隔离的脚本程序运行,受信任的原生模块在进程内运行,Makepad 开发者程序作为子进程运行。它获取应用的方式与手机 Shell(OctoSense-ROM 的 `home/`)完全相同。 +**要开发 OctoSense 应用?** 构建、检查和发布应用都不需要本仓库:请从 [OctoSense-org 主页](https://github.com/OctoSense-org)的阅读列表开始(先读 OctoScript-App-Design-Flow 的 `AGENTS.md`,再读 `docs/QUICKSTART.md`)。只有想在发布前在桌面 Shell 中看到自己的应用时,才需要构建本 Shell(见[发布前试用自己的应用](#发布前试用自己的应用))。 + ## 在仓库体系中的位置 | 仓库 | 与本仓库的关系 | @@ -172,6 +174,18 @@ launcher 把四类应用列在一起: App Hub 默认开启。从 launcher 打开 **App Hub**,浏览签名目录并安装应用;安装后的应用无需重启就会出现在 launcher 中。目录来源默认是 App Hub 仓库,可以用 `OCTOSENSE_HUB` 指向其他位置。要构建和发布应用,从 [OctoScript-App-Design-Flow](https://github.com/OctoSense-org/OctoScript-App-Design-Flow) 开始。 +#### 发布前试用自己的应用 + +用一次性信任锚把应用包发布到本地目录(命令见 OctoScript-App-Design-Flow 的 [PUBLISHING §4](https://github.com/OctoSense-org/OctoScript-App-Design-Flow/blob/main/docs/PUBLISHING.md#4-rehearse-the-store-path-locally):`hub keygen`/`certify`/`publish`),再让本 Shell 指向它: + +```sh +OCTOSENSE_HUB= OCTOSENSE_HUB_ANCHOR= \ + OCTOSENSE_HOME=/tmp/octosense-test OCTOSENSE_APP_DATA=/tmp/octosense-test-apps \ + cargo run --release +``` + +打开 **App Hub**,选中应用,点 **Get**,向下滚动到 **Install**,然后点 **Open**:它会像商店应用一样,在 Card runner 中按其 manifest 运行。已于 2026-09-26 在 macOS 上用一个新的脚本应用验证。两个 `OCTOSENSE_*` 状态变量让测试不影响 `~/.octosense`。 + ### 选择与覆盖系统应用 `system-apps.json` 列出应用及其应用包所在位置: From 23cb293e244f2342a127fb6707efa828fef1f707 Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Sat, 26 Sep 2026 15:46:32 -0700 Subject: [PATCH 29/42] AppCard (opt-in): octos from octos main via System-Apps c8d50732 Pin OctoSense-System-Apps to c8d50732 (System-Apps#9), whose AppCard takes octos-core/octos-cli from octos main (the merge of octos-org/octos#2541, 6ad76e5c) instead of branch appcard/mate70-on-main. Cargo.lock moves the octos crates to that rev. AppCard stays strictly opt-in (--features app-appcard); the default and mobile-apps builds still do not link octos. Co-Authored-By: Claude Opus 5.5 (1M context) --- Cargo.lock | 80 +++++++++++++++++++++++++------------------ native-apps.lock.json | 2 +- 2 files changed, 47 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 87411fa2..7c2e9395 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1277,6 +1277,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "037e2a1a92236d0aff7e845093f64661d6df4c02c9fcc61a60e9e1d736fa392f" dependencies = [ "clap", + "clap_lex", + "is_executable", + "shlex 2.0.1", ] [[package]] @@ -2815,8 +2818,8 @@ dependencies = [ "libc", "log", "rustversion", - "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "windows-result 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "windows-link 0.1.3", + "windows-result 0.3.4", ] [[package]] @@ -4113,7 +4116,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.62.2 (registry+https://github.com/rust-lang/crates.io-index)", + "windows-core 0.61.2", ] [[package]] @@ -4407,6 +4410,15 @@ dependencies = [ "once_cell 1.21.4", ] +[[package]] +name = "is_executable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cb6a9f675da968c63b6208c641b9dca58fc0133ae53375736b1767b0cab8bd" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -6633,8 +6645,8 @@ dependencies = [ [[package]] name = "octos-agent" -version = "2.0.3-rc.12" -source = "git+https://github.com/octos-org/octos.git?rev=18fcd3f16e527d2b601d7ae244f056fb711bb6b8#18fcd3f16e527d2b601d7ae244f056fb711bb6b8" +version = "2.0.3-rc.13" +source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" dependencies = [ "async-trait", "base64 0.22.1", @@ -6760,8 +6772,8 @@ dependencies = [ [[package]] name = "octos-bus" -version = "2.0.3-rc.12" -source = "git+https://github.com/octos-org/octos.git?rev=18fcd3f16e527d2b601d7ae244f056fb711bb6b8#18fcd3f16e527d2b601d7ae244f056fb711bb6b8" +version = "2.0.3-rc.13" +source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" dependencies = [ "aes 0.8.4", "async-trait", @@ -6789,8 +6801,8 @@ dependencies = [ [[package]] name = "octos-cli" -version = "2.0.3-rc.12" -source = "git+https://github.com/octos-org/octos.git?rev=18fcd3f16e527d2b601d7ae244f056fb711bb6b8#18fcd3f16e527d2b601d7ae244f056fb711bb6b8" +version = "2.0.3-rc.13" +source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" dependencies = [ "agent-client-protocol", "argon2", @@ -6869,8 +6881,8 @@ dependencies = [ [[package]] name = "octos-core" -version = "2.0.3-rc.12" -source = "git+https://github.com/octos-org/octos.git?rev=18fcd3f16e527d2b601d7ae244f056fb711bb6b8#18fcd3f16e527d2b601d7ae244f056fb711bb6b8" +version = "2.0.3-rc.13" +source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" dependencies = [ "chrono", "eyre", @@ -6883,8 +6895,8 @@ dependencies = [ [[package]] name = "octos-diagnostics" -version = "2.0.3-rc.12" -source = "git+https://github.com/octos-org/octos.git?rev=18fcd3f16e527d2b601d7ae244f056fb711bb6b8#18fcd3f16e527d2b601d7ae244f056fb711bb6b8" +version = "2.0.3-rc.13" +source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" dependencies = [ "eyre", "octos-core", @@ -6895,8 +6907,8 @@ dependencies = [ [[package]] name = "octos-fleet" -version = "2.0.3-rc.12" -source = "git+https://github.com/octos-org/octos.git?rev=18fcd3f16e527d2b601d7ae244f056fb711bb6b8#18fcd3f16e527d2b601d7ae244f056fb711bb6b8" +version = "2.0.3-rc.13" +source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" dependencies = [ "eyre", "octos-core", @@ -6911,8 +6923,8 @@ dependencies = [ [[package]] name = "octos-fleet-worker" -version = "2.0.3-rc.12" -source = "git+https://github.com/octos-org/octos.git?rev=18fcd3f16e527d2b601d7ae244f056fb711bb6b8#18fcd3f16e527d2b601d7ae244f056fb711bb6b8" +version = "2.0.3-rc.13" +source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" dependencies = [ "async-trait", "eyre", @@ -6928,8 +6940,8 @@ dependencies = [ [[package]] name = "octos-llm" -version = "2.0.3-rc.12" -source = "git+https://github.com/octos-org/octos.git?rev=18fcd3f16e527d2b601d7ae244f056fb711bb6b8#18fcd3f16e527d2b601d7ae244f056fb711bb6b8" +version = "2.0.3-rc.13" +source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" dependencies = [ "async-trait", "base64 0.22.1", @@ -6952,8 +6964,8 @@ dependencies = [ [[package]] name = "octos-memory" -version = "2.0.3-rc.12" -source = "git+https://github.com/octos-org/octos.git?rev=18fcd3f16e527d2b601d7ae244f056fb711bb6b8#18fcd3f16e527d2b601d7ae244f056fb711bb6b8" +version = "2.0.3-rc.13" +source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" dependencies = [ "bincode", "chrono", @@ -6971,8 +6983,8 @@ dependencies = [ [[package]] name = "octos-pipeline" -version = "2.0.3-rc.12" -source = "git+https://github.com/octos-org/octos.git?rev=18fcd3f16e527d2b601d7ae244f056fb711bb6b8#18fcd3f16e527d2b601d7ae244f056fb711bb6b8" +version = "2.0.3-rc.13" +source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" dependencies = [ "async-trait", "chrono", @@ -6993,8 +7005,8 @@ dependencies = [ [[package]] name = "octos-plugin" -version = "2.0.3-rc.12" -source = "git+https://github.com/octos-org/octos.git?rev=18fcd3f16e527d2b601d7ae244f056fb711bb6b8#18fcd3f16e527d2b601d7ae244f056fb711bb6b8" +version = "2.0.3-rc.13" +source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" dependencies = [ "eyre", "metrics", @@ -7008,7 +7020,7 @@ dependencies = [ [[package]] name = "octos-sandbox" version = "0.1.0" -source = "git+https://github.com/octos-org/octos.git?rev=18fcd3f16e527d2b601d7ae244f056fb711bb6b8#18fcd3f16e527d2b601d7ae244f056fb711bb6b8" +source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" dependencies = [ "clap", "eyre", @@ -7022,8 +7034,8 @@ dependencies = [ [[package]] name = "octos-services" -version = "2.0.3-rc.12" -source = "git+https://github.com/octos-org/octos.git?rev=18fcd3f16e527d2b601d7ae244f056fb711bb6b8#18fcd3f16e527d2b601d7ae244f056fb711bb6b8" +version = "2.0.3-rc.13" +source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" dependencies = [ "chrono", "dirs", @@ -7043,8 +7055,8 @@ dependencies = [ [[package]] name = "octos-store" -version = "2.0.3-rc.12" -source = "git+https://github.com/octos-org/octos.git?rev=18fcd3f16e527d2b601d7ae244f056fb711bb6b8#18fcd3f16e527d2b601d7ae244f056fb711bb6b8" +version = "2.0.3-rc.13" +source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" dependencies = [ "base64 0.22.1", "chrono", @@ -7063,8 +7075,8 @@ dependencies = [ [[package]] name = "octos-swarm" -version = "2.0.3-rc.12" -source = "git+https://github.com/octos-org/octos.git?rev=18fcd3f16e527d2b601d7ae244f056fb711bb6b8#18fcd3f16e527d2b601d7ae244f056fb711bb6b8" +version = "2.0.3-rc.13" +source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" dependencies = [ "async-trait", "chrono", @@ -7082,8 +7094,8 @@ dependencies = [ [[package]] name = "octos-workflows" -version = "2.0.3-rc.12" -source = "git+https://github.com/octos-org/octos.git?rev=18fcd3f16e527d2b601d7ae244f056fb711bb6b8#18fcd3f16e527d2b601d7ae244f056fb711bb6b8" +version = "2.0.3-rc.13" +source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" dependencies = [ "chrono", "eyre", diff --git a/native-apps.lock.json b/native-apps.lock.json index 90b0f793..51383fd0 100644 --- a/native-apps.lock.json +++ b/native-apps.lock.json @@ -3,7 +3,7 @@ "repositories": { "system-apps": { "url": "https://github.com/OctoSense-org/OctoSense-System-Apps.git", - "revision": "21b9897f995c769deda3f24d80adb36d46a92548" + "revision": "c8d507327bc49fbe15c8a0e8fa06cd598ba6c507" } } } From 122dfc7b820dfe0215ae03b6f24c9c2f1024fd8b Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Sat, 26 Sep 2026 16:50:52 -0700 Subject: [PATCH 30/42] App Hub 3e993d4c, System-Apps c0f2199c App Hub 0d36f50b -> 3e993d4c (card vocabulary once per thread, OctoSense-App-Hub#12), matching System-Apps' host services, so one octosense-appstore and no [patch]. System-Apps c8d50732 -> c0f2199c. Cargo.lock also gains octosense-llm-config, which System-Apps' AppCard (opt-in) now links. Co-Authored-By: Claude Opus 5.5 (1M context) --- Cargo.lock | 24 ++++++++++++++++++++---- Cargo.toml | 2 +- native-apps.lock.json | 2 +- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7c2e9395..36b7d6be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6721,6 +6721,7 @@ dependencies = [ "octos-core", "octoscript-node", "octoscript-ui-l0", + "octosense-llm-config", "reqwest 0.12.28", "robius-open", "serde", @@ -7171,7 +7172,7 @@ dependencies = [ [[package]] name = "octosense-app-hub" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/OctoSense-App-Hub?rev=0d36f50b9f0fdfbd8247b5deb8da8baa53c83066#0d36f50b9f0fdfbd8247b5deb8da8baa53c83066" +source = "git+https://github.com/OctoSense-org/OctoSense-App-Hub?rev=3e993d4c596560c42f011b2fd00bd40dbcac7cc5#3e993d4c596560c42f011b2fd00bd40dbcac7cc5" dependencies = [ "base64 0.22.1", "blake3", @@ -7187,7 +7188,7 @@ dependencies = [ [[package]] name = "octosense-app-hub-app" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/OctoSense-App-Hub?rev=0d36f50b9f0fdfbd8247b5deb8da8baa53c83066#0d36f50b9f0fdfbd8247b5deb8da8baa53c83066" +source = "git+https://github.com/OctoSense-org/OctoSense-App-Hub?rev=3e993d4c596560c42f011b2fd00bd40dbcac7cc5#3e993d4c596560c42f011b2fd00bd40dbcac7cc5" dependencies = [ "makepad-app-module", "makepad-widgets", @@ -7201,7 +7202,7 @@ dependencies = [ [[package]] name = "octosense-app-policy" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/OctoSense-App-Hub?rev=0d36f50b9f0fdfbd8247b5deb8da8baa53c83066#0d36f50b9f0fdfbd8247b5deb8da8baa53c83066" +source = "git+https://github.com/OctoSense-org/OctoSense-App-Hub?rev=3e993d4c596560c42f011b2fd00bd40dbcac7cc5#3e993d4c596560c42f011b2fd00bd40dbcac7cc5" dependencies = [ "blake3", "makepad-widgets", @@ -7222,7 +7223,7 @@ dependencies = [ [[package]] name = "octosense-appstore" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/OctoSense-App-Hub?rev=0d36f50b9f0fdfbd8247b5deb8da8baa53c83066#0d36f50b9f0fdfbd8247b5deb8da8baa53c83066" +source = "git+https://github.com/OctoSense-org/OctoSense-App-Hub?rev=3e993d4c596560c42f011b2fd00bd40dbcac7cc5#3e993d4c596560c42f011b2fd00bd40dbcac7cc5" dependencies = [ "makepad-app-module", "makepad-widgets", @@ -7233,6 +7234,21 @@ dependencies = [ "serde_json", ] +[[package]] +name = "octosense-llm-config" +version = "0.1.0" +dependencies = [ + "argon2", + "chacha20poly1305 0.10.1", + "chrono", + "flate2", + "qrcode", + "serde", + "serde_json", + "thiserror 2.0.20", + "url", +] + [[package]] name = "octosense-mail-service" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 3336a673..c4c9d059 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,7 +67,7 @@ octosense-appcard = { path = "apps/appcard", optional = true } # (system-apps.json, .cargo/config.toml), installed apps and their icons. # Same rev as the Mail host service's octosense-appstore (OctoSense-System-Apps): # one App Hub source, so one host-service registry. -octosense-app-hub-app = { git = "https://github.com/OctoSense-org/OctoSense-App-Hub", rev = "0d36f50b9f0fdfbd8247b5deb8da8baa53c83066", optional = true } +octosense-app-hub-app = { git = "https://github.com/OctoSense-org/OctoSense-App-Hub", rev = "3e993d4c596560c42f011b2fd00bd40dbcac7cc5", optional = true } # The `mail` host service the Mail system app calls through `host.request`: # accounts, message lists and send run here, with the passwords the host # holds; the app never gets a socket or a secret. From the pinned diff --git a/native-apps.lock.json b/native-apps.lock.json index 51383fd0..1a6a7145 100644 --- a/native-apps.lock.json +++ b/native-apps.lock.json @@ -3,7 +3,7 @@ "repositories": { "system-apps": { "url": "https://github.com/OctoSense-org/OctoSense-System-Apps.git", - "revision": "c8d507327bc49fbe15c8a0e8fa06cd598ba6c507" + "revision": "c0f2199c6d504ae65d8632747bb491bed820891b" } } } From fd41ca4867ccb991ed15cc34d7cc54463d1ece7b Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Sat, 26 Sep 2026 17:04:22 -0700 Subject: [PATCH 31/42] AI providers system app and the llm host service Link the AI providers system app from the pinned OctoSense-System-Apps (c0f2199c, which carries #1: the AI providers app, the octosense-llm-service `llm` host service and octosense-llm-config) on App Hub 3e993d4c (which has the `llm` capability), the rev both the Mail and llm services pin: one App Hub in the graph, no alias. - system-apps.json selects ai-providers. - The `app-hub` feature links octosense-llm-service and -config; register_host_services registers `llm` next to Mail with the AppCard kernel's octos home as its core dir, the desktop image picker (the open panel, filtered to PNG and JPEG) and image drops on the import sheet (src/llm_image.rs; OCTOSENSE_LLM_TEST_IMAGE answers a pick for hidden test runs). - With app-appcard linked, a change to the provider set restarts the running AppCard module so its kernel reads the new profile; without it nothing is registered as a change hook. - Start > Settings becomes Appearance plus AI providers once the build ships the app. Co-Authored-By: Claude Opus 5.5 --- Cargo.lock | 56 ++++++++++++++--- Cargo.toml | 13 ++-- README.md | 15 +++-- README.zh-CN.md | 13 ++-- src/apps.rs | 54 +++++++++++++--- src/llm_image.rs | 155 ++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 49 +++++++++++++++ src/shell/menu.rs | 42 ++++++++++++- system-apps.json | 2 +- 9 files changed, 365 insertions(+), 34 deletions(-) create mode 100644 src/llm_image.rs diff --git a/Cargo.lock b/Cargo.lock index 36b7d6be..30cba497 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2818,8 +2818,8 @@ dependencies = [ "libc", "log", "rustversion", - "windows-link 0.1.3", - "windows-result 0.3.4", + "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "windows-result 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -4049,7 +4049,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots", + "webpki-roots 1.0.9", ] [[package]] @@ -4116,7 +4116,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.61.2", + "windows-core 0.62.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -4751,7 +4751,7 @@ dependencies = [ "tokio", "tokio-rustls", "url", - "webpki-roots", + "webpki-roots 1.0.9", ] [[package]] @@ -7164,6 +7164,8 @@ dependencies = [ "makepad-wm-theme", "octosense-app-hub-app", "octosense-appcard", + "octosense-llm-config", + "octosense-llm-service", "octosense-mail-service", "octosense-reference", "rinx", @@ -7182,7 +7184,7 @@ dependencies = [ "rand_core 0.9.5", "serde", "serde_json", - "ureq", + "ureq 3.4.2", ] [[package]] @@ -7249,6 +7251,18 @@ dependencies = [ "url", ] +[[package]] +name = "octosense-llm-service" +version = "0.1.0" +dependencies = [ + "image", + "octosense-appstore", + "octosense-llm-config", + "rqrr", + "serde_json", + "ureq 2.12.1", +] + [[package]] name = "octosense-mail-service" version = "0.1.0" @@ -7265,7 +7279,7 @@ dependencies = [ "scraper", "serde_json", "sha2 0.10.9", - "webpki-roots", + "webpki-roots 1.0.9", ] [[package]] @@ -8286,7 +8300,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams 0.4.2", "web-sys", - "webpki-roots", + "webpki-roots 1.0.9", ] [[package]] @@ -10954,6 +10968,21 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" +dependencies = [ + "base64 0.22.1", + "log", + "once_cell 1.21.4", + "rustls", + "rustls-pki-types", + "url", + "webpki-roots 0.26.11", +] + [[package]] name = "ureq" version = "3.4.2" @@ -10968,7 +10997,7 @@ dependencies = [ "rustls-pki-types", "ureq-proto", "utf8-zero", - "webpki-roots", + "webpki-roots 1.0.9", ] [[package]] @@ -11361,6 +11390,15 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.9", +] + [[package]] name = "webpki-roots" version = "1.0.9" diff --git a/Cargo.toml b/Cargo.toml index c4c9d059..4a06e61c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,8 +65,8 @@ octosense-appcard = { path = "apps/appcard", optional = true } # module (`card`) that runs contained script apps, the system apps packed at # build time from the selection `OCTOSENSE_SYSTEM_APPS` names # (system-apps.json, .cargo/config.toml), installed apps and their icons. -# Same rev as the Mail host service's octosense-appstore (OctoSense-System-Apps): -# one App Hub source, so one host-service registry. +# Same rev as the Mail and llm host services' octosense-appstore +# (OctoSense-System-Apps): one App Hub source, so one host-service registry. octosense-app-hub-app = { git = "https://github.com/OctoSense-org/OctoSense-App-Hub", rev = "3e993d4c596560c42f011b2fd00bd40dbcac7cc5", optional = true } # The `mail` host service the Mail system app calls through `host.request`: # accounts, message lists and send run here, with the passwords the host @@ -74,6 +74,11 @@ octosense-app-hub-app = { git = "https://github.com/OctoSense-org/OctoSense-App- # OctoSense-System-Apps checkout beside this repository # (native-apps.lock.json, tools/setup-native.py). octosense-mail-service = { path = "../OctoSense-System-Apps/apps/mail/host-service", optional = true } +# The `llm` host service the AI providers system app calls: the assistant's +# LLM providers, their keys (macOS keychain) and the phone QR, on host-owned +# sheets. The config crate names the AppCard kernel's octos home it writes. +octosense-llm-service = { path = "../OctoSense-System-Apps/apps/ai-providers/host-service", optional = true } +octosense-llm-config = { path = "../OctoSense-System-Apps/apps/ai-providers/config", optional = true } # Rinx (the Matrix client with Moments and articles) as an in-process module. # Built without its standalone window entry point. rinx = { git = "https://github.com/upstreamlabs/Rinx", rev = "5ad2c1739e5b4776588518ab76440a5367c9968e", default-features = false, features = ["octosense-module"], optional = true } @@ -90,7 +95,7 @@ makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = " [features] # App Hub is part of the standard launcher: the store, the Card runner and -# the system apps (News, Photos, Maps, Camera, Mail) it runs. +# the system apps (News, Photos, Maps, Camera, Mail, AI providers) it runs. default = ["app-hub"] # Also usable on desktop for testing the same embedded apps with --module. # Photos is the system app here; `app-photos` links Makepad's native Photos @@ -98,7 +103,7 @@ default = ["app-hub"] # AppCard is not among them: it ships only with `--features app-appcard`. mobile-apps = ["app-reference", "app-sheets", "app-hub"] # App Hub and the host services its system apps call. -app-hub = ["dep:octosense-app-hub-app", "dep:octosense-mail-service"] +app-hub = ["dep:octosense-app-hub-app", "dep:octosense-mail-service", "dep:octosense-llm-service", "dep:octosense-llm-config"] app-reference = ["dep:octosense-reference"] # Sheets linked in as a module (hosting is still Process unless switched). app-sheets = ["dep:makepad-sheets"] diff --git a/README.md b/README.md index 18ece22b..07a74040 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ OctoSense-Desktop is the desktop shell of [OctoSense](https://github.com/OctoSen | Repository | Role for this repo | | --- | --- | | [OctoSense-ROM](https://github.com/OctoSense-org/OctoSense-ROM) | The phone shell (`home/`). Same app model, same runtime, same system apps. | -| [OctoSense-System-Apps](https://github.com/OctoSense-org/OctoSense-System-Apps) | News, Photos, Maps, Camera and Mail bundles, the Mail host service and the AppCard assistant (`octos-app`). Pinned sibling checkout. | +| [OctoSense-System-Apps](https://github.com/OctoSense-org/OctoSense-System-Apps) | News, Photos, Maps, Camera, Mail and AI providers bundles, the Mail and `llm` host services and the AppCard assistant (`octos-app`). Pinned sibling checkout. | | [OctoSense-App-Hub](https://github.com/OctoSense-org/OctoSense-App-Hub) | The signed catalog, the store and the Card runner. Linked as the Git crate `octosense-app-hub-app`. | | [OctoScript-App-Design-Flow](https://github.com/OctoSense-org/OctoScript-App-Design-Flow) | Where apps are designed, built and published to the App Hub. | | [OctoScript-Makepad](https://github.com/OctoSense-org/OctoScript-Makepad) | The runtime release that pins Makepad and OctoScript. Pinned sibling checkout. | @@ -33,7 +33,7 @@ OctoSense-Desktop is the desktop shell of [OctoSense](https://github.com/OctoSen | `native-apps.lock.json` | The OctoSense-System-Apps revision. | | `runtime-patches.lock.json` | Reviewed patches on top of the pinned Makepad, with their expected trees. Empty today: the pinned Makepad carries everything the shell needs. | | `tools/setup-native.py` | Prepares and checks the pinned sibling checkouts. | -| `scripts/` | `upstream.py` (WM provenance and catalog regeneration), `smoke.py` (native smoke test), their Python tests, and `provision-appcard-llm.sh` (Android). | +| `scripts/` | `upstream.py` (WM provenance and catalog regeneration), `smoke.py` (native smoke test), their Python tests, `system_apps_remote.sh` and `ai_providers_remote.sh` (hidden `--remote` end-to-end runs of the system apps and of AI providers), and `provision-appcard-llm.sh` (Android). | | `upstream/makepad.json` | Provenance of every file imported from Makepad's `apps/wm`. | | `resources/` | Themes, wallpapers, icons, Android manifest template, startup script. | | `docs/` | [Validation record](docs/validation.md), [upstream sync](docs/upstream.md), [local AI](docs/local-ai.md), [Android AppCard build](docs/android-appcard-build.md), dated plans. | @@ -105,7 +105,7 @@ A relocatable `.app`, installers and a Linux session compositor are not provided | Feature | Default | Effect | | --- | --- | --- | -| `app-hub` | on | Links `octosense-app-hub-app` (store `apphub`, Card runner `card`, system apps) and the Mail host service `octosense-mail-service`. Without it the build has no App Hub and no system apps. | +| `app-hub` | on | Links `octosense-app-hub-app` (store `apphub`, Card runner `card`, system apps) and the host services `octosense-mail-service` (Mail) and `octosense-llm-service` (AI providers). Without it the build has no App Hub and no system apps. | | `app-reference` | off | Links Reference as a module. | | `app-sheets` | off | Links Makepad's Sheets as a module. | | `app-photos` | off | Links Makepad's native Photos module; it replaces the Photos system app of the same id (for comparison). | @@ -138,6 +138,7 @@ App Hub's modules are the exception: they have no process form and always open i | `OCTOSENSE_SYSTEM_APPS` | The system-app selection file; `.cargo/config.toml` sets it to `system-apps.json`. | | `MAKEPAD_APP_CONFIG='{"mail_demo":true}'` | Serve Mail's demo mailbox (see [Demos](#demos)). | | `OCTOSENSE_MAIL_VAULT=file` | Keep Mail passwords in a 0600 file instead of the macOS keychain. | +| `OCTOSENSE_LLM_VAULT=file` | Keep AI providers' keys in the owner-only octos profile instead of the macOS keychain. | | `OCTOS_APP_CORE_BIN`, `OCTOS_APP_CORE_DIR` | Point the AppCard assistant at a local octos kernel. | | `MAKEPAD_REMOTE`, `MAKEPAD_HIDE_WINDOWS` | Remote-control bridge; hidden windows (see [Demos](#demos)). | @@ -147,7 +148,7 @@ The launcher lists four kinds of app together: | Kind | Comes from | Runs as | Launcher id | | --- | --- | --- | --- | -| **System apps**: News, Photos, Maps, Camera, Mail | OctoSense-System-Apps `apps//bundle`, selected by `system-apps.json`, packed into the build | Contained Splash programs in App Hub's Card runner, each in its own isolate under the capabilities its manifest asks for | `` (manifest id `os.`) | +| **System apps**: News, Photos, Maps, Camera, Mail, AI providers | OctoSense-System-Apps `apps//bundle`, selected by `system-apps.json`, packed into the build | Contained Splash programs in App Hub's Card runner, each in its own isolate under the capabilities its manifest asks for | `` (manifest id `os.`) | | **Store apps** | The signed App Hub catalog, installed from the store (`apphub`) | The same Card runner. Every open is checked against the catalog; an update closes old instances. | `hub:` | | **Native modules** | Rust crates linked into this binary | In-process `AppModule`s. Trusted code only: App Hub, AppCard, Reference and the `app-*` features. | module id | | **Developer programs** | `config/apps.json` | Separate processes in tiles, over Makepad's `--stdin-loop` hosting protocol, built on first launch | catalog `id` | @@ -168,6 +169,8 @@ Mail is the worked example (`octosense-mail-service`, from `../OctoSense-System- - The service tests the account, stores the password in the platform secret store (macOS keychain), and grants the account only to the app that added it. - Mail state lives under the host's own directory, outside every app's jail. +AI providers (`os.ai-providers`) edits the LLM providers the AppCard assistant runs on through the `llm` service (`octosense-llm-service`, from `../OctoSense-System-Apps/apps/ai-providers/host-service`). Keys are typed only on host sheets and go to the macOS keychain entry octos reads; the providers are written to the AppCard kernel's octos profile (`OCTOS_APP_CORE_DIR`, else `~/octos-home/.octos`). A phone's provider QR is imported from a picture of it: **Choose image** opens the open panel, or drop a screenshot on the import sheet. **Start → Settings → AI providers** opens it. With `--features app-appcard`, a change restarts a running AppCard so its kernel reads the new providers. + New app features that need a password, PIN or token belong in a host service and a host sheet, never in the app's own UI. ### Store apps (App Hub) @@ -194,7 +197,7 @@ Open **App Hub**, choose the app, **Get**, scroll to **Install**, then **Open**: { "schema": 1, "source": "../OctoSense-System-Apps/apps", - "apps": ["news", "photos", "maps", "camera", "mail"], + "apps": ["news", "photos", "maps", "camera", "mail", "ai-providers"], "assets": {} } ``` @@ -313,7 +316,7 @@ Phone builds always link Reference and Sheets, and App Hub with the system apps | --- | --- | --- | | Makepad / OctoScript | `native-runtime.lock.json` (a new OctoScript-Makepad release), and the `rev` of the Makepad Git dependencies in `Cargo.toml` and `apps/*/Cargo.toml` | If a patch is needed on top, record it in `runtime-patches.lock.json` (base, sha256, tree) | | System apps, Mail service, AppCard | `revision` in `native-apps.lock.json` | — | -| App Hub | `rev` of `octosense-app-hub-app` in `Cargo.toml` | Keep it at the App Hub rev the pinned System-Apps Mail service names (`octosense-appstore`), so the graph has one App Hub | +| App Hub | `rev` of `octosense-app-hub-app` in `Cargo.toml` | Keep it at the App Hub rev the pinned System-Apps Mail and `llm` services name (`octosense-appstore`), so the graph has one App Hub | After any change: `python3 tools/setup-native.py --update`, `cargo update` as needed, then `python3 tools/setup-native.py --check --cargo-manifest Cargo.toml` and the tests below. diff --git a/README.zh-CN.md b/README.zh-CN.md index e9929d32..df259d6d 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -11,7 +11,7 @@ OctoSense-Desktop 是 [OctoSense](https://github.com/OctoSense-org)(运行在 | 仓库 | 与本仓库的关系 | | --- | --- | | [OctoSense-ROM](https://github.com/OctoSense-org/OctoSense-ROM/blob/main/README.zh-CN.md) | 手机 Shell(`home/`)。相同的应用模型、相同的运行时、相同的系统应用。 | -| [OctoSense-System-Apps](https://github.com/OctoSense-org/OctoSense-System-Apps) | 新闻、相册、地图、相机、邮件的应用包,邮件宿主服务,以及 AppCard 助手(`octos-app`)。以固定版本的同级目录检出。 | +| [OctoSense-System-Apps](https://github.com/OctoSense-org/OctoSense-System-Apps) | 新闻、相册、地图、相机、邮件、AI 提供商的应用包,邮件与 `llm` 宿主服务,以及 AppCard 助手(`octos-app`)。以固定版本的同级目录检出。 | | [OctoSense-App-Hub](https://github.com/OctoSense-org/OctoSense-App-Hub) | 签名目录、商店和 Card 运行器。以 Git crate `octosense-app-hub-app` 链接。 | | [OctoScript-App-Design-Flow](https://github.com/OctoSense-org/OctoScript-App-Design-Flow) | 设计、构建应用并发布到 App Hub 的地方。 | | [OctoScript-Makepad](https://github.com/OctoSense-org/OctoScript-Makepad) | 固定 Makepad 与 OctoScript 版本的运行时发行版。以固定版本的同级目录检出。 | @@ -33,7 +33,7 @@ OctoSense-Desktop 是 [OctoSense](https://github.com/OctoSense-org)(运行在 | `native-apps.lock.json` | OctoSense-System-Apps 的版本。 | | `runtime-patches.lock.json` | 固定 Makepad 之上经过评审的补丁及其期望的源码树。目前为空:固定的 Makepad 已包含 Shell 所需的一切。 | | `tools/setup-native.py` | 准备并检查固定版本的同级目录。 | -| `scripts/` | `upstream.py`(WM 来源记录与目录重新生成)、`smoke.py`(原生冒烟测试)、它们的 Python 测试,以及 `provision-appcard-llm.sh`(Android)。 | +| `scripts/` | `upstream.py`(WM 来源记录与目录重新生成)、`smoke.py`(原生冒烟测试)、它们的 Python 测试、`system_apps_remote.sh` 和 `ai_providers_remote.sh`(以隐藏窗口 `--remote` 运行的系统应用与 AI 提供商端到端测试),以及 `provision-appcard-llm.sh`(Android)。 | | `upstream/makepad.json` | 每个从 Makepad `apps/wm` 引入的文件的来源记录。 | | `resources/` | 主题、壁纸、图标、Android manifest 模板、启动脚本。 | | `docs/` | [验证记录](docs/validation.md)、[上游同步](docs/upstream.md)、[本地 AI](docs/local-ai.md)、[Android AppCard 构建](docs/android-appcard-build.md)、按日期的计划文档。 | @@ -105,7 +105,7 @@ cargo build --release --workspace | Feature | 默认 | 作用 | | --- | --- | --- | -| `app-hub` | 开 | 链接 `octosense-app-hub-app`(商店 `apphub`、Card 运行器 `card`、系统应用)以及邮件宿主服务 `octosense-mail-service`。关闭后构建中没有 App Hub,也没有系统应用。 | +| `app-hub` | 开 | 链接 `octosense-app-hub-app`(商店 `apphub`、Card 运行器 `card`、系统应用)以及宿主服务 `octosense-mail-service`(邮件)和 `octosense-llm-service`(AI 提供商)。关闭后构建中没有 App Hub,也没有系统应用。 | | `app-reference` | 关 | 把 Reference 作为模块链接。 | | `app-sheets` | 关 | 把 Makepad 的 Sheets 作为模块链接。 | | `app-photos` | 关 | 链接 Makepad 的原生 Photos 模块;它会替换同 id 的相册系统应用(用于对比)。 | @@ -138,6 +138,7 @@ App Hub 的模块是例外:它们没有进程形态,总是在进程内打开 | `OCTOSENSE_SYSTEM_APPS` | 系统应用选择文件;`.cargo/config.toml` 将其设为 `system-apps.json`。 | | `MAKEPAD_APP_CONFIG='{"mail_demo":true}'` | 提供邮件的演示邮箱(见[演示](#演示))。 | | `OCTOSENSE_MAIL_VAULT=file` | 把邮件密码保存在权限为 0600 的文件中,而不是 macOS 钥匙串。 | +| `OCTOSENSE_LLM_VAULT=file` | 把 AI 提供商的密钥保存在仅所有者可读的 octos 配置文件中,而不是 macOS 钥匙串。 | | `OCTOS_APP_CORE_BIN`、`OCTOS_APP_CORE_DIR` | 让 AppCard 助手使用本地的 octos 内核。 | | `MAKEPAD_REMOTE`、`MAKEPAD_HIDE_WINDOWS` | 远程控制桥;隐藏窗口(见[演示](#演示))。 | @@ -147,7 +148,7 @@ launcher 把四类应用列在一起: | 类别 | 来源 | 运行方式 | Launcher id | | --- | --- | --- | --- | -| **系统应用**:新闻、相册、地图、相机、邮件 | OctoSense-System-Apps 的 `apps//bundle`,由 `system-apps.json` 选择,打包进构建 | App Hub 的 Card 运行器中隔离运行的 Splash 程序,每个应用一个 isolate,只拥有其清单申请的能力 | ``(清单 id `os.`) | +| **系统应用**:新闻、相册、地图、相机、邮件、AI 提供商 | OctoSense-System-Apps 的 `apps//bundle`,由 `system-apps.json` 选择,打包进构建 | App Hub 的 Card 运行器中隔离运行的 Splash 程序,每个应用一个 isolate,只拥有其清单申请的能力 | ``(清单 id `os.`) | | **商店应用** | 签名的 App Hub 目录,从商店(`apphub`)安装 | 同一个 Card 运行器。每次打开都会对照目录检查;更新会关闭旧实例。 | `hub:` | | **原生模块** | 链接进本二进制的 Rust crate | 进程内的 `AppModule`。只允许受信任的代码:App Hub、AppCard、Reference 以及各 `app-*` feature。 | 模块 id | | **开发者程序** | `config/apps.json` | tile 中的独立进程,通过 Makepad 的 `--stdin-loop` 托管协议运行,首次启动时构建 | 目录 `id` | @@ -168,6 +169,8 @@ launcher 把四类应用列在一起: - 服务先测试账户,再把密码存入平台的密钥存储(macOS 钥匙串),并且只把账户授予添加它的应用。 - 邮件状态保存在宿主自己的目录中,位于所有应用的沙箱之外。 +AI 提供商(`os.ai-providers`)通过 `llm` 服务(`octosense-llm-service`,来自 `../OctoSense-System-Apps/apps/ai-providers/host-service`)编辑 AppCard 助手使用的 LLM 提供商。密钥只在宿主面板上输入,保存到 octos 读取的 macOS 钥匙串条目;提供商写入 AppCard 内核的 octos 配置(`OCTOS_APP_CORE_DIR`,否则为 `~/octos-home/.octos`)。手机上的提供商二维码可从图片导入:**Choose image** 打开文件面板,或把截图拖到导入面板上。**开始 → 设置 → AI providers** 可打开它。使用 `--features app-appcard` 时,更改会重启正在运行的 AppCard,使其内核读取新的提供商。 + 需要密码、PIN 或令牌的新功能,应放在宿主服务和宿主自有面板中,绝不放在应用自己的界面里。 ### 商店应用(App Hub) @@ -194,7 +197,7 @@ OCTOSENSE_HUB= OCTOSENSE_HUB_ANCHOR= \ { "schema": 1, "source": "../OctoSense-System-Apps/apps", - "apps": ["news", "photos", "maps", "camera", "mail"], + "apps": ["news", "photos", "maps", "camera", "mail", "ai-providers"], "assets": {} } ``` diff --git a/src/apps.rs b/src/apps.rs index 385f924a..30ac4244 100644 --- a/src/apps.rs +++ b/src/apps.rs @@ -127,10 +127,21 @@ pub fn system_card_apps() -> Vec { Vec::new() } -/// The services contained apps call through `host.request` (ADR 0004): `mail` -/// keeps accounts and passwords for the Mail app. `mail_demo` in -/// MAKEPAD_APP_CONFIG serves a demo mailbox from a file vault instead (no -/// keychain, no network): `MAKEPAD_APP_CONFIG='{"mail_demo":true}'`. +/// The services contained apps call through `host.request` (ADR 0004), +/// registered once, before the first system app can open: +/// +/// - `mail` keeps accounts and passwords for the Mail app. `mail_demo` in +/// MAKEPAD_APP_CONFIG serves a demo mailbox from a file vault instead (no +/// keychain, no network): `MAKEPAD_APP_CONFIG='{"mail_demo":true}'`. +/// - `llm` keeps the assistant's LLM providers for AI providers +/// (`os.ai-providers`), written to the AppCard kernel's octos profile +/// (`OCTOS_APP_CORE_DIR`, else `~/octos-home/.octos`), keys in the keychain +/// entry octos reads (`OCTOSENSE_LLM_VAULT=file` keeps them in the +/// owner-only profile). No camera scanner: a phone's profile QR is imported +/// from a picture of it (the open panel, or an image dropped on the import +/// sheet: `llm_image`), or by pasting its text. With AppCard linked +/// (`app-appcard`), a change restarts its core so the kernel reads the new +/// providers; without it there is nothing to restart. #[cfg(feature = "app-hub")] pub fn register_host_services() { static ONCE: std::sync::Once = std::sync::Once::new(); @@ -145,9 +156,36 @@ pub fn register_host_services() { } else { octosense_mail_service::register() } + let mut llm = octosense_llm_service::Options::default(); + if let Some(dir) = octosense_llm_config::profile::default_core_dir() { + llm = llm.core_dir(dir); + } + #[cfg(not(any(target_os = "android", target_os = "ios")))] + { + llm = llm.image_picker(crate::llm_image::picker()).image_drops(true); + } + #[cfg(feature = "app-appcard")] + { + llm = llm.on_changed(|| { + RESTART_APPCARD.store(true, std::sync::atomic::Ordering::Release); + makepad_widgets::makepad_platform::thread::SignalToUI::set_ui_signal(); + }); + } + octosense_llm_service::register_with(llm); }); } +/// Set (any thread) when AI providers changed the saved provider set. +#[cfg(all(feature = "app-hub", feature = "app-appcard"))] +static RESTART_APPCARD: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false); + +/// UI thread: whether the AppCard core should restart to read a changed +/// provider set (once per change). +#[cfg(all(feature = "app-hub", feature = "app-appcard"))] +pub fn take_appcard_restart() -> bool { + RESTART_APPCARD.swap(false, std::sync::atomic::Ordering::AcqRel) +} + /// Apps App Hub installed: each is an app of its own in the launcher, hosted /// by the linked `card` module under its `hub:` identity. Read /// fresh each time, so an install shows up without a restart. @@ -344,16 +382,16 @@ mod tests { let catalog = bundled_catalog(); // AppCard is opt-in (`app-appcard`), not part of `mobile-apps`. let expected: &[&str] = if cfg!(feature = "app-appcard") { - &["reference", "sheets", "appcard", "apphub", "news", "photos", "maps", "camera", "mail"] + &["reference", "sheets", "appcard", "apphub", "news", "photos", "maps", "camera", "mail", "ai-providers"] } else { - &["reference", "sheets", "apphub", "news", "photos", "maps", "camera", "mail"] + &["reference", "sheets", "apphub", "news", "photos", "maps", "camera", "mail", "ai-providers"] }; assert_eq!(catalog.iter().map(|app| app.id.as_str()).collect::>(), expected); assert!(catalog.iter().all(|app| app.manifest.is_none())); // The system apps have no native module: the Card runner hosts them, // launched by their manifest id (ADR 0004). let registry = AppRegistry::default(); - for id in ["news", "photos", "maps", "camera", "mail"] { + for id in ["news", "photos", "maps", "camera", "mail", "ai-providers"] { let app = catalog.iter().find(|app| app.id == id).unwrap(); assert_eq!(card_manifest_id(app), Some(format!("os.{id}").as_str())); assert_eq!(registry.module(id).map(|m| m.id()), Some("card")); @@ -405,7 +443,7 @@ mod tests { #[test] fn the_system_apps_ship_as_card_apps() { let ids: Vec = system_card_apps().into_iter().map(|app| app.id).collect(); - assert_eq!(ids, ["news", "photos", "maps", "camera", "mail"]); + assert_eq!(ids, ["news", "photos", "maps", "camera", "mail", "ai-providers"]); let registry = AppRegistry::default(); for id in &ids { assert_eq!(registry.hosting(id), Hosting::Module); diff --git a/src/llm_image.rs b/src/llm_image.rs new file mode 100644 index 00000000..70da8e21 --- /dev/null +++ b/src/llm_image.rs @@ -0,0 +1,155 @@ +//! The desktop's image source for AI providers' QR import (the `llm` host +//! service's `QrImagePicker`, OctoSense-System-Apps +//! `apps/ai-providers/host-service`): Makepad's native open panel filtered to +//! PNG and JPEG, and image files dropped on the AI providers window while +//! its import sheet is up. Either way the service gets the file's bytes and +//! reads the code out of them on a worker; the sheet then asks for the PIN. +//! +//! Test hook: `OCTOSENSE_LLM_TEST_IMAGE=` makes "Choose image" answer +//! with that file at once, without a panel (the remote e2e run is hidden and +//! cannot click through a modal). Development only. +use makepad_widgets::makepad_platform::file_dialogs::{FileDialog, FileDialogAction}; +use makepad_widgets::makepad_platform::thread::SignalToUI; +use makepad_widgets::*; +use octosense_llm_service::{ImageDone, PickError, QrImagePicker}; +use std::path::{Path, PathBuf}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Mutex, OnceLock}; + +/// The system app whose window takes dropped images (its short id). +const APP: &str = "ai-providers"; +/// Larger files are not read at all; the service refuses over 20 MB anyway. +const MAX_FILE_BYTES: u64 = 20 * 1024 * 1024; +const TEST_IMAGE_ENV: &str = "OCTOSENSE_LLM_TEST_IMAGE"; + +fn dialog_id() -> LiveId { + live_id!(octosense_llm_qr_image) +} + +#[derive(Default)] +pub struct DesktopPicker { + /// The pick waiting on the open panel. + pending: Mutex>, + /// The panel should open on the next UI event. + open: AtomicBool, +} + +impl QrImagePicker for DesktopPicker { + fn pick(&self, done: ImageDone) { + if let Some(path) = std::env::var_os(TEST_IMAGE_ENV).filter(|p| !p.is_empty()) { + let path = PathBuf::from(path); + std::thread::spawn(move || done(read_image(&path))); + return; + } + if let Some(earlier) = self.pending.lock().unwrap().replace(done) { + earlier(Err(PickError::Cancelled)); + } + self.open.store(true, Ordering::SeqCst); + SignalToUI::set_ui_signal(); + } +} + +/// The one picker the service is registered with. +pub fn picker() -> Arc { + static PICKER: OnceLock> = OnceLock::new(); + PICKER.get_or_init(Arc::default).clone() +} + +fn is_image(path: &Path) -> bool { + matches!( + path.extension().and_then(|e| e.to_str()).map(str::to_ascii_lowercase).as_deref(), + Some("png" | "jpg" | "jpeg") + ) +} + +/// A chosen or dropped file's bytes, off the UI thread. +fn read_image(path: &Path) -> Result, PickError> { + let size = std::fs::metadata(path).map_err(|e| PickError::Failed(e.kind().to_string()))?.len(); + if size > MAX_FILE_BYTES { + return Err(PickError::Failed("larger than 20 MB".into())); + } + std::fs::read(path).map_err(|e| PickError::Failed(e.kind().to_string())) +} + +/// On every UI event: open the panel a pick asked for. +pub fn open_requested(cx: &mut Cx) { + let picker = picker(); + if picker.open.swap(false, Ordering::SeqCst) { + cx.open_select_file_dialog( + FileDialog::new() + .set_id(dialog_id()) + .set_title("Choose a picture of the provider code".into()) + .add_filter("Images".into(), vec!["png".into(), "jpg".into(), "jpeg".into()]), + ); + } +} + +/// The panel's answer, from the actions pass. +pub fn handle_actions(actions: &Actions) { + for action in actions { + let Some(answer) = action.downcast_ref::() else { + continue; + }; + let result = match answer { + FileDialogAction::FileSelected { id, paths } if *id == dialog_id() => paths.first().cloned().ok_or(PickError::Cancelled), + FileDialogAction::FileCancelled { id } if *id == dialog_id() => Err(PickError::Cancelled), + _ => continue, + }; + let Some(done) = picker().pending.lock().unwrap().take() else { + continue; + }; + match result { + Ok(path) if is_image(&path) => { + std::thread::spawn(move || done(read_image(&path))); + } + Ok(_) => done(Err(PickError::Failed("not a PNG or JPEG file".into()))), + Err(e) => done(Err(e)), + } + } +} + +/// The one image file a drag or drop carries. +fn dropped_image(items: &[DragItem]) -> Option { + match items { + [DragItem::FilePath { path, .. }] if is_image(Path::new(path)) => Some(PathBuf::from(path)), + _ => None, + } +} + +/// A drag or drop of one image file over the AI providers window while its +/// import sheet waits for one: answered "copy", and on the drop the file goes +/// to the service. `app_at` names the app whose window is at a point. `true` +/// when the event was the service's. +pub fn handle_drop(event: &Event, app_at: impl Fn(Vec2d) -> Option) -> bool { + let over_import = |abs: Vec2d, items: &[DragItem]| { + let path = dropped_image(items)?; + (octosense_llm_service::wants_image() && app_at(abs).as_deref() == Some(APP)).then_some(path) + }; + match event { + Event::Drag(e) => { + if over_import(e.abs, &e.items).is_some() { + *e.response.lock().unwrap() = DragResponse::Copy; + *e.handled.lock().unwrap() = true; + return true; + } + false + } + Event::Drop(e) => { + let Some(path) = over_import(e.abs, &e.items) else { + return false; + }; + *e.handled.lock().unwrap() = true; + std::thread::spawn(move || { + // A file that cannot be read is offered as nothing: the sheet + // says it is not an image. + let bytes = read_image(&path).unwrap_or_default(); + if !octosense_llm_service::offer_image(bytes) { + log!("llm: the import sheet closed before the dropped image was read"); + } + }); + log!("llm: image dropped on the import sheet"); + true + } + _ => false, + } +} diff --git a/src/main.rs b/src/main.rs index ae5e617d..0c17f042 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,6 +30,8 @@ mod dock_warp; mod host; mod hub; mod layout; +#[cfg(all(feature = "app-hub", not(any(target_os = "android", target_os = "ios"))))] +mod llm_image; mod octosense; mod module_host; mod module_view; @@ -1396,6 +1398,28 @@ impl App { self.redraw_all(cx); } + /// The provider set changed (AI providers): end every AppCard module + /// instance, so its agent and its kernel child stop, and open AppCard + /// again when one was running: its new kernel reads the new profile. + #[cfg(all(feature = "app-hub", feature = "app-appcard"))] + fn restart_appcard_core(&mut self, cx: &mut Cx) { + let mut ended = 0; + while let Some(client) = self.module_host.client_of_module("appcard") { + log!("llm: providers changed: restarting the AppCard core (client {client})"); + self.request_close(cx, client); + ended += 1; + if ended >= 8 || self.module_host.client_of_module("appcard") == Some(client) { + break; + } + } + if ended == 0 { + log!("llm: providers changed; no AppCard core is running, the next one reads them"); + } else { + self.launch_app(cx, "appcard"); + } + self.redraw_all(cx); + } + fn request_close(&mut self, cx: &mut Cx, client: ClientId) { // A module instance has no process to ask politely and nothing to // reap later: it ends now, through the same removal as a death. @@ -4147,6 +4171,8 @@ impl MatchEvent for App { } fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions) { + #[cfg(all(feature = "app-hub", not(any(target_os = "android", target_os = "ios"))))] + llm_image::handle_actions(actions); if self.gallery { let gallery = self.ui.widget(cx, ids!(shell_gallery)); { @@ -4461,6 +4487,29 @@ impl AppMain for App { { return; } + // AI providers' QR import: the open panel a pick asked for, and an + // image dropped on its window while the import sheet waits. + #[cfg(all(feature = "app-hub", not(any(target_os = "android", target_os = "ios"))))] + if self.state.is_some() { + llm_image::open_requested(cx); + if matches!(event, Event::Drag(_) | Event::Drop(_)) { + let desk = self.desk(cx); + let desk = desk.borrow::(); + let state = self.state.as_ref(); + let app_at = |p: Vec2d| { + let client = desk.as_ref()?.window_at(p)?; + state?.clients.get(&client).map(|slot| slot.app.clone()) + }; + if llm_image::handle_drop(event, app_at) { + return; + } + } + } + // AI providers changed the saved providers: restart the AppCard core. + #[cfg(all(feature = "app-hub", feature = "app-appcard"))] + if apps::take_appcard_restart() { + self.restart_appcard_core(cx); + } if self.phone_search_event(cx,event) {return;} if self.state.is_some() && self.phone_pointer(cx,event) {return;} if self.state.is_some() && self.snap_event(cx,event) {return;} diff --git a/src/shell/menu.rs b/src/shell/menu.rs index df6bcbf6..2b293852 100644 --- a/src/shell/menu.rs +++ b/src/shell/menu.rs @@ -482,6 +482,18 @@ impl MenuModel { ] {items.push(MenuItem::new(id,label,kind).icon(icon));} } let available = launcher::apps(); + // Settings gains a submenu once the build ships the AI providers + // system app (`os.ai-providers`): Appearance, then AI providers. + if path.starts_with("start") && available.iter().any(|app| app.id == AI_PROVIDERS) { + items.push( + MenuItem::new("start.settings.style", "Appearance", MenuKind::Menu) + .icon(Ico::Monitor), + ); + items.push( + MenuItem::new("start.settings.ai-providers", "AI providers", MenuKind::App) + .icon(Ico::Cpu), + ); + } items.retain(|item| { let app = match item.id.as_str() { "workspace.files" | "start.documents" => "apps.files", @@ -727,7 +739,8 @@ impl MenuModel { MenuKind::Menu => { let target=match row.target.as_str() { "start.programs"|"start.search"|"start.run"|"workspace.apps"=>"apps", - "start.settings"=>"style", + "start.settings" if !self.items.iter().any(|item| item.id=="start.settings.ai-providers")=>"style", + "start.settings.style"=>"style", "workspace.desktop"=>"desktop", other=>other, }; @@ -739,12 +752,16 @@ impl MenuModel { "workspace.files" => "apps.files".into(), "workspace.tools.terminal" => "apps.terminal".into(), "workspace.tools.task" => "apps.task".into(), + "start.settings.ai-providers" => AI_PROVIDERS.into(), _ => row.target, }), } } } +/// The launcher row of the AI providers system app (`os.ai-providers`). +const AI_PROVIDERS: &str = "apps.ai-providers"; + /// `MenuModel.js` `matchesQuery` + `searchScore`, lower is better. /// /// Every whitespace term has to match the label, the leaf id or an alias by @@ -1588,6 +1605,29 @@ mod tests { assert_eq!(centered_card_top(smaller, 280.0, 8.0, Some(300.0)), 138.0); } + /// Start > Settings is Appearance alone until the build ships the AI + /// providers system app; then it is a submenu that also opens it. + #[test] + fn start_settings_offers_ai_providers_once_it_ships() { + let mut model = MenuModel::default(); + model.open_at("", MenuSkin::Menu); + model.descend("start"); + let ships = launcher::apps().iter().any(|app| app.id == AI_PROVIDERS); + model.sel = model.rows.iter().position(|r| r.target == "start.settings").unwrap(); + assert_eq!(model.activate(), None); + if ships { + assert_eq!(model.path, "start.settings"); + model.sel = model + .rows + .iter() + .position(|r| r.target == "start.settings.ai-providers") + .unwrap(); + assert_eq!(model.activate().as_deref(), Some(AI_PROVIDERS)); + } else { + assert_eq!(model.path, "style"); + } + } + #[test] fn navigating_between_menus_recenters_after_a_search() { let mut model = MenuModel::default(); diff --git a/system-apps.json b/system-apps.json index 390574e0..933675f8 100644 --- a/system-apps.json +++ b/system-apps.json @@ -1,6 +1,6 @@ { "schema": 1, "source": "../OctoSense-System-Apps/apps", - "apps": ["news", "photos", "maps", "camera", "mail"], + "apps": ["news", "photos", "maps", "camera", "mail", "ai-providers"], "assets": {} } From 9bb85e5b0ca8d00cf0079b9d243dfd70f4fa4b38 Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Sat, 26 Sep 2026 16:23:53 -0700 Subject: [PATCH 32/42] Remote e2e runs for the system apps and AI providers scripts/system_apps_remote.sh and scripts/ai_providers_remote.sh drive a hidden release build (MAKEPAD_HIDE_WINDOWS=1, --remote) with a temp OCTOSENSE_HOME, octos core dir and file vaults. The AI providers run is sandboxed with outbound HTTPS denied and uses fake keys only: the add wizard, paste, drop and picker imports of the QR-A fixture, and the phone QR export read back and compared with the saved profile. The system-apps run also treats the Card runner building an app's first Splash VM on the UI thread as a known first-open hang, as it already did for the bundle unpack. Co-Authored-By: Claude Opus 5.5 --- scripts/ai_providers_remote.sh | 667 +++++++++++++++++++++++++++++++++ scripts/system_apps_remote.sh | 181 +++++++++ 2 files changed, 848 insertions(+) create mode 100755 scripts/ai_providers_remote.sh create mode 100755 scripts/system_apps_remote.sh diff --git a/scripts/ai_providers_remote.sh b/scripts/ai_providers_remote.sh new file mode 100755 index 00000000..778e51e2 --- /dev/null +++ b/scripts/ai_providers_remote.sh @@ -0,0 +1,667 @@ +#!/usr/bin/env bash +# AI providers' QR import from an image, and the phone QR it exports, on the +# desktop, driven through Makepad's remote (instrument) mode: no OS +# screenshots, only the app's own frames. +# +# cargo build --release [--config …] --features mobile-apps +# cargo build --release [--config …] -p octosense-llm-service --example read_qr_image +# scripts/ai_providers_remote.sh [artifacts-dir] +# +# Runs the release binary hidden with its own OCTOSENSE_HOME, octos core dir +# and file vaults under a temp directory (neither ~/.octosense nor the login +# keychain is touched). Every key in it is fake, and the binary runs under +# sandbox-exec with outbound HTTPS denied: no fake key reaches a real +# provider, and a provider test fails as a network failure. A local fake +# endpoint (fake.py, 127.0.0.1) stands in for a provider that answers. The +# flow: +# 1. AI providers opens; the host reports an image picker ("Import code +# from image"). +# W1. Add DeepSeek with the add wizard, step by step: 1 search the families +# and choose DeepSeek, 2 the model pull-down (DeepSeek V4 Flash, +# Recommended), 3 the route pull-down (Official API), 4 a fake key, 5 +# Test connection: the provider cannot be reached, Save stays disabled +# and "Save without testing" saves it as the primary. +# W2. Change its model with the card's model pull-down (DeepSeek V4 Pro). +# W3. Add Z.ai as a fallback on another model (GLM-4.7) and route (a +# custom OpenAI-compatible endpoint: the fake endpoint); Test connection +# passes ("Connected"), and "Add as fallback" saves it. +# W4. The saved-models list and _main.json say so; Test connection on a +# row; then both are removed with the cards' Remove (tap twice). +# 2. Import into the empty list by PASTE: an old single-provider code +# (DeepSeek deepseek-v4-flash, fake key ending fcb0) typed into the +# import sheet's code field is saved as the primary. +# 3. Add OpenAI with the wizard (its default gpt-4o on the official route) +# with the fake key sk-test-0000000000001234, saved without testing. +# 4. Import by DROP: the fixture QR-A (OctoSense-System-Apps +# config/tests/fixtures/qr-a.png, PIN 7K3M-9QX2: DeepSeek deepseek-chat +# and Z.ai glm-4.6) dropped on the import sheet through the native drop +# path (/drop), PIN typed. An import only adds, so there is no confirm +# step: the primary and OpenAI stay first, QR-A's providers are appended +# as fallbacks, and its DeepSeek key goes to a slot of its own +# (DEEPSEEK_2_API_KEY) so the primary's key is not overwritten. +# 5. Import QR-A again by PICKER: "Choose image" (OCTOSENSE_LLM_TEST_IMAGE +# answers with the fixture instead of the open panel, which a hidden run +# cannot click through); a wrong PIN is refused and changes nothing, the +# right one changes nothing either ("Already saved"). +# 6. Show QR for phone, grab the sheet, read the QR out of the grab (the +# service's own image search, rqrr) and open it with the PIN on the +# sheet: it equals the saved set and keys, both DeepSeek slots included. +# The countdown +# ticks, and at 0 the sheet closes by itself (OCTOSENSE_LLM_QR_SECONDS +# shortens its five minutes to $QR_SECONDS s), with no Splash error in +# the log. +# 7. No key text in /snap, /d, /log or the host log; no panic, no Splash +# error; /gq exits. +# Artifacts: grabs/, export.png (the export sheet at full scale) and +# export-pin.txt, for scanning on a phone. The script-drawn controls (the +# wizard's fields, rows and pull-downs, the app's cards) are not in /snap: +# they are found in grabs by their colours (blocks.py). +set -euo pipefail + +ROOT=$(cd "$(dirname "$0")/.." && pwd) +BIN=${OCTOSENSE_BIN:-$ROOT/target/release/octosense} +READ_QR=${READ_QR_IMAGE:-$ROOT/target/release/examples/read_qr_image} +SRC=$(cd "$ROOT" && python3 -c 'import json; print(json.load(open("system-apps.json"))["source"])') +FIXTURE=$(cd "$ROOT/$SRC/ai-providers/config/tests/fixtures" && pwd)/qr-a.png +FIXTURE_PIN=7K3M-9QX2 +FAKE_KEY=sk-test-0000000000001234 +# The old single-provider code the empty list is filled from (its key is fake). +LEGACY_CODE='{"llm_family":"deepseek","llm_model":"deepseek-v4-flash","llm_key":"sk-test-legacy-00000000fcb0"}' +# The phone QR's lifetime in this run: long enough to grab and read the code. +QR_SECONDS=${QR_SECONDS:-20} +# What a Splash script error logs (the export sheet's countdown used to log +# these every second). +SPLASH_ERRORS='pop_stack_resolved|mes empty|splash host callback error|\[E\] splash' +# Every key the run handles: none may show up in the UI tree or the logs. +SECRETS='sk-test-|zai-test-|0000000000001234|0000000000005678' +ZAI_KEY=sk-test-0000000000005678 +WORK=${1:-$(mktemp -d -t octosense-ai-providers)} +mkdir -p "$WORK/home" "$WORK/octos" "$WORK/grabs" +LOG=$WORK/host.log +PROFILE=$WORK/octos/profiles/_main.json +[ -x "$BIN" ] || { echo "no $BIN: build it first"; exit 2; } +[ -x "$READ_QR" ] || { echo "no $READ_QR: cargo build --release -p octosense-llm-service --example read_qr_image"; exit 2; } +echo "artifacts: $WORK" + +cd "$WORK" +: >"$LOG" +env -u MAKEPAD_HOME -u MAKEPAD_WM_ROOT -u MAKEPAD_WM_THEME \ + OCTOSENSE_HOME="$WORK/home" OCTOS_APP_CORE_DIR="$WORK/octos" \ + OCTOSENSE_MAIL_VAULT=file OCTOSENSE_LLM_VAULT=file \ + OCTOSENSE_LLM_TEST_IMAGE="$FIXTURE" OCTOSENSE_LLM_QR_SECONDS="$QR_SECONDS" \ + MAKEPAD_HIDE_WINDOWS=1 sandbox-exec -p '(version 1)(allow default)(deny network-outbound (remote tcp "*:443"))' \ + "$BIN" --remote >"$LOG" 2>&1 & +PID=$! +PORT= +# A provider that answers: OpenAI-compatible chat completions for the Z.ai +# fake key, on 127.0.0.1 (the sandbox lets local traffic through). +cat >"$WORK/fake.py" <<'EOF' +import http.server, json, sys +KEY = sys.argv[1] +class H(http.server.BaseHTTPRequestHandler): + def answer(self, ok, body): + data = json.dumps(body if ok else {"error": {"message": "Incorrect API key"}}).encode() + self.send_response(200 if ok else 401) + self.send_header("content-type", "application/json"); self.send_header("content-length", str(len(data))) + self.end_headers(); self.wfile.write(data) + def do_POST(self): + self.rfile.read(int(self.headers.get("content-length", 0))) + self.answer(self.headers.get("authorization") == "Bearer " + KEY, {"choices": []}) + def do_GET(self): + self.answer(self.headers.get("authorization") == "Bearer " + KEY, {"data": [{"id": "glm-4.7"}]}) + def log_message(self, *args): pass +server = http.server.HTTPServer(("127.0.0.1", 0), H) +print(server.server_address[1], flush=True) +server.serve_forever() +EOF +python3 "$WORK/fake.py" "$ZAI_KEY" >"$WORK/fake.port" 2>/dev/null & +FAKE=$! +disown "$FAKE" +cleanup() { + kill "$FAKE" 2>/dev/null || true + if kill -0 "$PID" 2>/dev/null; then + [ -n "$PORT" ] && curl -s "127.0.0.1:$PORT/quit" >/dev/null || true + sleep 2 + kill "$PID" 2>/dev/null || true + fi +} +trap cleanup EXIT + +fail() { echo "FAIL: $*"; [ -z "${HOLD_ON_FAIL:-}" ] || sleep "$HOLD_ON_FAIL"; exit 1; } +pass() { echo "PASS: $*"; } +for _ in $(seq 1 120); do + PORT=$(sed -n 's/.*\[makepad-remote\] listening on 127\.0\.0\.1:\([0-9]*\) pid=.*/\1/p' "$LOG" | head -1) + [ -n "$PORT" ] && break + sleep 0.5 +done +[ -n "$PORT" ] || fail "no remote port in $LOG" +get() { curl -fsS "127.0.0.1:$PORT/$1"; } +q() { python3 -c 'import sys,urllib.parse; print(urllib.parse.quote(sys.argv[1]))' "$1"; } +key() { + local out + out=$(curl -s "127.0.0.1:$PORT/k?k=press&c=$1&wait=1${2:-}") + case $out in *'"err"'*) echo "note: key $1: $out" ;; esac +} +type_keys() { + local text=$1 i c + for ((i = 0; i < ${#text}; i++)); do + c=${text:i:1} + case $c in + " ") key Space ;; + *) key "Key$(printf %s "$c" | tr a-z A-Z)" ;; + esac + done +} +# Input waits for the next frame; a hidden window sometimes misses it and +# the remote answers "retry" although the input was delivered. What input +# did is checked through the profile, the log and snapshots, so a missed +# frame is only noted (retrying could click twice). +input() { + local out + out=$(curl -s "127.0.0.1:$PORT/$1&wait=1") + case $out in *'"err"'*) echo "note: ${1%%\?*}: $out" ;; esac +} +text() { input "t?t=$(q "$1")"; } +click() { input "click?x=$1&y=$2"; } +scroll() { input "m?k=scroll&x=$1&y=$2&dy=$3"; } +# A grab at layout scale (one pixel a point); prints its path. +grab() { + local png + for _ in 1 2 3 4 5; do + png=$(curl -s "127.0.0.1:$PORT/g?scale=${2:-0.5}" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("png",""))' || true) + [ -n "$png" ] && { cp "$png" "$WORK/grabs/$1.png"; echo "$WORK/grabs/$1.png"; return; } + sleep 1 + done + fail "no frame for $1" +} +# The centre of the card widget whose text is exactly $1: "x y". +widget() { + get "snap?q=$(q "$1")" | python3 -c ' +import json,sys +hits=[e for e in json.load(sys.stdin)["s"] if e.get("t")==sys.argv[1] and e["r"][2]>0] +if hits: x,y,w,h=hits[-1]["r"]; print(x+w/2, y+h/2)' "$1" +} +wait_widget() { + for _ in $(seq 1 ${2:-40}); do [ -n "$(widget "$1")" ] && return 0; sleep 0.5; done + fail "never showed: $1" +} +# The host's sheet (its rect and its program) once it runs `$1`. +sheet_rect() { + get "snap?q=$(q "$1")" | python3 -c ' +import json,sys +hits=[e for e in json.load(sys.stdin)["s"] if e["i"]=="sheet" and sys.argv[1] in (e.get("t") or "")] +if hits: print(*hits[-1]["r"])' "$1" +} +wait_sheet() { + local r + for _ in $(seq 1 40); do r=$(sheet_rect "$1"); [ -n "$r" ] && { echo "$r"; return 0; }; sleep 0.5; done + fail "no sheet running $1" +} +wait_log() { + for _ in $(seq 1 ${2:-40}); do grep -q "$1" "$LOG" && return 0; sleep 0.5; done + fail "log never said: $1" +} +# Sheets are Splash isolates whose widgets /snap does not list: find their +# controls in a grab instead. Prints "pill X Y" (the blue action at the top +# right: Import, Save, Close) and "full Y" / "half X Y" for each grey field +# or button, top to bottom. +cat >"$WORK/sheet.py" <<'EOF' +import sys, zlib, struct +def read_png(path): + data = open(path, "rb").read() + pos, chunks = 8, {} + idat = b"" + while pos < len(data): + n, kind = struct.unpack(">I4s", data[pos:pos + 8]) + body = data[pos + 8:pos + 8 + n] + if kind == b"IHDR": w, h, depth, color = struct.unpack(">IIBB", body[:10]) + if kind == b"IDAT": idat += body + pos += 12 + n + assert depth == 8 and color in (2, 6), (depth, color) + bpp = 4 if color == 6 else 3 + raw, stride, rows, prev = zlib.decompress(idat), w * bpp, [], bytearray(w * bpp) + i = 0 + for _ in range(h): + f, line = raw[i], bytearray(raw[i + 1:i + 1 + stride]); i += 1 + stride + for x in range(stride): + a = line[x - bpp] if x >= bpp else 0 + b = prev[x]; c = prev[x - bpp] if x >= bpp else 0 + if f == 1: line[x] = (line[x] + a) & 255 + elif f == 2: line[x] = (line[x] + b) & 255 + elif f == 3: line[x] = (line[x] + (a + b) // 2) & 255 + elif f == 4: + p = a + b - c; pa, pb, pc = abs(p - a), abs(p - b), abs(p - c) + line[x] = (line[x] + (a if pa <= pb and pa <= pc else b if pb <= pc else c)) & 255 + rows.append(bytes(line)); prev = line + return w, h, bpp, rows +path, sx, sy, sw, sh = sys.argv[1], *map(float, sys.argv[2:6]) +w, h, bpp, rows = read_png(path) +px = lambda x, y: rows[y][x * bpp:x * bpp + 3] +x0, x1 = int(sx + 20), int(sx + sw - 20) +inner = x1 - x0 +grey = lambda p: 200 <= p[0] <= 248 and abs(p[0] - p[1]) <= 2 and 3 <= p[2] - p[0] <= 7 +blue = lambda p: p[0] < 60 and 100 <= p[1] <= 160 and p[2] > 200 +# The pill: the blue columns tall enough to be a filled button. +top = [y for y in range(int(sy), int(sy + 110))] +cols = [x for x in range(x0, x1) if sum(blue(px(x, y)) for y in top) >= 20] +if cols: + ys = [y for y in top if sum(blue(px(x, y)) for x in cols[::3]) >= len(cols[::3]) // 2] + print("pill", (cols[0] + cols[-1]) // 2, (ys[0] + ys[-1]) // 2 if ys else int(sy + 60)) +out, y = [], int(sy + 60) +count = lambda y: sum(grey(px(x, y)) for x in range(x0, x1, 2)) * 2 +while y < int(min(sy + sh, h)): + if count(y) >= 0.4 * inner: + start = y + while y < h and count(y) >= 0.4 * inner: y += 1 + if y - start >= 20: + row, runs, run = start + 3, [], None + for x in range(x0, x1): + if grey(px(x, row)): + run = [x, x] if run is None else [run[0], x] + elif run: runs.append(run); run = None + if run: runs.append(run) + runs = [r for r in runs if r[1] - r[0] >= 0.3 * inner] + mid = (start + y) // 2 + if runs and runs[0][1] - runs[0][0] >= 0.85 * inner: + if not any(l.startswith("full") for l in out): + # The note line just above the first full-width control: + # how far right its text reaches (0: no text). + ink = [x for x in range(x0, x1) for yy in range(start - 40, start - 6) if max(px(x, yy)) < 120] + out.append("note %d" % (max(ink) - sx if ink else 0)) + out.append("full %d" % mid) + else: + for r in runs: out.append("half %d %d" % ((r[0] + r[1]) // 2, mid)) + y += 1 +print("\n".join(out)) +EOF +# `controls NAME RECT…` grabs, then prints the sheet's controls. +controls() { local png; png=$(grab "$1"); shift; python3 "$WORK/sheet.py" "$png" "$@"; } +# The import sheet moved on to the PIN once its note reads "Code read from +# the image. Type the PIN shown beside it." (wider than "Choose a screenshot +# or photo of the code…" or none), with no script error on the way. +pin_prompt() { + local C n + for _ in $(seq 1 10); do + sleep 1 + C=$(controls "$@") + n=$(echo "$C" | awk '$1=="note"{print $2; exit}') + [ "${n:-0}" -ge 420 ] && break + done + if grep -q "not found in prototype chain" "$LOG"; then + grep "not found in prototype chain" "$LOG" | head -3 + fail "the import sheet's script failed" + fi + [ "${n:-0}" -ge 420 ] || return 1 + echo "$C" +} +nth_full() { awk -v n="$1" '$1=="full"{i++; if(i==n){print $2; exit}}'; } +pill() { awk '$1=="pill"{print $2, $3; exit}'; } +# Blocks of one colour in a grab (the wizard's fields, rows, pull-downs and +# buttons, the cards' controls): "cx cy w h" per block, top to bottom. +# `blocks.py SHEET_PY PNG RRGGBB [MINCOUNT] [MINW]`: rows with at least +# MINCOUNT pixels of the colour (+-3; +-30 for text, MINCOUNT 1) make bands +# (a text band may pause for 3 rows); a band splits where the colour pauses +# for more than 12 px across (24 for text); narrower blocks than MINW drop. +cat >"$WORK/blocks.py" <<'EOF' +import sys +exec(open(sys.argv[1]).read().split("path, sx, sy")[0]) +w, h, bpp, rows = read_png(sys.argv[2]) +want = tuple(int(sys.argv[3][i:i + 2], 16) for i in (0, 2, 4)) +mincount = int(sys.argv[4]) if len(sys.argv) > 4 else 40 +minw = int(sys.argv[5]) if len(sys.argv) > 5 else 40 +text = mincount <= 1 +gap, tol = (24, 30) if text else (12, 3) +def hits(y): + r = rows[y] + return [x for x in range(w) if abs(r[x * bpp] - want[0]) <= tol and abs(r[x * bpp + 1] - want[1]) <= tol and abs(r[x * bpp + 2] - want[2]) <= tol] +bands, cur, quiet = [], None, 0 +for y in range(h): + xs = hits(y) + if len(xs) >= mincount: + if cur is None: cur = [y, y, set()] + cur[1] = y; cur[2].update(xs); quiet = 0 + elif cur is not None: + quiet += 1 + if quiet > (3 if text else 0): bands.append(cur); cur = None; quiet = 0 +if cur: bands.append(cur) +out = [] +for y0, y1, xs in bands: + xs = sorted(xs); start = prev = xs[0] + for x in xs[1:] + [10 ** 9]: + if x - prev > gap: + if prev - start + 1 >= minw and y1 - y0 + 1 >= (6 if text else 14): + out.append(((start + prev) // 2, (y0 + y1) // 2, prev - start + 1, y1 - y0 + 1)) + start = x + prev = x +for b in sorted(out, key=lambda b: (b[1], b[0])): print(*b) +EOF +# The wizard's and the cards' colours (bundle/main.splash and +# host-service/src/sheets.rs in OctoSense-System-Apps): pull-down, option +# row, chosen row, field, Test connection, Save without testing, accent, +# danger. +PULLDOWN=edf2fc; OPTION=f6f8fc; CHOSEN=e8f0fe; FIELD=f2f2f7; TEST=e3edff; BYPASS=fff1e0; BLUE=007aff; RED=ff3b30 +in_png() { python3 "$WORK/blocks.py" "$WORK/sheet.py" "$1" "$2" "${3:-40}" "${4:-40}"; } +# `blocks NAME COLOUR [MINCOUNT] [MINW]`: grab, then the colour's blocks. +blocks() { local png; png=$(grab "$1"); in_png "$png" "$2" "${3:-40}" "${4:-40}"; } +nth() { awk -v n="$1" 'NR==n{print $1, $2; exit}'; } +# The app scrolls as one list: its top (the cards) or its end (the QR +# buttons, under the cards). +top_app() { scroll 548 400 -3000; sleep 1; } +bottom_app() { scroll 548 400 3000; sleep 1; } + +profile_families() { + python3 -c ' +import json,sys +llm=json.load(open(sys.argv[1]))["config"]["llm"] +print(" ".join(s["family_id"] for s in [llm.get("primary")]+llm.get("fallbacks",[]) if s))' "$PROFILE" +} + +for _ in $(seq 1 60); do curl -s "127.0.0.1:$PORT/snap?q=main_window" | grep -q '"s":\[{' && break; sleep 0.5; done +pass "desktop up on port $PORT (pid $PID)" + +# 1. AI providers opens and offers the image import. +key Space "&cmd=1"; type_keys "ai providers"; key Return +wait_log "card: os.ai-providers running under" +wait_widget "Import code from image" +pass "AI providers opened; the host has an image picker (Import code from image)" + +# The rows are drawn by the app's script (on_render), which /snap does not +# list: the masked keys are in the grab; the keys themselves in the profile. +keys_end_with() { + python3 -c ' +import json,sys +env=json.load(open(sys.argv[1]))["config"]["env_vars"] +want=dict(a.split("=") for a in sys.argv[2:]) +sys.exit(0 if sorted(env)==sorted(want) and all(env[k].endswith(v) for k,v in want.items()) else 1)' "$PROFILE" "$@" +} +# Each saved route's model and the key env var octos reads it from. +profile_routes() { + python3 -c ' +import json,sys +llm=json.load(open(sys.argv[1]))["config"]["llm"] +print(" ".join("%s:%s" % (s.get("model_id","-"), (s.get("route") or {}).get("api_key_env","-")) for s in [llm.get("primary")]+llm.get("fallbacks",[]) if s))' "$PROFILE" +} +# The import sheet closes by itself once the code is imported. +sheet_closes() { + for _ in $(seq 1 40); do [ -z "$(sheet_rect "$1")" ] && return 0; sleep 0.5; done + return 1 +} + +# The add wizard, driven like a person. `next_step NAME` grabs (NAME) and +# presses the footer's Next (or the save on step 5): the lowest blue button +# narrower than a field; greyed out, it is not blue, and the step fails. +next_step() { + local X Y + read -r X Y _ < <(blocks "$1" $BLUE 40 60 | awk '$3<400' | tail -1) || true + [ -n "$Y" ] || fail "$1: Next is not enabled" + click "$X" "$Y"; sleep 1.5 +} +# `pick NAME N`: open the step's pull-down and take its option N. +pick() { + local X Y W H X2 Y2 + read -r X Y W H _ < <(blocks "$1-closed" $PULLDOWN 40 400 | head -1) || true + [ -n "$Y" ] || fail "$1: no pull-down on this step" + click "$X" "$Y"; sleep 1.5 + read -r X2 Y2 _ < <(blocks "$1" $OPTION 40 400 | awk -v y=$((Y + H / 2)) '$2>y' | nth "$2") || true + [ -n "$Y2" ] || fail "$1: the pull-down shows no option $2" + click "$X2" "$Y2"; sleep 1 +} +# `add_model NAME QUERY MODEL_N ROUTE_N KEY [BASE]`: the five steps. Step 1 +# searches the families for QUERY and chooses the first; steps 2 and 3 take +# option MODEL_N / ROUTE_N of their pull-downs (0: keep the preselected +# one), and BASE goes into the custom route's base URL; step 4 takes KEY; +# step 5 tests, then saves (Save once the test passes, else Save without +# testing after a network failure); SAVED says which: tested or untested. +add_model() { + local name=$1 query=$2 model_n=$3 route_n=$4 key=$5 base=${6:-} X Y before + SAVED= + before=$(cat "$PROFILE" 2>/dev/null || true) + read -r X Y _ < <(widget "Add model") || true; click "$X" "$Y" + read -r SX SY SW SH < <(wait_sheet "llm.sheet.fetch_models") + sleep 1.5 + read -r X Y _ < <(blocks "$name-1-start" $FIELD 40 400 | nth 1) || true + [ -n "$Y" ] || fail "$name: no search field on step 1" + click "$X" "$Y"; text "$query"; sleep 1.5 + read -r X Y _ < <(blocks "$name-1-search" $OPTION 40 400 | nth 1) || true + [ -n "$Y" ] || fail "$name: no family matches $query" + click "$X" "$Y"; sleep 1.5 + [ -n "$(blocks "$name-1-family" $CHOSEN 40 400)" ] || fail "$name: the family row is not marked chosen" + next_step "$name-1-next" + [ "$model_n" -gt 0 ] && pick "$name-2-model" "$model_n" + next_step "$name-2-next" + [ "$route_n" -gt 0 ] && pick "$name-3-route" "$route_n" + if [ -n "$base" ]; then + read -r X Y _ < <(blocks "$name-3-custom" $FIELD 40 400 | nth 1) || true + [ -n "$Y" ] || fail "$name: no base URL field" + click "$X" "$Y"; text "$base"; sleep 1 + fi + next_step "$name-3-next" + read -r X Y _ < <(blocks "$name-4-key" $FIELD 40 400 | nth 1) || true + [ -n "$Y" ] || fail "$name: no key field on step 4" + click "$X" "$Y"; text "$key"; sleep 1 + next_step "$name-4-next" + read -r X Y _ < <(blocks "$name-5-review" $TEST 40 400 | head -1) || true + [ -n "$Y" ] || fail "$name: no Test connection on step 5" + [ -z "$(blocks "$name-5-review" $BLUE 40 60 | awk '$3<400')" ] || fail "$name: Save is enabled before a test" + click "$X" "$Y" + for _ in $(seq 1 15); do + sleep 2 + read -r X Y _ < <(blocks "$name-5-tested" $BLUE 40 60 | awk '$3<400' | tail -1) || true + if [ -n "$Y" ]; then click "$X" "$Y"; SAVED=tested; break; fi + read -r X Y _ < <(in_png "$WORK/grabs/$name-5-tested.png" $BYPASS 40 100 | head -1) || true + if [ -n "$Y" ]; then click "$X" "$Y"; SAVED=untested; break; fi + done + [ -n "$Y" ] || fail "$name: the test neither passed nor offered Save without testing" + # The result line: green "Connected · N ms", or red "Couldn't connect: …". + if [ "$SAVED" = tested ]; then + [ -n "$(in_png "$WORK/grabs/$name-5-tested.png" 248a3d 1 100)" ] || fail "$name: no green Connected line (grab $name-5-tested)" + else + [ -n "$(in_png "$WORK/grabs/$name-5-tested.png" c4281c 1 100)" ] || fail "$name: no red Couldn't connect line (grab $name-5-tested)" + fi + for _ in $(seq 1 20); do [ "$(cat "$PROFILE" 2>/dev/null || true)" != "$before" ] && return 0; sleep 0.5; done + fail "$name: the save did not change the profile" +} +profile_json() { python3 -c 'import json,sys; print(json.dumps(json.load(open(sys.argv[1]))["config"].get(sys.argv[2]), sort_keys=True))' "$PROFILE" "$1"; } + +# W1. DeepSeek with the wizard; the provider is unreachable (sandbox). +add_model w1 deepseek 1 1 "$FAKE_KEY" +[ "$SAVED" = untested ] || fail "DeepSeek was not saved without testing" +wait_widget "Added DeepSeek · deepseek-v4-flash" +sheet_closes "llm.sheet.fetch_models" || fail "the wizard stayed up after the save" +[ "$(profile_json llm)" = '{"fallbacks": [], "primary": {"family_id": "deepseek", "model_id": "deepseek-v4-flash"}}' ] \ + || fail "profile after the wizard: $(profile_json llm)" +keys_end_with DEEPSEEK_API_KEY=1234 || fail "the DeepSeek key is not in the profile" +pass "DeepSeek added with the wizard: family search, model pull-down (DeepSeek V4 Flash, Recommended), route pull-down (Official API), key, Test connection failed (unreachable), Save without testing (grabs w1-1-family, w1-2-model, w1-3-route, w1-4-next, w1-5-review, w1-5-tested)" + +# W2. The card's model pull-down. +read -r X Y _ < <(blocks w2-list $PULLDOWN 40 400 | nth 1) || true +[ -n "$Y" ] || fail "no model pull-down on the DeepSeek card" +click "$X" "$Y"; sleep 2 +read -r X Y _ < <(blocks w2-pulldown $OPTION 40 400 | nth 2) || true +[ -n "$Y" ] || fail "the card's model pull-down has no second model" +click "$X" "$Y" +wait_widget "DeepSeek now uses DeepSeek V4 Pro." +[ "$(profile_json llm)" = '{"fallbacks": [], "primary": {"family_id": "deepseek", "model_id": "deepseek-v4-pro"}}' ] \ + || fail "profile after the model change: $(profile_json llm)" +pass "the card's model pull-down listed DeepSeek's catalog models and switched to DeepSeek V4 Pro (grab w2-pulldown)" + +# W3. Z.ai as a fallback: GLM-4.7 (the second model) on a custom +# OpenAI-compatible route (the route pull-down's option after Official API), +# the local fake endpoint: the test passes. +FAKE_PORT=$(head -1 "$WORK/fake.port") +[ -n "$FAKE_PORT" ] || fail "the fake endpoint did not start" +ZAI_BASE="http://127.0.0.1:$FAKE_PORT/v1" +add_model w3 "z.ai" 2 2 "$ZAI_KEY" "$ZAI_BASE" +[ "$SAVED" = tested ] || fail "Z.ai was not saved after a passing test" +wait_widget "Added Z.ai · glm-4.7" +sheet_closes "llm.sheet.fetch_models" || fail "the wizard stayed up after the Z.ai save" +[ "$(profile_json llm)" = '{"fallbacks": [{"family_id": "zai", "model_id": "glm-4.7", "route": {"api_type": "openai", "base_url": "'"$ZAI_BASE"'"}}], "primary": {"family_id": "deepseek", "model_id": "deepseek-v4-pro"}}' ] \ + || fail "profile after the Z.ai add: $(profile_json llm)" +keys_end_with DEEPSEEK_API_KEY=1234 ZAI_API_KEY=5678 || fail "the keys after the Z.ai add are wrong" +pass "Z.ai added as a fallback on GLM-4.7 and a custom OpenAI-compatible route; Test connection passed, Add as fallback saved it (grabs w3-2-model, w3-3-custom, w3-5-tested)" + +# W4. The list shows both; Test connection on the fallback's row passes; +# remove both with the cards' Remove (tap twice). +# Both cards in view: the list scrolled past the header. +top_app; scroll 548 400 220; sleep 1 +[ "$(blocks w4-saved-models $PULLDOWN 40 400 | wc -l | tr -d ' ')" = 2 ] || fail "the list does not show two model pull-downs (grab w4-saved-models)" +# Green text: the rows' key statuses, and "Connected" once the test passes. +GREEN=248a3d +read -r X Y _ < <(blocks w4-rows $TEST 40 60 | tail -1) || true +[ -n "$Y" ] || fail "no Test connection on the second row" +BEFORE=$(in_png "$WORK/grabs/w4-rows.png" $GREEN 1 60 | wc -l) +click "$X" "$Y" +for _ in $(seq 1 20); do sleep 1; [ "$(blocks w4-row-tested $GREEN 1 60 | wc -l)" -gt "$BEFORE" ] && break; done +[ "$(in_png "$WORK/grabs/w4-row-tested.png" $GREEN 1 60 | wc -l)" -gt "$BEFORE" ] || fail "the row's Test connection did not say Connected (grab w4-row-tested)" +for n in 1 2; do + top_app + read -r X Y _ < <(blocks "w4-remove-$n" $RED 1 30 | head -1) || true + [ -n "$Y" ] || fail "no Remove on the first card (grab w4-remove-$n)" + click "$X" "$Y"; sleep 1 + click "$X" "$Y"; sleep 2 +done +python3 -c 'import json,sys; sys.exit(0 if "llm" not in json.load(open(sys.argv[1]))["config"] else 1)' "$PROFILE" || fail "the list is not empty after removing both: $(profile_json llm)" +top_app; grab w4-empty >/dev/null +pass "the list showed DeepSeek V4 Pro (primary) and Z.ai GLM-4.7 (fallback 1); the fallback's Test connection said Connected; both removed with Remove, tapped twice (grabs w4-saved-models, w4-row-tested, w4-empty)" + +# 2. Import into the empty list by pasting an old single-provider code. +bottom_app; read -r X Y < <(widget "Import code from image"); click "$X" "$Y" +read -r SX SY SW SH < <(wait_sheet "llm.sheet.import") +sleep 1 +C=$(controls paste-sheet "$SX" "$SY" "$SW" "$SH") +CODE_Y=$(echo "$C" | nth_full 2); read -r PX PY < <(echo "$C" | pill) +[ -n "$CODE_Y" ] && [ -n "$PX" ] || { echo "$C"; fail "import sheet controls not found"; } +click $((SX + SW / 2)) "$CODE_Y"; text "$LEGACY_CODE"; click "$PX" "$PY" +wait_widget "Saved DeepSeek · deepseek-v4-flash as primary." +sheet_closes "llm.sheet.import" || fail "the import sheet stayed up after the paste" +[ "$(profile_routes)" = "deepseek-v4-flash:-" ] || fail "profile after the paste: $(profile_routes)" +keys_end_with DEEPSEEK_API_KEY=fcb0 || fail "the pasted key is not in the profile" +grab imported-by-paste >/dev/null +pass "an old single-provider code pasted into the empty list: DeepSeek deepseek-v4-flash is the primary (grab imported-by-paste)" + +# 3. Add OpenAI with a fake key on the host's sheet. +add_openai() { + add_model "$1" openai 0 0 "$FAKE_KEY" + [ "$SAVED" = untested ] || fail "OpenAI was not saved without testing" + wait_widget "Added OpenAI · gpt-4o" +} +add_openai add1 +[ "$(profile_families)" = "deepseek openai" ] || fail "profile after the add: $(profile_families)" +keys_end_with DEEPSEEK_API_KEY=fcb0 OPENAI_API_KEY=1234 || fail "OpenAI's key is not in the profile" +grab added-openai >/dev/null +pass "OpenAI added with the wizard (gpt-4o, official route), saved without testing (grab added-openai)" + +# 4. Import QR-A by drop onto the sheet: appended, no confirm step. +bottom_app; read -r X Y < <(widget "Import code from image"); click "$X" "$Y" +read -r SX SY SW SH < <(wait_sheet "llm.sheet.image") +sleep 1 +DROP=$(curl -s --get --data-urlencode "path=$FIXTURE" --data-urlencode "x=$((SX + SW / 2))" \ + --data-urlencode "y=$((SY + SH / 2))" --data-urlencode wait=1 "127.0.0.1:$PORT/drop") +case $DROP in *'"drop_handled":true'*'"drag_response":"copy"'*) ;; *) fail "the sheet did not take the drop: $DROP" ;; esac +wait_log "llm: image dropped on the import sheet" +C=$(pin_prompt drop-read "$SX" "$SY" "$SW" "$SH") || fail "the sheet did not ask for the PIN after the drop" +PIN_Y=$(echo "$C" | nth_full 3); read -r PX PY < <(echo "$C" | pill) +[ -n "$PIN_Y" ] && [ -n "$PX" ] || { echo "$C"; fail "import sheet controls not found"; } +click $((SX + SW / 2)) "$PIN_Y"; text "$FIXTURE_PIN"; click "$PX" "$PY" +wait_widget "Added DeepSeek · deepseek-chat, Z.ai · glm-4.6 as fallbacks." +sheet_closes "llm.sheet.image" || fail "the import sheet stayed up (a confirm step?)" +[ "$(profile_families)" = "deepseek openai deepseek zai" ] || fail "profile after the drop: $(profile_families)" +[ "$(profile_routes)" = "deepseek-v4-flash:- gpt-4o:- deepseek-chat:DEEPSEEK_2_API_KEY glm-4.6:-" ] || fail "routes after the drop: $(profile_routes)" +keys_end_with DEEPSEEK_API_KEY=fcb0 OPENAI_API_KEY=1234 DEEPSEEK_2_API_KEY=0000 ZAI_API_KEY=0000 \ + || fail "the keys after the drop are wrong (the primary's key must be untouched)" +grab imported-by-drop >/dev/null +pass "dropped QR-A appended as fallbacks without a confirm step: primary + OpenAI first, QR-A's DeepSeek in DEEPSEEK_2_API_KEY, the primary's key untouched (grab imported-by-drop)" + +# 5. Import QR-A again by the picker (test image), a wrong PIN first: the same +# code again changes nothing. +pick_and_unlock() { + local X Y C + bottom_app; read -r X Y < <(widget "Import code from image"); click "$X" "$Y" + read -r SX SY SW SH < <(wait_sheet "llm.sheet.pick") + sleep 1 + C=$(controls "$1-sheet" "$SX" "$SY" "$SW" "$SH") + CHOOSE_Y=$(echo "$C" | nth_full 1); PIN_Y=$(echo "$C" | nth_full 3); read -r PX PY < <(echo "$C" | pill) + click $((SX + SW / 2)) "$CHOOSE_Y" + pin_prompt "$1-picked" "$SX" "$SY" "$SW" "$SH" >/dev/null || fail "the sheet did not ask for the PIN after the pick" +} +pick_and_unlock pick1 +BEFORE=$(cat "$PROFILE") +click $((SX + SW / 2)) "$PIN_Y"; text "0000-0000"; click "$PX" "$PY"; sleep 4 +[ -n "$(sheet_rect "llm.sheet.pick")" ] || fail "a wrong PIN closed the sheet" +[ "$(cat "$PROFILE")" = "$BEFORE" ] || fail "a wrong PIN changed the profile" +grab wrong-pin >/dev/null +click $((SX + SW / 2)) "$PIN_Y" +for _ in 1 2 3 4 5 6 7 8 9; do key Backspace; done +text "$FIXTURE_PIN"; click "$PX" "$PY" +wait_widget "Already saved: DeepSeek · deepseek-chat, Z.ai · glm-4.6." +sheet_closes "llm.sheet.pick" || fail "the import sheet stayed up after the second import" +[ "$(cat "$PROFILE")" = "$BEFORE" ] || fail "importing QR-A again changed the profile" +grab reimported >/dev/null +pass "QR-A picked and imported again after a refused wrong PIN: nothing changed, no duplicates (grab reimported)" + +# 6. The phone QR: show the code, read it from the grab. +LOG_MARK=$(( $(wc -l <"$LOG") + 1 )) +bottom_app; read -r X Y < <(widget "Show QR for phone"); click "$X" "$Y" +read -r SX SY SW SH < <(wait_sheet "countdown :=") +SHOWN=$(date +%s) +sleep 1 +PIN=$(get "snap?q=$(q "pin := Label")" | python3 -c ' +import json,re,sys +for e in json.load(sys.stdin)["s"]: + m=re.search(r"pin := Label\{[^\n]*text: \"([A-Z0-9-]+)\"", e.get("t") or "") + if m: print(m.group(1))' | tail -1) +[ -n "$PIN" ] || fail "no PIN on the export sheet" +EXPORT=$(grab export 1) +cp "$EXPORT" "$WORK/export.png"; echo "$PIN" >"$WORK/export-pin.txt" +READ=$("$READ_QR" "$EXPORT" "$PIN" "$WORK/octos") || fail "the exported QR does not match the profile: $READ" +echo "$READ" | grep -q '"same_as_profile":true' || fail "$READ" +echo "$READ" | grep -q '"family":"openai"' || fail "OpenAI missing from the code: $READ" +echo "$READ" | grep -q '"DEEPSEEK_2_API_KEY":"••••0000"' || fail "the second DeepSeek slot is missing from the code: $READ" +echo "$READ" | grep -q '"DEEPSEEK_API_KEY":"••••fcb0"' || fail "the primary's key is missing from the code: $READ" +pass "export QR read back from the grab (rqrr) with PIN $PIN: same set and keys as _main.json, both DeepSeek slots included" +# The countdown ticks: two grabs of the sheet two seconds apart differ (only +# the "Expires in" line, below the PIN, changes on it: scroll down to it). +same_sheet() { + python3 - "$1" "$2" "$SX" "$SY" "$SW" "$SH" "$WORK/sheet.py" <<'PY' +import sys +exec(open(sys.argv[7]).read().split("path, sx, sy")[0]) +a, b = read_png(sys.argv[1]), read_png(sys.argv[2]) +sx, sy, sw, sh = map(int, map(float, sys.argv[3:7])) +rows = range(sy, min(sy + sh, a[1], b[1])) +diff = sum(a[3][y][sx * a[2]:(sx + sw) * a[2]] != b[3][y][sx * b[2]:(sx + sw) * b[2]] for y in rows) +sys.exit(0 if diff == 0 else 1) +PY +} +scroll $((SX + SW / 2)) $((SY + SH / 2)) 600; sleep 1 +T1=$(grab countdown-1); sleep 2; T2=$(grab countdown-2) +same_sheet "$T1" "$T2" && fail "the countdown does not tick (grabs countdown-1 and countdown-2 are the same)" +# At 0 the sheet closes itself: the service's own close comes 5 s after the +# lifetime (counted from before the code was sealed), so a sheet gone within +# the lifetime + 2 s closed itself. +for _ in $(seq 1 $((QR_SECONDS * 2 + 20))); do [ -z "$(sheet_rect "countdown :=")" ] && break; sleep 0.5; done +CLOSED=$(( $(date +%s) - SHOWN )) +[ -z "$(sheet_rect "countdown :=")" ] || fail "the export sheet did not close after $QR_SECONDS s" +[ "$CLOSED" -le $((QR_SECONDS + 2)) ] || fail "the export sheet closed after ${CLOSED} s, not by its own countdown ($QR_SECONDS s)" +grab export-closed >/dev/null +if tail -n +"$LOG_MARK" "$LOG" | grep -Eq "$SPLASH_ERRORS"; then + tail -n +"$LOG_MARK" "$LOG" | grep -E "$SPLASH_ERRORS" | head -5 + fail "Splash errors while the export sheet was up" +fi +pass "the countdown ticked and the sheet closed itself after ${CLOSED} s ($QR_SECONDS s lifetime); no Splash error during the export" + +# 7. No key text anywhere the UI or logs show; no panic. +get "snap?all=1" >"$WORK/snap.json"; get "d" >"$WORK/dump.txt"; get "log?n=5000" >"$WORK/remote-log.json" +if grep -Eq "$SECRETS" "$WORK/snap.json" "$WORK/dump.txt" "$WORK/remote-log.json" "$LOG"; then + grep -Eo ".{40}($SECRETS).{10}" "$WORK/snap.json" "$WORK/dump.txt" "$WORK/remote-log.json" "$LOG" | head -5 + fail "key text in the UI tree or a log" +fi +grep -q "panicked" "$LOG" "$WORK/remote-log.json" && fail "panic in the log" +if grep -Eq "$SPLASH_ERRORS" "$LOG"; then grep -E "$SPLASH_ERRORS" "$LOG" | head -5; fail "Splash errors in the host log"; fi +pass "no key text in /snap, /d, /log or the host log; no panic, no Splash error" + +get "gq?scale=0.5" >/dev/null +for _ in $(seq 1 30); do kill -0 "$PID" 2>/dev/null || break; sleep 0.5; done +kill -0 "$PID" 2>/dev/null && fail "the shell did not exit after /gq" +kill "$FAKE" 2>/dev/null || true +trap - EXIT +pass "shell exited after /gq; grabs in $WORK/grabs, export.png + export-pin.txt in $WORK" diff --git a/scripts/system_apps_remote.sh b/scripts/system_apps_remote.sh new file mode 100755 index 00000000..b4e4f198 --- /dev/null +++ b/scripts/system_apps_remote.sh @@ -0,0 +1,181 @@ +#!/usr/bin/env bash +# The system apps on the desktop, driven through Makepad's remote +# (instrument) mode: no OS screenshots, only the app's own frames. +# +# cargo build --release [--config …] && scripts/system_apps_remote.sh [artifacts-dir] +# +# Runs the release binary hidden, with its own OCTOSENSE_HOME, octos core dir +# and file vaults under a temp directory, so neither ~/.octosense nor the login +# keychain is touched. Checks: the launcher lists every selected system app; +# Photos opens and draws (no network needed); Mail opens and its host-owned +# sign-in sheet appears (nobody signs in); each app runs in an isolate under +# its manifest's policy; bundles unpack under /apps/.system//; +# AI providers (when selected) opens on its empty provider list; the log has +# no panic and no UI hang outside the known ones. Grabs are kept as evidence. +set -euo pipefail + +ROOT=$(cd "$(dirname "$0")/.." && pwd) +BIN=${OCTOSENSE_BIN:-$ROOT/target/release/octosense} +WORK=${1:-$(mktemp -d -t octosense-system-apps)} +mkdir -p "$WORK/home" "$WORK/octos" "$WORK/grabs" +LOG=$WORK/host.log +APPS=$(python3 -c 'import json,sys; print(" ".join(json.load(open(sys.argv[1]))["apps"]))' "$ROOT/system-apps.json") +echo "artifacts: $WORK" +echo "system apps: $APPS" + +# From the temp directory: Maps' tile cache is written relative to the +# working directory (./local/tilecache_v4), not under the app's jail. +cd "$WORK" +env -u MAKEPAD_HOME -u MAKEPAD_WM_ROOT -u MAKEPAD_WM_THEME \ + OCTOSENSE_HOME="$WORK/home" OCTOS_APP_CORE_DIR="$WORK/octos" \ + OCTOSENSE_MAIL_VAULT=file OCTOSENSE_LLM_VAULT=file \ + MAKEPAD_HIDE_WINDOWS=1 "$BIN" --remote >"$LOG" 2>&1 & +PID=$! +PORT= +cleanup() { + if kill -0 "$PID" 2>/dev/null; then + [ -n "$PORT" ] && curl -s "127.0.0.1:$PORT/quit" >/dev/null || true + sleep 2 + kill "$PID" 2>/dev/null || true + fi +} +trap cleanup EXIT + +fail() { echo "FAIL: $*"; exit 1; } +pass() { echo "PASS: $*"; } +for _ in $(seq 1 120); do + PORT=$(sed -n 's/.*\[makepad-remote\] listening on 127\.0\.0\.1:\([0-9]*\) pid=.*/\1/p' "$LOG" | head -1) + [ -n "$PORT" ] && break + sleep 0.5 +done +[ -n "$PORT" ] || fail "no remote port in $LOG" +get() { curl -fsS "127.0.0.1:$PORT/$1"; } +# Input waits for the next frame; while a bundle unpacks on the UI thread the +# wait can time out after the key was delivered. What a key did is checked +# through the log and snapshots, so a timed-out wait is only noted. +key() { + local out + out=$(curl -s "127.0.0.1:$PORT/k?k=press&c=$1&wait=1${2:-}") + case $out in *'"err"'*) echo "note: key $1: $out" ;; esac +} +grab() { + local png + for _ in 1 2 3 4 5; do + # A hidden window's grab can miss its frame ("retry"): ask again. + png=$(curl -s "127.0.0.1:$PORT/g?scale=0.5" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("png",""))' || true) + [ -n "$png" ] && { cp "$png" "$WORK/grabs/$1.png"; return; } + sleep 1 + done + fail "no frame for $1" +} +# `/snap` texts, one per line, for a query. +snap() { get "snap?q=$(python3 -c 'import sys,urllib.parse; print(urllib.parse.quote(sys.argv[1]))' "$1")" \ + | python3 -c 'import json,sys; [print(e["i"], e["ty"]) for e in json.load(sys.stdin)["s"]]'; } +wait_log() { + for _ in $(seq 1 ${2:-40}); do grep -q "$1" "$LOG" && return 0; sleep 0.5; done + fail "log never said: $1" +} +# Type into the Start menu's search (KeyDown characters, as the smoke test does). +type_keys() { + local text=$1 i c + for ((i = 0; i < ${#text}; i++)); do + c=${text:i:1} + case $c in + " ") key Space ;; + -) key Minus ;; + *) key "Key$(printf %s "$c" | tr a-z A-Z)" ;; + esac + done +} +launch() { key Space "&cmd=1"; type_keys "$1"; key Return; } +label_of() { # the manifest name of a selected app + python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["name"])' \ + "$ROOT/$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["source"])' "$ROOT/system-apps.json")/$1/bundle/manifest.json" +} + +for _ in $(seq 1 60); do curl -s "127.0.0.1:$PORT/snap?q=main_window" | grep -q '"s":\[{' && break; sleep 0.5; done +pass "desktop up on port $PORT (pid $PID)" + +# 1. The launcher lists each system app (its short id, its manifest name). +for app in $APPS; do + key Space "&cmd=1" + type_keys "$app" + grab "launcher-$app" + key Escape +done +python3 - "$WORK/grabs" $APPS <<'EOF' || fail "launcher grabs missing" +import sys, pathlib +d = pathlib.Path(sys.argv[1]) +assert all((d / f"launcher-{a}.png").stat().st_size > 10000 for a in sys.argv[2:]) +EOF +grep -q 'wm: modules linked: .*"card"' "$LOG" || fail "the Card runner is not linked" +pass "launcher searched for: $APPS (grabs launcher-*.png); Card runner linked" + +# 2. Photos: opens in the Card runner and draws its library. +launch photos +wait_log "card: os.photos running under" +for _ in $(seq 1 20); do snap "Collections" | grep -q "^card Splash" && break; sleep 0.5; done +snap "Collections" | grep -q "^card Splash" || fail "Photos drew nothing" +grab photos +pass "Photos opened: $(grep -o 'card: os.photos running under.*' "$LOG" | head -1)" + +# 3. Mail: opens, and its sign-in is the host's sheet, not the app's. +launch mail +wait_log "card: os.mail running under" +sleep 2 +grab mail-welcome +snap "Add account" | grep -q "^card Splash" || fail "Mail shows no Add account" +# The button is the Splash's; click its centre as drawn in the welcome pane. +read -r X Y < <(get "snap?q=card" | python3 -c ' +import json,sys +cards=[e for e in json.load(sys.stdin)["s"] if e["i"]=="card" and "account = nil" in (e.get("t") or "")] +x,y,w,h=cards[-1]["r"]; print(x+w/2, y+h*0.567)') +get "click?x=$X&y=$Y&wait=1" >/dev/null +for _ in $(seq 1 20); do snap "Add a mail account" | grep -q "^sheet Splash" && break; sleep 0.5; done +snap "Add a mail account" | grep -q "^sheet Splash" || fail "Mail's sign-in sheet did not open" +grab mail-sheet +pass "Mail's host-owned sign-in sheet is up (not signed in)" + +# 4. AI providers, when this build selects it: opens on an empty list. +if [[ " $APPS " == *" ai-providers "* ]]; then + key Escape + launch "ai providers" + sleep 3 + if grep -q 'os.ai-providers requests unknown capability' "$LOG"; then + fail "App Hub's policy at this pin does not know the \`llm\` capability; AI providers is refused (its notice is drawn)" + fi + wait_log "card: os.ai-providers running under" + sleep 2 + grab ai-providers + snap "No models yet" | grep -q "^card Splash" || fail "AI providers shows no empty list" + NAME=$(label_of ai-providers) + [ -z "$(ls -A "$WORK/octos" 2>/dev/null)" ] || echo "note: octos core dir is not empty" + pass "$NAME opened: $(grep -o 'card: os.ai-providers running under.*' "$LOG" | head -1)" +fi + +# 5. Containment: one isolate per app under its policy, `os.` ids, bundles +# unpacked outside every jail under the temp home. +for app in photos mail; do + [ -f "$WORK/home/apps/.system/os.$app/"*/manifest.json ] || fail "os.$app not unpacked under $WORK/home/apps/.system" + [ -d "$WORK/home/apps/os.$app" ] || fail "os.$app has no jail" +done +pass "bundles unpacked under /apps/.system///, jails at /apps/" + +# 6. The log: no panic; UI hangs only where known (see KNOWN below). +get "log?n=2000" >"$WORK/remote-log.json" +grep -q "panicked" "$LOG" "$WORK/remote-log.json" && fail "panic in the log" +# Known: the catalog's startup Makepad-source probe, and App Hub unpacking and +# hashing a system bundle, or building an app's Splash VM, on the UI thread +# the first time it opens. +KNOWN='makepad_source::makepad_root|octosense_appstore::system::prepare|CxSplashVmExt>::alloc_splash_vm' +UNKNOWN=$(grep "\[ui-hang\]" "$LOG" | grep -Ev "$KNOWN" || true) +[ -z "$UNKNOWN" ] || { echo "$UNKNOWN" | cut -c1-400; fail "unexpected UI hang"; } +echo "note: $(grep -c "\[ui-hang\]" "$LOG" || true) known UI hang(s) (startup catalog probe, first-open bundle unpack or VM)" +pass "no panic, no unexpected [ui-hang]" + +# Finish: grab and quit, then confirm the process is gone. +get "gq?scale=0.5" >/dev/null +for _ in $(seq 1 30); do kill -0 "$PID" 2>/dev/null || break; sleep 0.5; done +kill -0 "$PID" 2>/dev/null && fail "the shell did not exit after /gq" +trap - EXIT +pass "shell exited after /gq; grabs in $WORK/grabs" From b7da5bec81abfc4ece0492c2b26a699b99be474d Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Sat, 26 Sep 2026 17:52:33 -0700 Subject: [PATCH 33/42] Docs: fix a dead anchor in local-ai.md Docs audit for hackathon contestants (second pass): docs/local-ai.md linked `../README.md#add-an-app`, an anchor that no longer exists; point it at "Developer programs and the catalog". Kernel/AppCard/octos text is left to the in-flight octos-core work. Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/local-ai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/local-ai.md b/docs/local-ai.md index c1eb6d33..0abb4ce7 100644 --- a/docs/local-ai.md +++ b/docs/local-ai.md @@ -20,7 +20,7 @@ makepad_rev=$(python3 -c 'import json; print(json.load(open("upstream/makepad.js git -C ../makepad checkout --detach "$makepad_rev" ``` -For an existing checkout, keep its revision aligned through the [upstream workflow](upstream.md). A personal app catalog must also contain an `aichat` entry; see [app registration](../README.md#add-an-app). Opening the assistant builds it through Cargo on first launch. It does not require enabling the host's optional `app-aichat` feature. +For an existing checkout, keep its revision aligned through the [upstream workflow](upstream.md). A personal app catalog must also contain an `aichat` entry; see [developer programs and the catalog](../README.md#developer-programs-and-the-catalog). Opening the assistant builds it through Cargo on first launch. It does not require enabling the host's optional `app-aichat` feature. ## Install the tested model From 0f0e4df93f1a4677210d85dca72e3997a533bde5 Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Sat, 26 Sep 2026 18:42:51 -0700 Subject: [PATCH 34/42] The octos kernel as a shell service (octos-core) System-Apps 47b96a0 (OctoSense-System-Apps#11): octosense-octos-core owns the octos kernel. OctoSense-Desktop now links it in every standard build: - feature `octos-core`, default, in `mobile-apps`, implied by `app-appcard`; Android and iOS always link it; - configured at startup (apps::configure_octos_kernel: the data dir, the shell's log), stopped on shutdown; on a desktop a kernel runs only when OCTOS_APP_CORE_BIN names one; - the `llm` service registers on the kernel's core dir with its `octos-core` feature, so a provider change restarts the kernel and its consumers reconnect; the app-appcard-only restart of AppCard instances is gone; - tools/android-kernel.py cross-builds octos at the rev Cargo.lock pins and runs the packager with MAKEPAD_ANDROID_EXTRA_LIBS=liboctos.so=..., so an APK bundles the kernel (tests in tools/test_android_kernel.py); - CI: the kernel service must be in the default and mobile-apps graphs; the AppCard UI (octosense-appcard) must not be; - docs (en + zh-CN): the kernel service, AI providers wording, octos pin 6ad76e5c, opt-out, the Android bundling. Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/workflows/runtime.yml | 20 +++++-- Cargo.lock | 18 +++++- Cargo.toml | 36 ++++++++---- README.md | 27 +++++---- README.zh-CN.md | 27 +++++---- apps/appcard/src/lib.rs | 19 +++--- docs/android-appcard-build.md | 62 ++++++++++--------- native-apps.lock.json | 2 +- src/apps.rs | 56 ++++++++++-------- src/main.rs | 35 +++-------- tools/android-kernel.py | 108 ++++++++++++++++++++++++++++++++++ tools/test_android_kernel.py | 46 +++++++++++++++ 12 files changed, 328 insertions(+), 128 deletions(-) create mode 100644 tools/android-kernel.py create mode 100644 tools/test_android_kernel.py diff --git a/.github/workflows/runtime.yml b/.github/workflows/runtime.yml index 898a3cb8..c39c4578 100644 --- a/.github/workflows/runtime.yml +++ b/.github/workflows/runtime.yml @@ -19,14 +19,26 @@ jobs: - name: Compile with the shared runtime working-directory: workspace/OctoSense run: | - # AppCard (octos) is opt-in: the default and mobile-apps builds must - # not link it, and the opt-in must still build. + # The octos kernel is a shell service (feature `octos-core`): the + # default and mobile-apps builds link it, on desktop and Android. + cargo check --locked cargo check --locked --features mobile-apps + for features in "" "--features mobile-apps"; do + for target in "" "--target aarch64-linux-android"; do + cargo tree --locked $features $target -i octosense-octos-core --depth 0 >/dev/null || { echo "::error::octosense-octos-core is missing ($features $target)"; exit 1; } + done + done + # AppCard's UI (the octosense-appcard module) stays opt-in: the + # default and mobile-apps builds must not link it, and the opt-in + # must still build. for target in "" "--target aarch64-linux-android"; do - out=$(cargo tree --locked --features mobile-apps $target -i octos-core 2>&1 || true) - grep -q "did not match any packages" <<<"$out" || { echo "::error::octos-core is linked without app-appcard ($target)"; echo "$out"; exit 1; } + out=$(cargo tree --locked --features mobile-apps $target -i octosense-appcard 2>&1 || true) + grep -q "did not match any packages" <<<"$out" || { echo "::error::octosense-appcard is linked without app-appcard ($target)"; echo "$out"; exit 1; } done cargo check --locked --workspace --features mobile-apps,app-appcard + - name: Android kernel bundling tool + working-directory: workspace/OctoSense + run: python -m unittest tools/test_android_kernel.py - name: Reject divergent runtime sources working-directory: workspace/OctoSense run: | diff --git a/Cargo.lock b/Cargo.lock index 30cba497..27928f0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6722,6 +6722,7 @@ dependencies = [ "octoscript-node", "octoscript-ui-l0", "octosense-llm-config", + "octosense-octos-core", "reqwest 0.12.28", "robius-open", "serde", @@ -6759,14 +6760,13 @@ dependencies = [ "chrono", "futures-util", "log", - "octos-cli", "octos-core", + "octosense-octos-core", "reqwest 0.12.28", "serde", "serde_json", "tokio", "tokio-tungstenite 0.24.0", - "tracing-subscriber", "url", "uuid", ] @@ -7167,6 +7167,7 @@ dependencies = [ "octosense-llm-config", "octosense-llm-service", "octosense-mail-service", + "octosense-octos-core", "octosense-reference", "rinx", ] @@ -7258,6 +7259,7 @@ dependencies = [ "image", "octosense-appstore", "octosense-llm-config", + "octosense-octos-core", "rqrr", "serde_json", "ureq 2.12.1", @@ -7282,6 +7284,18 @@ dependencies = [ "webpki-roots 1.0.9", ] +[[package]] +name = "octosense-octos-core" +version = "0.1.0" +dependencies = [ + "log", + "octos-cli", + "octosense-llm-config", + "serde_json", + "tokio", + "tracing-subscriber", +] + [[package]] name = "octosense-reference" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 4a06e61c..6f44a054 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,11 +74,16 @@ octosense-app-hub-app = { git = "https://github.com/OctoSense-org/OctoSense-App- # OctoSense-System-Apps checkout beside this repository # (native-apps.lock.json, tools/setup-native.py). octosense-mail-service = { path = "../OctoSense-System-Apps/apps/mail/host-service", optional = true } -# The `llm` host service the AI providers system app calls: the assistant's -# LLM providers, their keys (macOS keychain) and the phone QR, on host-owned -# sheets. The config crate names the AppCard kernel's octos home it writes. +# The `llm` host service the AI providers system app calls: the octos +# kernel's LLM providers, their keys (macOS keychain) and the phone QR, on +# host-owned sheets. The config crate names the kernel's octos home it writes. octosense-llm-service = { path = "../OctoSense-System-Apps/apps/ai-providers/host-service", optional = true } octosense-llm-config = { path = "../OctoSense-System-Apps/apps/ai-providers/config", optional = true } +# The octos agent kernel as a shell service (OctoSense-System-Apps +# crates/octos-core): one kernel per process, started when a consumer (AppCard, +# Rinx) connects, configured by the AI providers app, restarted when the +# providers change. Feature `octos-core`, on by default. +octosense-octos-core = { path = "../OctoSense-System-Apps/crates/octos-core", optional = true } # Rinx (the Matrix client with Moments and articles) as an in-process module. # Built without its standalone window entry point. rinx = { git = "https://github.com/upstreamlabs/Rinx", rev = "5ad2c1739e5b4776588518ab76440a5367c9968e", default-features = false, features = ["octosense-module"], optional = true } @@ -91,17 +96,26 @@ makepad-aichat = { git = "https://github.com/OctoSense-org/makepad.git", rev = " # available modules into native mobile builds without requiring CLI flags. [target.'cfg(any(target_os = "android", target_os = "ios"))'.dependencies] octosense-reference = { path = "apps/reference" } +# The octos kernel service: Android runs the APK's bundled liboctos.so +# (MAKEPAD_ANDROID_EXTRA_LIBS, tools/android-kernel.py); iOS has none. +octosense-octos-core = { path = "../OctoSense-System-Apps/crates/octos-core" } makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "cd812acde910e9c4523f83509356450eb8edf908", default-features = false } [features] # App Hub is part of the standard launcher: the store, the Card runner and -# the system apps (News, Photos, Maps, Camera, Mail, AI providers) it runs. -default = ["app-hub"] +# the system apps (News, Photos, Maps, Camera, Mail, AI providers) it runs; +# the octos kernel is a shell service in every standard build. +default = ["app-hub", "octos-core"] # Also usable on desktop for testing the same embedded apps with --module. # Photos is the system app here; `app-photos` links Makepad's native Photos # module instead, for comparison (a linked module of the same id wins). # AppCard is not among them: it ships only with `--features app-appcard`. -mobile-apps = ["app-reference", "app-sheets", "app-hub"] +mobile-apps = ["app-reference", "app-sheets", "app-hub", "octos-core"] +# The octos kernel as a shell service (octosense-octos-core): configured at +# startup, its profile written and the kernel restarted by the `llm` service, +# AppCard and other consumers connect to it. Opt out with +# `--no-default-features` plus the features you still want; Android and iOS +# builds always link it. # App Hub and the host services its system apps call. app-hub = ["dep:octosense-app-hub-app", "dep:octosense-mail-service", "dep:octosense-llm-service", "dep:octosense-llm-config"] app-reference = ["dep:octosense-reference"] @@ -109,10 +123,12 @@ app-reference = ["dep:octosense-reference"] app-sheets = ["dep:makepad-sheets"] # Photos (the picture wall) linked in as a module, the same way. app-photos = ["dep:makepad-photos"] -# The AppCard module (the assistant in a tile, with the octos kernel) linked -# in. Opt-in only, on desktop and native mobile alike: AppCard is not shipped -# by default for now (it interfered with the other apps). -app-appcard = ["dep:octosense-appcard"] +octos-core = ["dep:octosense-octos-core", "octosense-llm-service?/octos-core"] +# The AppCard module (the assistant in a tile) linked in; it connects to the +# shell's octos kernel, so it implies `octos-core`. Opt-in only, on desktop +# and native mobile alike: AppCard is not shipped by default for now (it +# interfered with the other apps). +app-appcard = ["dep:octosense-appcard", "octos-core"] # The aichat module linked in (the pane seats it in-process where there are # no processes, or when `aichat` is switched to module hosting). app-aichat = ["dep:makepad-aichat"] diff --git a/README.md b/README.md index 07a74040..c6863e01 100644 --- a/README.md +++ b/README.md @@ -11,12 +11,12 @@ OctoSense-Desktop is the desktop shell of [OctoSense](https://github.com/OctoSen | Repository | Role for this repo | | --- | --- | | [OctoSense-ROM](https://github.com/OctoSense-org/OctoSense-ROM) | The phone shell (`home/`). Same app model, same runtime, same system apps. | -| [OctoSense-System-Apps](https://github.com/OctoSense-org/OctoSense-System-Apps) | News, Photos, Maps, Camera, Mail and AI providers bundles, the Mail and `llm` host services and the AppCard assistant (`octos-app`). Pinned sibling checkout. | +| [OctoSense-System-Apps](https://github.com/OctoSense-org/OctoSense-System-Apps) | News, Photos, Maps, Camera, Mail and AI providers bundles, the Mail and `llm` host services, the octos kernel service (`crates/octos-core`) and the AppCard assistant (`octos-app`, opt-in, not shipped by default). Pinned sibling checkout. | | [OctoSense-App-Hub](https://github.com/OctoSense-org/OctoSense-App-Hub) | The signed catalog, the store and the Card runner. Linked as the Git crate `octosense-app-hub-app`. | | [OctoScript-App-Design-Flow](https://github.com/OctoSense-org/OctoScript-App-Design-Flow) | Where apps are designed, built and published to the App Hub. | | [OctoScript-Makepad](https://github.com/OctoSense-org/OctoScript-Makepad) | The runtime release that pins Makepad and OctoScript. Pinned sibling checkout. | | [makepad (OctoSense fork)](https://github.com/OctoSense-org/makepad) | The framework. Pinned sibling checkout. | -| [octos](https://github.com/octos-org/octos) | The agent kernel behind the AppCard assistant. Git dependency, one revision (`18fcd3f1`), linked only with `app-appcard`. | +| [octos](https://github.com/octos-org/octos) | The agent kernel, a shell service (`octos-core`, on by default): AI providers configures it, AppCard and other consumers connect to it. One revision (`6ad76e5c`, the one System-Apps pins), in `Cargo.lock`; the kernel itself is a separate binary (desktop: `OCTOS_APP_CORE_BIN`; Android: bundled `liboctos.so`). | ## Repository layout @@ -109,10 +109,11 @@ A relocatable `.app`, installers and a Linux session compositor are not provided | `app-reference` | off | Links Reference as a module. | | `app-sheets` | off | Links Makepad's Sheets as a module. | | `app-photos` | off | Links Makepad's native Photos module; it replaces the Photos system app of the same id (for comparison). | -| `app-appcard` | off | Links the AppCard assistant module (`apps/appcard`) and octos. Opt-in on every target, phones included; not shipped for now. | +| `app-appcard` | off | Links the AppCard assistant module (`apps/appcard`); implies `octos-core`. Opt-in on every target, phones included; not shipped for now. | +| `octos-core` | on | The octos kernel service (`octosense-octos-core`, from `../OctoSense-System-Apps/crates/octos-core`): the one kernel AppCard and other consumers share, configured by AI providers. Always on for Android and iOS. Leave it out with `--no-default-features --features app-hub` (and whatever else you want). | | `app-aichat` | off | Links Makepad's AI chat as a module, without its model engine. | | `app-rinx` | off | Links [Rinx](https://github.com/upstreamlabs/Rinx), the Matrix client, as a module. | -| `mobile-apps` | off | `app-reference` + `app-sheets` + `app-hub`: the set phone builds link, for testing on desktop. Not AppCard. | +| `mobile-apps` | off | `app-reference` + `app-sheets` + `app-hub` + `octos-core`: the set phone builds link, for testing on desktop. Not AppCard. | A linked module opens with `--module ` (or a `: Module` line in `wm/apps.splash` under the state directory): @@ -139,7 +140,7 @@ App Hub's modules are the exception: they have no process form and always open i | `MAKEPAD_APP_CONFIG='{"mail_demo":true}'` | Serve Mail's demo mailbox (see [Demos](#demos)). | | `OCTOSENSE_MAIL_VAULT=file` | Keep Mail passwords in a 0600 file instead of the macOS keychain. | | `OCTOSENSE_LLM_VAULT=file` | Keep AI providers' keys in the owner-only octos profile instead of the macOS keychain. | -| `OCTOS_APP_CORE_BIN`, `OCTOS_APP_CORE_DIR` | Point the AppCard assistant at a local octos kernel. | +| `OCTOS_APP_CORE_BIN`, `OCTOS_APP_CORE_DIR` | The octos kernel binary the shell's kernel service runs (none: no kernel on this desktop) and its core dir (default `~/octos-home/.octos`; the AI providers profile is `/profiles/_main.json`). | | `MAKEPAD_REMOTE`, `MAKEPAD_HIDE_WINDOWS` | Remote-control bridge; hidden windows (see [Demos](#demos)). | ## The app model @@ -169,7 +170,9 @@ Mail is the worked example (`octosense-mail-service`, from `../OctoSense-System- - The service tests the account, stores the password in the platform secret store (macOS keychain), and grants the account only to the app that added it. - Mail state lives under the host's own directory, outside every app's jail. -AI providers (`os.ai-providers`) edits the LLM providers the AppCard assistant runs on through the `llm` service (`octosense-llm-service`, from `../OctoSense-System-Apps/apps/ai-providers/host-service`). Keys are typed only on host sheets and go to the macOS keychain entry octos reads; the providers are written to the AppCard kernel's octos profile (`OCTOS_APP_CORE_DIR`, else `~/octos-home/.octos`). A phone's provider QR is imported from a picture of it: **Choose image** opens the open panel, or drop a screenshot on the import sheet. **Start → Settings → AI providers** opens it. With `--features app-appcard`, a change restarts a running AppCard so its kernel reads the new providers. +AI providers (`os.ai-providers`) edits the octos kernel's LLM providers through the `llm` service (`octosense-llm-service`, from `../OctoSense-System-Apps/apps/ai-providers/host-service`). Keys are typed only on host sheets and go to the macOS keychain entry octos reads; the providers are written to the kernel's profile under the shell's octos core dir (`/profiles/_main.json`; core dir `OCTOS_APP_CORE_DIR`, else `~/octos-home/.octos`). A phone's provider QR is imported from a picture of it: **Choose image** opens the open panel, or drop a screenshot on the import sheet. **Start → Settings → AI providers** opens it. After a change the service restarts the kernel if one runs; its consumers (AppCard) reconnect to the new one. + +**The octos kernel** is a shell service, not part of any app: `octosense-octos-core` (feature `octos-core`, default). The shell configures it at startup (`apps::configure_octos_kernel`); nothing runs until a consumer connects, then one kernel per process (` serve --stdio --data-dir ` on a desktop, the APK's `liboctos.so` on Android; none on iOS or on a desktop without `OCTOS_APP_CORE_BIN`). AppCard's agent connects to it; Rinx's host can take its own connection. It stops when the last consumer leaves and when the shell exits. New app features that need a password, PIN or token belong in a host service and a host sheet, never in the app's own UI. @@ -236,7 +239,7 @@ python3 scripts/upstream.py catalog --apply # rewrite config/apps.json and apps ### The AppCard assistant -AppCard is **not shipped for now**: it interfered with the other apps, so no build links it unless asked. Default, `mobile-apps`, Android and iOS builds leave it and octos out, and it has no tile, group or launcher entry. `--features app-appcard` brings it back on any target (for a phone, pass the feature to `cargo makepad`); it needs the `../OctoSense-System-Apps` sibling that `tools/setup-native.py` prepares and pulls octos at the revision below. +AppCard is **not shipped for now**: it interfered with the other apps, so no build links it unless asked. Default, `mobile-apps`, Android and iOS builds leave its UI out (the octos kernel service stays), and it has no tile, group or launcher entry. `--features app-appcard` brings it back on any target (for a phone, pass the feature to `cargo makepad`); it needs the `../OctoSense-System-Apps` sibling that `tools/setup-native.py` prepares and pulls octos at the revision below. `apps/appcard` (feature `app-appcard`, opt-in) hosts the whole AppCard assistant in one tile: `octos-app` from `../OctoSense-System-Apps/apps/appcard/app/app`, built without its `standalone` feature. Routing, cards, sessions, the composer and the kernel agent run inside the tile's isolate; `ask` is the module's AI-bus tool. @@ -244,7 +247,7 @@ AppCard is **not shipped for now**: it interfered with the other apps, so no bui cargo run --release --features app-appcard -- --module appcard ``` -On desktop, `OCTOS_APP_CORE_BIN` and `OCTOS_APP_CORE_DIR` point it at a local octos kernel; without one it shows its login / WebSocket screen. Every octos crate comes from octos-org/octos at the one revision `octos-app` pins. +It starts no kernel of its own: it connects to the shell's. On desktop that needs `OCTOS_APP_CORE_BIN` (and optionally `OCTOS_APP_CORE_DIR`); without a kernel it shows its login / WebSocket screen. Every octos crate comes from octos-org/octos at the one revision `octos-app` and `crates/octos-core` pin (`6ad76e5c`). ## Demos @@ -301,7 +304,7 @@ With the Makepad Android toolchain installed and a device on ADB: cargo makepad android run -p octosense --release ``` -Phone builds always link Reference and Sheets, and App Hub with the system apps through the default feature; AppCard only with `--features app-appcard`. The launcher label is **OctoSense**, application id `dev.makepad.octosense`. With `app-appcard`, AppCard's Java features (GPS, notifications, share, intents) and the bundled `liboctos.so` kernel need the fork's buildtool and `MAKEPAD_ANDROID_EXTRA_LIBS`; see [docs/android-appcard-build.md](docs/android-appcard-build.md). The dedicated phone shell is OctoSense-ROM's `home/`. +Phone builds always link Reference and Sheets and the octos kernel service, and App Hub with the system apps through the default feature; AppCard only with `--features app-appcard`. The launcher label is **OctoSense**, application id `dev.makepad.octosense`. The APK must bundle the kernel as `liboctos.so`: `python3 tools/android-kernel.py --sdk -- cargo makepad android run -p octosense --release` cross-builds `octos` at the revision `Cargo.lock` pins (in `target/octos-kernel/`) and runs the packager with `MAKEPAD_ANDROID_EXTRA_LIBS=liboctos.so=` (`--kernel ` for a prebuilt one). Without it the phone runs no kernel; the AI providers are still saved. AppCard's Java features (GPS, notifications, share, intents) need the fork's buildtool; see [docs/android-appcard-build.md](docs/android-appcard-build.md). The dedicated phone shell is OctoSense-ROM's `home/`. ## Desktop styles and settings @@ -328,7 +331,9 @@ After any change: `python3 tools/setup-native.py --update`, `cargo update` as ne cargo test --locked --workspace cargo test --locked --workspace --features mobile-apps cargo test --locked -p octosense --features mobile-apps,app-appcard appcard -cargo tree --locked --features mobile-apps -i octos-core # must not match: no octos without app-appcard +cargo tree --locked --features mobile-apps -i octosense-octos-core # the octos kernel service is linked +cargo tree --locked --features mobile-apps -i octosense-appcard # must not match: no AppCard UI without app-appcard +python3 -m unittest tools/test_android_kernel.py python3 -m unittest discover -s scripts -p 'test_*.py' python3 scripts/upstream.py catalog python3 tools/setup-native.py --check --cargo-manifest Cargo.toml @@ -346,7 +351,7 @@ Results for each change are recorded in [docs/validation.md](docs/validation.md) ## CI -`.github/workflows/runtime.yml` runs on every push and pull request, on macOS 14: `setup-native.py`, `cargo check --locked --features mobile-apps`, a `cargo tree` check that octos is absent from the `mobile-apps` graph (host and Android), `cargo check --locked --workspace --features mobile-apps,app-appcard`, and `setup-native.py --check --cargo-manifest Cargo.toml`. It does **not** run `cargo test`, the Python tests or the smoke tests; run those locally before opening a PR. +`.github/workflows/runtime.yml` runs on every push and pull request, on macOS 14: `setup-native.py`, `cargo check --locked` (default and `--features mobile-apps`), `cargo tree` checks that the octos kernel service is in the default and `mobile-apps` graphs and the AppCard UI (`octosense-appcard`) is not (host and Android), `cargo check --locked --workspace --features mobile-apps,app-appcard`, the `tools/android-kernel.py` tests, and `setup-native.py --check --cargo-manifest Cargo.toml`. It does **not** run `cargo test`, the other Python tests or the smoke tests; run those locally before opening a PR. ## Known gaps diff --git a/README.zh-CN.md b/README.zh-CN.md index df259d6d..17508c9a 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -11,12 +11,12 @@ OctoSense-Desktop 是 [OctoSense](https://github.com/OctoSense-org)(运行在 | 仓库 | 与本仓库的关系 | | --- | --- | | [OctoSense-ROM](https://github.com/OctoSense-org/OctoSense-ROM/blob/main/README.zh-CN.md) | 手机 Shell(`home/`)。相同的应用模型、相同的运行时、相同的系统应用。 | -| [OctoSense-System-Apps](https://github.com/OctoSense-org/OctoSense-System-Apps) | 新闻、相册、地图、相机、邮件、AI 提供商的应用包,邮件与 `llm` 宿主服务,以及 AppCard 助手(`octos-app`)。以固定版本的同级目录检出。 | +| [OctoSense-System-Apps](https://github.com/OctoSense-org/OctoSense-System-Apps) | 新闻、相册、地图、相机、邮件、AI 提供商的应用包,邮件与 `llm` 宿主服务,octos 内核服务(`crates/octos-core`),以及 AppCard 助手(`octos-app`,需显式启用,默认不发布)。以固定版本的同级目录检出。 | | [OctoSense-App-Hub](https://github.com/OctoSense-org/OctoSense-App-Hub) | 签名目录、商店和 Card 运行器。以 Git crate `octosense-app-hub-app` 链接。 | | [OctoScript-App-Design-Flow](https://github.com/OctoSense-org/OctoScript-App-Design-Flow) | 设计、构建应用并发布到 App Hub 的地方。 | | [OctoScript-Makepad](https://github.com/OctoSense-org/OctoScript-Makepad) | 固定 Makepad 与 OctoScript 版本的运行时发行版。以固定版本的同级目录检出。 | | [makepad(OctoSense 分支)](https://github.com/OctoSense-org/makepad) | 框架。以固定版本的同级目录检出。 | -| [octos](https://github.com/octos-org/octos) | AppCard 助手背后的 Agent 内核。Git 依赖,只有一个版本(`18fcd3f1`),仅在启用 `app-appcard` 时链接。 | +| [octos](https://github.com/octos-org/octos) | Agent 内核,一项 Shell 服务(`octos-core`,默认开启):由 AI 提供商配置,AppCard 等使用方连接它。只有一个版本(`6ad76e5c`,即 System-Apps 固定的版本),记录在 `Cargo.lock` 中;内核本身是单独的二进制(桌面:`OCTOS_APP_CORE_BIN`;Android:打包的 `liboctos.so`)。 | ## 仓库结构 @@ -109,10 +109,11 @@ cargo build --release --workspace | `app-reference` | 关 | 把 Reference 作为模块链接。 | | `app-sheets` | 关 | 把 Makepad 的 Sheets 作为模块链接。 | | `app-photos` | 关 | 链接 Makepad 的原生 Photos 模块;它会替换同 id 的相册系统应用(用于对比)。 | -| `app-appcard` | 关 | 链接 AppCard 助手模块(`apps/appcard`)及 octos。在所有目标平台(包括手机)上都需显式启用;目前不随产品发布。 | +| `app-appcard` | 关 | 链接 AppCard 助手模块(`apps/appcard`);隐含 `octos-core`。在所有目标平台(包括手机)上都需显式启用;目前不随产品发布。 | +| `octos-core` | 开 | octos 内核服务(`octosense-octos-core`,来自 `../OctoSense-System-Apps/crates/octos-core`):AppCard 等使用方共享的唯一内核,由 AI 提供商配置。Android 和 iOS 上总是开启。用 `--no-default-features --features app-hub`(加上其他需要的 feature)去掉它。 | | `app-aichat` | 关 | 把 Makepad 的 AI chat 作为模块链接,不含模型引擎。 | | `app-rinx` | 关 | 把 Matrix 客户端 [Rinx](https://github.com/upstreamlabs/Rinx) 作为模块链接。 | -| `mobile-apps` | 关 | `app-reference` + `app-sheets` + `app-hub`:手机构建链接的同一组模块,用于在桌面上测试。不含 AppCard。 | +| `mobile-apps` | 关 | `app-reference` + `app-sheets` + `app-hub` + `octos-core`:手机构建链接的同一组模块,用于在桌面上测试。不含 AppCard。 | 已链接的模块用 `--module ` 打开(或在状态目录下的 `wm/apps.splash` 中写一行 `: Module`): @@ -139,7 +140,7 @@ App Hub 的模块是例外:它们没有进程形态,总是在进程内打开 | `MAKEPAD_APP_CONFIG='{"mail_demo":true}'` | 提供邮件的演示邮箱(见[演示](#演示))。 | | `OCTOSENSE_MAIL_VAULT=file` | 把邮件密码保存在权限为 0600 的文件中,而不是 macOS 钥匙串。 | | `OCTOSENSE_LLM_VAULT=file` | 把 AI 提供商的密钥保存在仅所有者可读的 octos 配置文件中,而不是 macOS 钥匙串。 | -| `OCTOS_APP_CORE_BIN`、`OCTOS_APP_CORE_DIR` | 让 AppCard 助手使用本地的 octos 内核。 | +| `OCTOS_APP_CORE_BIN`、`OCTOS_APP_CORE_DIR` | Shell 的内核服务运行的 octos 内核二进制(未设置则本机没有内核)及其 core 目录(默认 `~/octos-home/.octos`;AI 提供商的 profile 是 `<目录>/profiles/_main.json`)。 | | `MAKEPAD_REMOTE`、`MAKEPAD_HIDE_WINDOWS` | 远程控制桥;隐藏窗口(见[演示](#演示))。 | ## 应用模型 @@ -169,7 +170,9 @@ launcher 把四类应用列在一起: - 服务先测试账户,再把密码存入平台的密钥存储(macOS 钥匙串),并且只把账户授予添加它的应用。 - 邮件状态保存在宿主自己的目录中,位于所有应用的沙箱之外。 -AI 提供商(`os.ai-providers`)通过 `llm` 服务(`octosense-llm-service`,来自 `../OctoSense-System-Apps/apps/ai-providers/host-service`)编辑 AppCard 助手使用的 LLM 提供商。密钥只在宿主面板上输入,保存到 octos 读取的 macOS 钥匙串条目;提供商写入 AppCard 内核的 octos 配置(`OCTOS_APP_CORE_DIR`,否则为 `~/octos-home/.octos`)。手机上的提供商二维码可从图片导入:**Choose image** 打开文件面板,或把截图拖到导入面板上。**开始 → 设置 → AI providers** 可打开它。使用 `--features app-appcard` 时,更改会重启正在运行的 AppCard,使其内核读取新的提供商。 +AI 提供商(`os.ai-providers`)通过 `llm` 服务(`octosense-llm-service`,来自 `../OctoSense-System-Apps/apps/ai-providers/host-service`)编辑 octos 内核的 LLM 提供商。密钥只在宿主面板上输入,保存到 octos 读取的 macOS 钥匙串条目;提供商写入 Shell 的 octos core 目录下内核的 profile(`/profiles/_main.json`;core 目录为 `OCTOS_APP_CORE_DIR`,否则为 `~/octos-home/.octos`)。手机上的提供商二维码可从图片导入:**Choose image** 打开文件面板,或把截图拖到导入面板上。**开始 → 设置 → AI providers** 可打开它。更改后服务会重启正在运行的内核,使用方(AppCard)会重新连接到新内核。 + +**octos 内核**是一项 Shell 服务,不属于任何应用:`octosense-octos-core`(feature `octos-core`,默认开启)。Shell 在启动时配置它(`apps::configure_octos_kernel`);在有使用方连接之前不运行任何东西,之后每个进程只有一个内核(桌面上是 ` serve --stdio --data-dir `,Android 上是 APK 中的 `liboctos.so`;iOS 以及未设置 `OCTOS_APP_CORE_BIN` 的桌面上没有内核)。AppCard 的 Agent 连接它;Rinx 的宿主可以拿到自己的连接。最后一个使用方离开或 Shell 退出时内核停止。 需要密码、PIN 或令牌的新功能,应放在宿主服务和宿主自有面板中,绝不放在应用自己的界面里。 @@ -236,7 +239,7 @@ python3 scripts/upstream.py catalog --apply # rewrite config/apps.json and apps ### AppCard 助手 -AppCard **目前不随产品发布**:它会干扰其他应用,因此除非显式要求,任何构建都不链接它。默认构建、`mobile-apps`、Android 和 iOS 构建都不包含它和 octos,也没有它的磁贴、分组或启动器条目。使用 `--features app-appcard` 可在任意目标平台上恢复它(手机构建请把该 feature 传给 `cargo makepad`);它需要 `tools/setup-native.py` 准备的 `../OctoSense-System-Apps` 同级目录,并按下文所述版本拉取 octos。 +AppCard **目前不随产品发布**:它会干扰其他应用,因此除非显式要求,任何构建都不链接它。默认构建、`mobile-apps`、Android 和 iOS 构建都不包含它的界面(octos 内核服务仍然存在),也没有它的磁贴、分组或启动器条目。使用 `--features app-appcard` 可在任意目标平台上恢复它(手机构建请把该 feature 传给 `cargo makepad`);它需要 `tools/setup-native.py` 准备的 `../OctoSense-System-Apps` 同级目录,并按下文所述版本拉取 octos。 `apps/appcard`(feature `app-appcard`,需显式启用)在一个 tile 中托管完整的 AppCard 助手:来自 `../OctoSense-System-Apps/apps/appcard/app/app` 的 `octos-app`,构建时关闭其 `standalone` feature。路由、卡片、会话、输入框和内核 Agent 都在该 tile 的 isolate 中运行;`ask` 是该模块的 AI 总线工具。 @@ -244,7 +247,7 @@ AppCard **目前不随产品发布**:它会干扰其他应用,因此除非 cargo run --release --features app-appcard -- --module appcard ``` -在桌面上,`OCTOS_APP_CORE_BIN` 和 `OCTOS_APP_CORE_DIR` 让它使用本地 octos 内核;没有内核时显示登录 / WebSocket 界面。所有 octos crate 都来自 octos-org/octos,且只有 `octos-app` 固定的那一个版本。 +它不会自己启动内核,而是连接 Shell 的内核。在桌面上这需要 `OCTOS_APP_CORE_BIN`(`OCTOS_APP_CORE_DIR` 可选);没有内核时显示登录 / WebSocket 界面。所有 octos crate 都来自 octos-org/octos,且只有 `octos-app` 和 `crates/octos-core` 固定的那一个版本(`6ad76e5c`)。 ## 演示 @@ -301,7 +304,7 @@ Makepad 的 [`makepad_test`](https://github.com/OctoSense-org/makepad/tree/main/ cargo makepad android run -p octosense --release ``` -手机构建总是链接 Reference 和 Sheets,并通过默认 feature 链接 App Hub 及系统应用;AppCard 仅在使用 `--features app-appcard` 时链接。launcher 显示名为 **OctoSense**,应用 id 为 `dev.makepad.octosense`。启用 `app-appcard` 时,AppCard 的 Java 功能(GPS、通知、分享、intent)以及打包的 `liboctos.so` 内核需要使用分支的 buildtool 和 `MAKEPAD_ANDROID_EXTRA_LIBS`;见 [docs/android-appcard-build.md](docs/android-appcard-build.md)。专门的手机 Shell 是 OctoSense-ROM 的 `home/`。 +手机构建总是链接 Reference、Sheets 和 octos 内核服务,并通过默认 feature 链接 App Hub 及系统应用;AppCard 仅在使用 `--features app-appcard` 时链接。launcher 显示名为 **OctoSense**,应用 id 为 `dev.makepad.octosense`。APK 必须以 `liboctos.so` 形式打包内核:`python3 tools/android-kernel.py --sdk -- cargo makepad android run -p octosense --release` 会按 `Cargo.lock` 固定的版本交叉编译 `octos`(放在 `target/octos-kernel/`),并以 `MAKEPAD_ANDROID_EXTRA_LIBS=liboctos.so=` 运行打包工具(`--kernel ` 使用预先编译好的内核)。没有它时手机上不运行内核;AI 提供商的设置仍会保存。AppCard 的 Java 功能(GPS、通知、分享、intent)需要使用分支的 buildtool;见 [docs/android-appcard-build.md](docs/android-appcard-build.md)。专门的手机 Shell 是 OctoSense-ROM 的 `home/`。 ## 桌面样式与设置 @@ -328,7 +331,9 @@ cargo makepad android run -p octosense --release cargo test --locked --workspace cargo test --locked --workspace --features mobile-apps cargo test --locked -p octosense --features mobile-apps,app-appcard appcard -cargo tree --locked --features mobile-apps -i octos-core # 必须匹配不到:未启用 app-appcard 时没有 octos +cargo tree --locked --features mobile-apps -i octosense-octos-core # octos 内核服务已链接 +cargo tree --locked --features mobile-apps -i octosense-appcard # 必须匹配不到:未启用 app-appcard 时没有 AppCard 界面 +python3 -m unittest tools/test_android_kernel.py python3 -m unittest discover -s scripts -p 'test_*.py' python3 scripts/upstream.py catalog python3 tools/setup-native.py --check --cargo-manifest Cargo.toml @@ -346,7 +351,7 @@ python3 scripts/smoke.py --cargo-run --default-catalog ## CI -`.github/workflows/runtime.yml` 在每次 push 和 pull request 时于 macOS 14 上运行:`setup-native.py`、`cargo check --locked --features mobile-apps`、检查 `mobile-apps` 依赖图(主机和 Android)中没有 octos 的 `cargo tree`、`cargo check --locked --workspace --features mobile-apps,app-appcard`,以及 `setup-native.py --check --cargo-manifest Cargo.toml`。它**不**运行 `cargo test`、Python 测试或冒烟测试;提交 PR 前请在本地运行。 +`.github/workflows/runtime.yml` 在每次 push 和 pull request 时于 macOS 14 上运行:`setup-native.py`、`cargo check --locked`(默认以及 `--features mobile-apps`)、检查 octos 内核服务在默认和 `mobile-apps` 依赖图中且 AppCard 界面(`octosense-appcard`)不在其中的 `cargo tree`(主机和 Android)、`cargo check --locked --workspace --features mobile-apps,app-appcard`、`tools/android-kernel.py` 的测试,以及 `setup-native.py --check --cargo-manifest Cargo.toml`。它**不**运行 `cargo test`、其他 Python 测试或冒烟测试;提交 PR 前请在本地运行。 ## 已知不足 diff --git a/apps/appcard/src/lib.rs b/apps/appcard/src/lib.rs index bd6f2a69..3f2c4711 100644 --- a/apps/appcard/src/lib.rs +++ b/apps/appcard/src/lib.rs @@ -19,12 +19,14 @@ //! kernel transport come up, exactly as in the APK) and draws the app's root //! body without its `Window{}`. The host's tile is the window. //! -//! The kernel is the app's own affair: `octos_app`'s `stdio_spawn` finds -//! `liboctos.so` in this APK's nativeLibraryDir (bundled with -//! `MAKEPAD_ANDROID_EXTRA_LIBS`, see docs/android-appcard-build.md), gives it -//! `HOME=/octos-home` and the memory budget, and runs `serve --stdio` -//! as a `kill_on_drop` child of the agent's runtime — so this module spawns -//! no kernel of its own (the Phase-A probe would have been a second child). +//! The kernel is the shell's, not the app's: `octosense_octos_core` (feature +//! `octos-core`, which `app-appcard` implies) finds `liboctos.so` in this +//! APK's nativeLibraryDir (bundled by `tools/android-kernel.py`, see +//! docs/android-appcard-build.md), gives it `HOME=/octos-home` and the +//! memory budget, and runs `serve --stdio` once per process when the first +//! consumer connects. The app's agent is one connection to it; this module +//! spawns no kernel of its own. When the AI providers change, the `llm` +//! service restarts the kernel and the agent's transport reconnects. //! //! What stays host-owned: the OS window and keyboard insets, and the //! notifications / share / WebView overlay of the standalone APK's Java @@ -75,8 +77,9 @@ impl AppModule for AppCardModule { root: root.clone(), executor: Box::new(AppCardExecutor { root }), // The host runs this before it drops the root: stop the agent — - // its tokio runtime and the kernel child (kill_on_drop) go with - // it — so nothing outlives the isolate. + // its tokio runtime and its kernel connection go with it (the + // kernel stops when its last consumer leaves) — so nothing + // outlives the isolate. shutdown: Box::new(move |_vm| { if let Some(mut inner) = shell.borrow_mut::() { inner.shutdown(); diff --git a/docs/android-appcard-build.md b/docs/android-appcard-build.md index 2e7bb0f4..42824f91 100644 --- a/docs/android-appcard-build.md +++ b/docs/android-appcard-build.md @@ -1,8 +1,13 @@ # Android build with AppCard's framework, buildtool and bundled kernel -> AppCard is not shipped for now: Android and iOS builds link it only with -> `--features app-appcard`. This recipe applies to such opt-in builds; a -> default APK needs no octos kernel. +> The octos kernel is a shell service now (`octosense-octos-core`, feature +> `octos-core`, always on for Android), not AppCard's: **every** APK must +> bundle `liboctos.so`. `tools/android-kernel.py` does it (it cross-builds +> the kernel at the revision `Cargo.lock` pins, `--no-default-features +> --features api,git,ast`, and runs the packager with +> `MAKEPAD_ANDROID_EXTRA_LIBS`). AppCard itself is not shipped for now and +> links only with `--features app-appcard`. The steps below are the manual +> equivalent; their pins are older than the current ones. OctoSense on the phone now runs the AppCard assistant module (`apps/appcard`, which mounts `octos-app` from the pinned OctoSense-System-Apps checkout, @@ -27,19 +32,20 @@ give it: `dev.makepad.octosense`, not persistent). 3. **The octos kernel**, cross-built for `aarch64-linux-android` and bundled into the APK as `liboctos.so`. Android lets an app exec only from its - nativeLibraryDir, so the kernel must ship as a "library"; the hosted app - (`octos-app`'s `stdio_spawn`) finds it there and runs `octos serve --stdio` - with `HOME=/octos-home`. + nativeLibraryDir, so the kernel must ship as a "library"; the shell's + kernel service (`octosense-octos-core`) finds it there and runs `octos + serve --stdio` with `HOME=/octos-home` when the first consumer + (AppCard, Rinx) connects. ## Step by step ```sh # 0. Paths (adjust): a checkout of the fork, a checkout of octos-org/octos at -# the rev octos-app pins for every octos crate (18fcd3f1, see -# OctoSense-System-Apps apps/appcard/app/Cargo.toml), and the Android +# the rev Cargo.lock pins for octos-cli (6ad76e5c today; the same rev +# OctoSense-System-Apps pins), and the Android # toolchain dir that `cargo makepad android install-toolchain` produced. FORK=/path/to/makepad-fork # https://github.com/OctoSense-org/makepad.git -OCTOS=/path/to/octos # https://github.com/octos-org/octos.git at 18fcd3f16e527d2b601d7ae244f056fb711bb6b8 +OCTOS=/path/to/octos # https://github.com/octos-org/octos.git at 6ad76e5c1e659bdf10ec05ae869428b48edccf7f TOOLCHAIN=/path/to/android_33_macos_aarch64 # ndk/, platforms/, build-tools/, platform-tools/, openjdk/ # 1. The buildtool cargo-makepad. It looks for the toolchain relative to its @@ -90,25 +96,25 @@ unzip -l target/makepad-android-apk/octosense/apk/octosense.apk | grep liboctos "$ADB" logcat -d -s Makepad | grep -E 'stdio:|kernel' # the kernel's start ``` -When the AppCard tile starts (the module delivers `Event::Startup` to the -hosted app on first contact), the app's agent spawns the kernel over stdio -and logs one of - -- `stdio: octos=…/lib/arm64/liboctos.so HOME=…/files/octos-home` — the bundled - kernel was found and started; the agent's `client_hello` follows; -- `stdio: bundled octos not found under …; using WebSocket transport` — the - APK was built without `MAKEPAD_ANDROID_EXTRA_LIBS`; the app shows its - login screen. - -The kernel is a `kill_on_drop` child of the agent's runtime: the module's -`shutdown` stops the agent when the tile's instance is torn down, and the -child goes with it. The module spawns no kernel of its own. - -On a desktop the app spawns a local kernel only when BOTH `OCTOS_APP_CORE_BIN` -(the `octos` binary) and `OCTOS_APP_CORE_DIR` (its data dir; the app runs -`serve --stdio --data-dir --config /config.json` with -`OCTOS_HOME=`) are set; otherwise it uses the WebSocket transport / login -screen, so a developer's own `octos serve` is never touched. +At startup the shell logs `octos: kernel service ready (starts on first use), +core dir …` (or `octos: no octos kernel: …/liboctos.so is not bundled`). When +the AppCard tile starts (the module delivers `Event::Startup` to the hosted +app on first contact), the app's agent connects to the kernel service, which +starts the kernel and logs `octos-core: starting kernel 1: …/liboctos.so +serve --stdio`; without a kernel the agent logs `kernel: no octos kernel: …; +using the WebSocket transport` and the app shows its login screen. + +The kernel is the shell's, one per process: the module's `shutdown` drops the +agent's connection when the tile's instance is torn down, and the kernel stops +when its last consumer leaves. When the AI providers change, the `llm` service +restarts it and the agent reconnects. + +On a desktop the kernel service runs a local kernel only when +`OCTOS_APP_CORE_BIN` names the `octos` binary (data dir `OCTOS_APP_CORE_DIR`, +else `~/octos-home/.octos`; it runs `serve --stdio --data-dir `, plus +`--config /config.json` when that file exists, with `OCTOS_HOME=`); +otherwise AppCard uses the WebSocket transport / login screen, so a +developer's own `octos serve` is never touched. ## Provisioning the LLM key diff --git a/native-apps.lock.json b/native-apps.lock.json index 1a6a7145..3e95f0b2 100644 --- a/native-apps.lock.json +++ b/native-apps.lock.json @@ -3,7 +3,7 @@ "repositories": { "system-apps": { "url": "https://github.com/OctoSense-org/OctoSense-System-Apps.git", - "revision": "c0f2199c6d504ae65d8632747bb491bed820891b" + "revision": "47b96a0ae0b105844a71210f93980901132c3157" } } } diff --git a/src/apps.rs b/src/apps.rs index 30ac4244..48d05d5f 100644 --- a/src/apps.rs +++ b/src/apps.rs @@ -133,15 +133,15 @@ pub fn system_card_apps() -> Vec { /// - `mail` keeps accounts and passwords for the Mail app. `mail_demo` in /// MAKEPAD_APP_CONFIG serves a demo mailbox from a file vault instead (no /// keychain, no network): `MAKEPAD_APP_CONFIG='{"mail_demo":true}'`. -/// - `llm` keeps the assistant's LLM providers for AI providers -/// (`os.ai-providers`), written to the AppCard kernel's octos profile -/// (`OCTOS_APP_CORE_DIR`, else `~/octos-home/.octos`), keys in the keychain -/// entry octos reads (`OCTOSENSE_LLM_VAULT=file` keeps them in the -/// owner-only profile). No camera scanner: a phone's profile QR is imported -/// from a picture of it (the open panel, or an image dropped on the import -/// sheet: `llm_image`), or by pasting its text. With AppCard linked -/// (`app-appcard`), a change restarts its core so the kernel reads the new -/// providers; without it there is nothing to restart. +/// - `llm` keeps the octos kernel's LLM providers for AI providers +/// (`os.ai-providers`), written to the kernel's profile under the shell's +/// octos core dir (`octosense_octos_core::core_dir()`: `OCTOS_APP_CORE_DIR`, +/// else `~/octos-home/.octos`), keys in the keychain entry octos reads +/// (`OCTOSENSE_LLM_VAULT=file` keeps them in the owner-only profile). No +/// camera scanner: a phone's profile QR is imported from a picture of it +/// (the open panel, or an image dropped on the import sheet: `llm_image`), +/// or by pasting its text. With `octos-core` (default) the service restarts +/// the kernel after a change; its consumers (AppCard) reconnect. #[cfg(feature = "app-hub")] pub fn register_host_services() { static ONCE: std::sync::Once = std::sync::Once::new(); @@ -157,33 +157,37 @@ pub fn register_host_services() { octosense_mail_service::register() } let mut llm = octosense_llm_service::Options::default(); - if let Some(dir) = octosense_llm_config::profile::default_core_dir() { + #[cfg(any(feature = "octos-core", target_os = "android", target_os = "ios"))] + let core_dir = octosense_octos_core::core_dir(); + #[cfg(not(any(feature = "octos-core", target_os = "android", target_os = "ios")))] + let core_dir = octosense_llm_config::profile::default_core_dir(); + if let Some(dir) = core_dir { llm = llm.core_dir(dir); } #[cfg(not(any(target_os = "android", target_os = "ios")))] { llm = llm.image_picker(crate::llm_image::picker()).image_drops(true); } - #[cfg(feature = "app-appcard")] - { - llm = llm.on_changed(|| { - RESTART_APPCARD.store(true, std::sync::atomic::Ordering::Release); - makepad_widgets::makepad_platform::thread::SignalToUI::set_ui_signal(); - }); - } octosense_llm_service::register_with(llm); }); } -/// Set (any thread) when AI providers changed the saved provider set. -#[cfg(all(feature = "app-hub", feature = "app-appcard"))] -static RESTART_APPCARD: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false); - -/// UI thread: whether the AppCard core should restart to read a changed -/// provider set (once per change). -#[cfg(all(feature = "app-hub", feature = "app-appcard"))] -pub fn take_appcard_restart() -> bool { - RESTART_APPCARD.swap(false, std::sync::atomic::Ordering::AcqRel) +/// Configure the shell's octos kernel (`octosense_octos_core`) once, at +/// startup, before anything connects to it. Nothing runs until a consumer +/// (AppCard, Rinx) connects; then one kernel per process: on a desktop the +/// binary `OCTOS_APP_CORE_BIN` names (none otherwise), on Android the APK's +/// `liboctos.so`. Its diagnostics go to the shell's log. +#[cfg(any(feature = "octos-core", target_os = "android", target_os = "ios"))] +pub fn configure_octos_kernel(data_dir: Option) { + let mut options = octosense_octos_core::Options::default().log(|line| makepad_widgets::log!("{line}")); + if let Some(dir) = data_dir.filter(|d| !d.is_empty()) { + options = options.app_data_dir(dir); + } + octosense_octos_core::configure(options); + match octosense_octos_core::launch() { + Ok(_) => makepad_widgets::log!("octos: kernel service ready (starts on first use), core dir {:?}", octosense_octos_core::core_dir()), + Err(why) => makepad_widgets::log!("octos: {why}; the providers are still saved under {:?}", octosense_octos_core::core_dir()), + } } /// Apps App Hub installed: each is an app of its own in the launcher, hosted diff --git a/src/main.rs b/src/main.rs index 0c17f042..35dd0fe2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1398,28 +1398,6 @@ impl App { self.redraw_all(cx); } - /// The provider set changed (AI providers): end every AppCard module - /// instance, so its agent and its kernel child stop, and open AppCard - /// again when one was running: its new kernel reads the new profile. - #[cfg(all(feature = "app-hub", feature = "app-appcard"))] - fn restart_appcard_core(&mut self, cx: &mut Cx) { - let mut ended = 0; - while let Some(client) = self.module_host.client_of_module("appcard") { - log!("llm: providers changed: restarting the AppCard core (client {client})"); - self.request_close(cx, client); - ended += 1; - if ended >= 8 || self.module_host.client_of_module("appcard") == Some(client) { - break; - } - } - if ended == 0 { - log!("llm: providers changed; no AppCard core is running, the next one reads them"); - } else { - self.launch_app(cx, "appcard"); - } - self.redraw_all(cx); - } - fn request_close(&mut self, cx: &mut Cx, client: ClientId) { // A module instance has no process to ask politely and nothing to // reap later: it ends now, through the same removal as a death. @@ -3974,6 +3952,11 @@ fn scan_theme_color(source: &str, key: &str) -> Option { impl MatchEvent for App { fn handle_startup(&mut self, cx: &mut Cx) { + // The octos kernel is a shell service: configured here, started when + // a consumer (AppCard, Rinx) connects. AI providers' `llm` service + // writes its profile and restarts it after a change. + #[cfg(any(feature = "octos-core", target_os = "android", target_os = "ios"))] + apps::configure_octos_kernel(cx.get_data_dir()); // Where App Hub keeps what it installs: `$OCTOSENSE_APP_DATA`, else // `apps/` in the platform data directory or OctoSense's own state. #[cfg(feature = "app-hub")] @@ -4505,11 +4488,6 @@ impl AppMain for App { } } } - // AI providers changed the saved providers: restart the AppCard core. - #[cfg(all(feature = "app-hub", feature = "app-appcard"))] - if apps::take_appcard_restart() { - self.restart_appcard_core(cx); - } if self.phone_search_event(cx,event) {return;} if self.state.is_some() && self.phone_pointer(cx,event) {return;} if self.state.is_some() && self.snap_event(cx,event) {return;} @@ -4622,6 +4600,9 @@ impl AppMain for App { if let Some(state) = &mut self.state { clients::shutdown_clients(&mut state.clients); } + // Stop the octos kernel, if one runs, and let it release its data dir. + #[cfg(any(feature = "octos-core", target_os = "android", target_os = "ios"))] + octosense_octos_core::shutdown(); } if let Event::Timer(te) = event { self.fire_test_timers(cx, te); diff --git a/tools/android-kernel.py b/tools/android-kernel.py new file mode 100644 index 00000000..0a80baff --- /dev/null +++ b/tools/android-kernel.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 +"""Bundle OctoSense's octos kernel into an Android APK (Python 3.9+). + +The octos kernel is a shell service (feature `octos-core`, on in every +Android build): at run time it execs `liboctos.so serve --stdio` from the +APK's native lib dir, the only place an Android app may exec a binary from. +This tool cross-builds that binary from the one octos revision `Cargo.lock` +pins and runs the packager with it bundled: + + python3 tools/android-kernel.py --sdk \\ + -- cargo makepad android run -p octosense --release + +It checks octos out into `target/octos-kernel/src` (never a sibling you may +be working in), builds `octos` for aarch64-linux-android with the SDK's NDK +clang (API 33, `--no-default-features --features api,git,ast`) and runs the +command after `--` with `MAKEPAD_ANDROID_EXTRA_LIBS=liboctos.so=`. +`--kernel ` bundles a prebuilt aarch64-linux-android `octos` instead; +with no command it only builds and prints the kernel's path. +""" +import argparse +import glob +import os +from pathlib import Path +import re +import subprocess +import sys + +ROOT = Path(__file__).resolve().parents[1] +OCTOS_URL = "https://github.com/octos-org/octos.git" +TARGET = "aarch64-linux-android" +API = "33" +KERNEL_BUILD = ["-p", "octos-cli", "--bin", "octos", "--no-default-features", "--features", "api,git,ast"] + + +def octos_revision(lock=None): + """The one octos revision OctoSense links (Cargo.lock).""" + text = (lock or ROOT / "Cargo.lock").read_text() + match = re.search(r'name = "octos-cli"\nversion = "[^"]+"\nsource = "git\+https://github\.com/octos-org/octos\.git\?rev=([0-9a-f]{40})#', text) + if not match: + raise RuntimeError("Cargo.lock names no octos-cli from octos-org/octos: cannot tell which kernel to build") + return match[1] + + +def ndk_bin(sdk): + """The newest NDK's LLVM bin dir inside a cargo-makepad Android SDK dir.""" + found = sorted(glob.glob(str(sdk / "ndk/*/toolchains/llvm/prebuilt/*/bin")), + key=lambda p: [int(x) if x.isdigit() else x for x in re.split(r"[./]", p)]) + if not found: + raise RuntimeError(f"No NDK under {sdk}/ndk (cargo makepad android install-toolchain puts one there)") + return Path(found[-1]) + + +def build_env(bin_dir): + clang = bin_dir / f"{TARGET}{API}-clang" + return {"CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER": str(clang), + "CARGO_TARGET_AARCH64_LINUX_ANDROID_AR": str(bin_dir / "llvm-ar"), + "CC_aarch64_linux_android": str(clang), + "CXX_aarch64_linux_android": str(bin_dir / f"{TARGET}{API}-clang++"), + "AR_aarch64_linux_android": str(bin_dir / "llvm-ar"), + "RANLIB_aarch64_linux_android": str(bin_dir / "llvm-ranlib")} + + +def plan(revision, work, offline=False): + """(cwd, argv) steps that check out and build the kernel.""" + src = work / "src" + steps = [(ROOT, ["git", "init", "--quiet", str(src)])] + if not offline: + steps.append((src, ["git", "fetch", "--quiet", "--no-tags", "--depth=1", OCTOS_URL, revision])) + steps.append((src, ["git", "checkout", "--quiet", "--detach", revision])) + build = ["cargo", "build", "--locked", "--release", "--target", TARGET, *KERNEL_BUILD] + if offline: + build.append("--offline") + steps.append((src, build)) + return steps, work / "target" / TARGET / "release/octos" + + +def main(argv=None): + argv = sys.argv[1:] if argv is None else argv + command = [] + if "--" in argv: + i = argv.index("--") + argv, command = argv[:i], argv[i + 1:] + p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + p.add_argument("--sdk", type=Path, help="cargo-makepad's Android SDK dir (holds ndk/)") + p.add_argument("--kernel", type=Path, help="A prebuilt aarch64-linux-android octos to bundle") + p.add_argument("--offline", action="store_true") + args = p.parse_args(argv) + if args.kernel: + kernel = args.kernel.resolve() + if not kernel.is_file(): + p.error(f"no such kernel: {kernel}") + else: + if not args.sdk: + p.error("--sdk is required to build the kernel (or pass --kernel)") + work = ROOT / "target/octos-kernel" + steps, kernel = plan(octos_revision(), work, args.offline) + env = dict(os.environ, **build_env(ndk_bin(args.sdk.resolve()))) + env["CARGO_TARGET_DIR"] = str(work / "target") + for cwd, step in steps: + subprocess.run(step, cwd=cwd, env=env, check=True) + print(f"octos kernel: {kernel}", flush=True) + if command: + env = dict(os.environ, MAKEPAD_ANDROID_EXTRA_LIBS=f"liboctos.so={kernel}") + sys.exit(subprocess.run(command, cwd=ROOT, env=env).returncode) + + +if __name__ == "__main__": + main() diff --git a/tools/test_android_kernel.py b/tools/test_android_kernel.py new file mode 100644 index 00000000..f588dd77 --- /dev/null +++ b/tools/test_android_kernel.py @@ -0,0 +1,46 @@ +"""Tests for tools/android-kernel.py (no network, no build).""" +import importlib.util +from pathlib import Path +import tempfile +import unittest + +ROOT = Path(__file__).resolve().parents[1] +spec = importlib.util.spec_from_file_location("android_kernel", ROOT / "tools/android-kernel.py") +kernel = importlib.util.module_from_spec(spec) +spec.loader.exec_module(kernel) + + +class AndroidKernelTests(unittest.TestCase): + def test_the_revision_is_the_one_cargo_lock_pins(self): + rev = kernel.octos_revision() + self.assertRegex(rev, r"^[0-9a-f]{40}$") + with tempfile.TemporaryDirectory() as temp: + lock = Path(temp) / "Cargo.lock" + lock.write_text('[[package]]\nname = "serde"\n') + with self.assertRaises(RuntimeError): + kernel.octos_revision(lock) + + def test_the_plan_checks_out_and_cross_builds_the_kernel(self): + work = Path("/w") + steps, binary = kernel.plan("a" * 40, work) + commands = [c for _, c in steps] + self.assertIn(["git", "fetch", "--quiet", "--no-tags", "--depth=1", kernel.OCTOS_URL, "a" * 40], commands) + self.assertEqual(commands[-1], ["cargo", "build", "--locked", "--release", "--target", "aarch64-linux-android", *kernel.KERNEL_BUILD]) + self.assertEqual(binary, work / "target/aarch64-linux-android/release/octos") + offline, _ = kernel.plan("a" * 40, work, offline=True) + self.assertFalse(any(c[:2] == ["git", "fetch"] for _, c in offline)) + + def test_the_ndk_clang_is_found_in_the_sdk(self): + with tempfile.TemporaryDirectory() as temp: + sdk = Path(temp) + with self.assertRaises(RuntimeError): + kernel.ndk_bin(sdk) + bin_dir = sdk / "ndk/28.2.1/toolchains/llvm/prebuilt/darwin-x86_64/bin" + bin_dir.mkdir(parents=True) + self.assertEqual(kernel.ndk_bin(sdk), bin_dir) + env = kernel.build_env(bin_dir) + self.assertEqual(env["CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER"], str(bin_dir / "aarch64-linux-android33-clang")) + + +if __name__ == "__main__": + unittest.main() From 54aaff07ff1800abf24bc37ab40d607e70a0afa5 Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Sat, 26 Sep 2026 19:38:25 -0700 Subject: [PATCH 35/42] Runtime: OctoScript-Makepad 99c1e5ee (makepad d0a9def5), App Hub 46d67e51, System-Apps d30c67cb One makepad (d0a9def5, via OctoScript-Makepad 99c1e5ee), one App Hub (46d67e51, the rev System-Apps' Mail and llm host services name), System-Apps d30c67cb (its runtime repin, on top of the octos kernel service). Co-Authored-By: Claude Opus 5.5 (1M context) --- Cargo.lock | 8 ++++---- Cargo.toml | 26 +++++++++++++------------- apps/appcard/Cargo.toml | 4 ++-- apps/reference/Cargo.toml | 4 ++-- native-apps.lock.json | 2 +- native-runtime.lock.json | 2 +- 6 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 27928f0a..83aba8c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7175,7 +7175,7 @@ dependencies = [ [[package]] name = "octosense-app-hub" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/OctoSense-App-Hub?rev=3e993d4c596560c42f011b2fd00bd40dbcac7cc5#3e993d4c596560c42f011b2fd00bd40dbcac7cc5" +source = "git+https://github.com/OctoSense-org/OctoSense-App-Hub?rev=46d67e51b62827a1224b1aacddc2a7b9e69185fc#46d67e51b62827a1224b1aacddc2a7b9e69185fc" dependencies = [ "base64 0.22.1", "blake3", @@ -7191,7 +7191,7 @@ dependencies = [ [[package]] name = "octosense-app-hub-app" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/OctoSense-App-Hub?rev=3e993d4c596560c42f011b2fd00bd40dbcac7cc5#3e993d4c596560c42f011b2fd00bd40dbcac7cc5" +source = "git+https://github.com/OctoSense-org/OctoSense-App-Hub?rev=46d67e51b62827a1224b1aacddc2a7b9e69185fc#46d67e51b62827a1224b1aacddc2a7b9e69185fc" dependencies = [ "makepad-app-module", "makepad-widgets", @@ -7205,7 +7205,7 @@ dependencies = [ [[package]] name = "octosense-app-policy" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/OctoSense-App-Hub?rev=3e993d4c596560c42f011b2fd00bd40dbcac7cc5#3e993d4c596560c42f011b2fd00bd40dbcac7cc5" +source = "git+https://github.com/OctoSense-org/OctoSense-App-Hub?rev=46d67e51b62827a1224b1aacddc2a7b9e69185fc#46d67e51b62827a1224b1aacddc2a7b9e69185fc" dependencies = [ "blake3", "makepad-widgets", @@ -7226,7 +7226,7 @@ dependencies = [ [[package]] name = "octosense-appstore" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/OctoSense-App-Hub?rev=3e993d4c596560c42f011b2fd00bd40dbcac7cc5#3e993d4c596560c42f011b2fd00bd40dbcac7cc5" +source = "git+https://github.com/OctoSense-org/OctoSense-App-Hub?rev=46d67e51b62827a1224b1aacddc2a7b9e69185fc#46d67e51b62827a1224b1aacddc2a7b9e69185fc" dependencies = [ "makepad-app-module", "makepad-widgets", diff --git a/Cargo.toml b/Cargo.toml index 6f44a054..af6d1963 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,19 +40,19 @@ path = "src/main.rs" [dependencies] # `appcard,maps`: the Octoscript-AppCard widget set (kit, text roles, weather, # finance, matplot) and the map navigation layer the appcard module renders. -makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "cd812acde910e9c4523f83509356450eb8edf908", features = ["appcard", "maps"] } -makepad-studio-protocol = { git = "https://github.com/OctoSense-org/makepad.git", rev = "cd812acde910e9c4523f83509356450eb8edf908" } -makepad-network = { git = "https://github.com/OctoSense-org/makepad.git", rev = "cd812acde910e9c4523f83509356450eb8edf908" } -makepad-wm-api = { git = "https://github.com/OctoSense-org/makepad.git", rev = "cd812acde910e9c4523f83509356450eb8edf908" } +makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d", features = ["appcard", "maps"] } +makepad-studio-protocol = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d" } +makepad-network = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d" } +makepad-wm-api = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d" } # The AI services wire only: the WM relays frames between the aichat child and the apps. -makepad-ai-services = { git = "https://github.com/OctoSense-org/makepad.git", rev = "cd812acde910e9c4523f83509356450eb8edf908" } -makepad-strict-json = { git = "https://github.com/OctoSense-org/makepad.git", rev = "cd812acde910e9c4523f83509356450eb8edf908" } +makepad-ai-services = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d" } +makepad-strict-json = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d" } # In-process hosting (aicontrol §3): the module contract, the palette applied # into each instance's isolate, and the modules this build links. -makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "cd812acde910e9c4523f83509356450eb8edf908" } -makepad-wm-theme = { git = "https://github.com/OctoSense-org/makepad.git", rev = "cd812acde910e9c4523f83509356450eb8edf908" } -makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "cd812acde910e9c4523f83509356450eb8edf908", default-features = false, optional = true } -makepad-photos = { git = "https://github.com/OctoSense-org/makepad.git", rev = "cd812acde910e9c4523f83509356450eb8edf908", default-features = false, optional = true } +makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d" } +makepad-wm-theme = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d" } +makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d", default-features = false, optional = true } +makepad-photos = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d", default-features = false, optional = true } octosense-reference = { path = "apps/reference", optional = true } # The whole Octoscript-AppCard app hosted in a tile, in-process (Phase B of # the AppCard port): octos-app's AppShell — routing brain, cards, kernel agent. @@ -67,7 +67,7 @@ octosense-appcard = { path = "apps/appcard", optional = true } # (system-apps.json, .cargo/config.toml), installed apps and their icons. # Same rev as the Mail and llm host services' octosense-appstore # (OctoSense-System-Apps): one App Hub source, so one host-service registry. -octosense-app-hub-app = { git = "https://github.com/OctoSense-org/OctoSense-App-Hub", rev = "3e993d4c596560c42f011b2fd00bd40dbcac7cc5", optional = true } +octosense-app-hub-app = { git = "https://github.com/OctoSense-org/OctoSense-App-Hub", rev = "46d67e51b62827a1224b1aacddc2a7b9e69185fc", optional = true } # The `mail` host service the Mail system app calls through `host.request`: # accounts, message lists and send run here, with the passwords the host # holds; the app never gets a socket or a secret. From the pinned @@ -90,7 +90,7 @@ rinx = { git = "https://github.com/upstreamlabs/Rinx", rev = "5ad2c1739e5b477658 # The assistant as a module, seated in the pane in-process: the web build's # only chat, a desktop option. Without the engine feature: no model runtime, # NoModel and the tool console (the child process keeps the real models). -makepad-aichat = { git = "https://github.com/OctoSense-org/makepad.git", rev = "cd812acde910e9c4523f83509356450eb8edf908", default-features = false, optional = true } +makepad-aichat = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d", default-features = false, optional = true } # A phone cannot spawn the desktop catalog's Cargo binaries. Link the # available modules into native mobile builds without requiring CLI flags. @@ -99,7 +99,7 @@ octosense-reference = { path = "apps/reference" } # The octos kernel service: Android runs the APK's bundled liboctos.so # (MAKEPAD_ANDROID_EXTRA_LIBS, tools/android-kernel.py); iOS has none. octosense-octos-core = { path = "../OctoSense-System-Apps/crates/octos-core" } -makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "cd812acde910e9c4523f83509356450eb8edf908", default-features = false } +makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d", default-features = false } [features] # App Hub is part of the standard launcher: the store, the Card runner and diff --git a/apps/appcard/Cargo.toml b/apps/appcard/Cargo.toml index edf96231..d2774c6c 100644 --- a/apps/appcard/Cargo.toml +++ b/apps/appcard/Cargo.toml @@ -6,8 +6,8 @@ license = "Apache-2.0" publish = false [dependencies] -makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "cd812acde910e9c4523f83509356450eb8edf908", features = ["appcard", "maps"] } -makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "cd812acde910e9c4523f83509356450eb8edf908" } +makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d", features = ["appcard", "maps"] } +makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d" } # The whole AppCard assistant as a library, from the pinned # OctoSense-System-Apps checkout beside this repository (apps/appcard/app/app; # native-apps.lock.json, tools/setup-native.py): routing brain, card store and diff --git a/apps/reference/Cargo.toml b/apps/reference/Cargo.toml index 8814bb4a..c6252141 100644 --- a/apps/reference/Cargo.toml +++ b/apps/reference/Cargo.toml @@ -6,5 +6,5 @@ license = "Apache-2.0" publish = false [dependencies] -makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "cd812acde910e9c4523f83509356450eb8edf908" } -makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "cd812acde910e9c4523f83509356450eb8edf908" } +makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d" } +makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d" } diff --git a/native-apps.lock.json b/native-apps.lock.json index 3e95f0b2..e13c4602 100644 --- a/native-apps.lock.json +++ b/native-apps.lock.json @@ -3,7 +3,7 @@ "repositories": { "system-apps": { "url": "https://github.com/OctoSense-org/OctoSense-System-Apps.git", - "revision": "47b96a0ae0b105844a71210f93980901132c3157" + "revision": "d30c67cb8f470f4b0c9259c091a7efeae5098205" } } } diff --git a/native-runtime.lock.json b/native-runtime.lock.json index 9e20e807..993be108 100644 --- a/native-runtime.lock.json +++ b/native-runtime.lock.json @@ -1,5 +1,5 @@ { "schema_version": 1, "url": "https://github.com/OctoSense-org/Octoscript-Makepad.git", - "revision": "463e3da8ac170a987e34552a1f96551d2a2cc6ea" + "revision": "99c1e5ee7925060ced59c66d6f8763c22217e4ad" } From b4d5b1773fc0898cda685e6724113583a78a4c27 Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Sat, 26 Sep 2026 20:53:00 -0700 Subject: [PATCH 36/42] Hosted Rinx with the shell's assistant (Rinx ADR 0007) - Apps' assistant access: src/app_peers_host.rs gives each native module whose declared octos.* services host policy grants a scoped service from octosense-app-peers (System-Apps crates/app-peers), offered for the duration of its create and released at teardown. It is backed by ONE octos peer per app and account, owned by the system agent _main:api:octosense#system, on the shell's kernel. Modules without granted services get no peer. - Rinx (app-rinx, now in default) at hagency-org/Rinx with octosense-module only; app-rinx implies octos-core. CI checks that Rinx's standalone entry and local kernel stay out of the graph; System-Apps crates resolve to the sibling checkout (no URL aliases). - Runtime: OctoScript-Makepad c3d53ba8 (makepad db4691d0: Splash reapply_text, stateful mini-app inputs); System-Apps 4fa0f122 (app-peers). AppCard stays opt-in. Supersedes #41. Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/workflows/runtime.yml | 11 +++ Cargo.lock | 50 +++++++--- Cargo.toml | 54 +++++++---- apps/appcard/Cargo.toml | 4 +- apps/reference/Cargo.toml | 4 +- native-apps.lock.json | 2 +- native-runtime.lock.json | 2 +- src/app_peers_host.rs | 170 ++++++++++++++++++++++++++++++++++ src/apps.rs | 7 ++ src/main.rs | 1 + src/module_host.rs | 17 ++++ 11 files changed, 286 insertions(+), 36 deletions(-) create mode 100644 src/app_peers_host.rs diff --git a/.github/workflows/runtime.yml b/.github/workflows/runtime.yml index c39c4578..684e7da7 100644 --- a/.github/workflows/runtime.yml +++ b/.github/workflows/runtime.yml @@ -36,6 +36,17 @@ jobs: grep -q "did not match any packages" <<<"$out" || { echo "::error::octosense-appcard is linked without app-appcard ($target)"; echo "$out"; exit 1; } done cargo check --locked --workspace --features mobile-apps,app-appcard + # Hosted Rinx (app-rinx, in default) is the library module only: + # never its standalone entry or its own local kernel (Rinx ADR + # 0007). Its assistant is the shell's app-peers service. + for target in "" "--target aarch64-linux-android"; do + tree=$(cargo tree --locked $target -e features -i rinx) + grep -q 'rinx feature "octosense-module"' <<<"$tree" || { echo "::error::rinx is not linked as a module ($target)"; exit 1; } + if grep -qE 'rinx feature "(standalone|octos-local|octos-remote)"' <<<"$tree"; then + echo "::error::hosted rinx pulls a standalone runtime ($target)"; exit 1 + fi + cargo tree --locked $target -i octosense-app-peers --depth 0 >/dev/null || { echo "::error::octosense-app-peers is missing ($target)"; exit 1; } + done - name: Android kernel bundling tool working-directory: workspace/OctoSense run: python -m unittest tools/test_android_kernel.py diff --git a/Cargo.lock b/Cargo.lock index 83aba8c4..9c66dd16 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -356,7 +356,7 @@ dependencies = [ [[package]] name = "article-core" version = "0.1.0" -source = "git+https://github.com/upstreamlabs/Rinx?rev=5ad2c1739e5b4776588518ab76440a5367c9968e#5ad2c1739e5b4776588518ab76440a5367c9968e" +source = "git+https://github.com/hagency-org/Rinx.git?rev=82391adf228692222462fc8696a04906a7aaf3eb#82391adf228692222462fc8696a04906a7aaf3eb" dependencies = [ "blake3", "image", @@ -373,7 +373,7 @@ dependencies = [ [[package]] name = "article-makepad" version = "0.1.0" -source = "git+https://github.com/upstreamlabs/Rinx?rev=5ad2c1739e5b4776588518ab76440a5367c9968e#5ad2c1739e5b4776588518ab76440a5367c9968e" +source = "git+https://github.com/hagency-org/Rinx.git?rev=82391adf228692222462fc8696a04906a7aaf3eb#82391adf228692222462fc8696a04906a7aaf3eb" dependencies = [ "article-core", "libc", @@ -5718,7 +5718,7 @@ dependencies = [ [[package]] name = "matrix-sdk" version = "0.18.0" -source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +source = "git+https://github.com/matrix-org/matrix-rust-sdk?rev=6892cb217ae4a886571e928c8efcccfbec5490a6#6892cb217ae4a886571e928c8efcccfbec5490a6" dependencies = [ "anymap2", "as_variant", @@ -5776,7 +5776,7 @@ dependencies = [ [[package]] name = "matrix-sdk-base" version = "0.18.0" -source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +source = "git+https://github.com/matrix-org/matrix-rust-sdk?rev=6892cb217ae4a886571e928c8efcccfbec5490a6#6892cb217ae4a886571e928c8efcccfbec5490a6" dependencies = [ "as_variant", "async-trait", @@ -5803,7 +5803,7 @@ dependencies = [ [[package]] name = "matrix-sdk-common" version = "0.18.0" -source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +source = "git+https://github.com/matrix-org/matrix-rust-sdk?rev=6892cb217ae4a886571e928c8efcccfbec5490a6#6892cb217ae4a886571e928c8efcccfbec5490a6" dependencies = [ "eyeball-im", "futures-core", @@ -5826,7 +5826,7 @@ dependencies = [ [[package]] name = "matrix-sdk-crypto" version = "0.18.0" -source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +source = "git+https://github.com/matrix-org/matrix-rust-sdk?rev=6892cb217ae4a886571e928c8efcccfbec5490a6#6892cb217ae4a886571e928c8efcccfbec5490a6" dependencies = [ "aes 0.9.3", "as_variant", @@ -5865,7 +5865,7 @@ dependencies = [ [[package]] name = "matrix-sdk-indexeddb" version = "0.18.0" -source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +source = "git+https://github.com/matrix-org/matrix-rust-sdk?rev=6892cb217ae4a886571e928c8efcccfbec5490a6#6892cb217ae4a886571e928c8efcccfbec5490a6" dependencies = [ "async-trait", "base64 0.23.1", @@ -5897,7 +5897,7 @@ dependencies = [ [[package]] name = "matrix-sdk-sqlite" version = "0.18.0" -source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +source = "git+https://github.com/matrix-org/matrix-rust-sdk?rev=6892cb217ae4a886571e928c8efcccfbec5490a6#6892cb217ae4a886571e928c8efcccfbec5490a6" dependencies = [ "as_variant", "async-trait", @@ -5924,7 +5924,7 @@ dependencies = [ [[package]] name = "matrix-sdk-store-encryption" version = "0.18.0" -source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +source = "git+https://github.com/matrix-org/matrix-rust-sdk?rev=6892cb217ae4a886571e928c8efcccfbec5490a6#6892cb217ae4a886571e928c8efcccfbec5490a6" dependencies = [ "base64 0.23.1", "blake3", @@ -5946,7 +5946,7 @@ dependencies = [ [[package]] name = "matrix-sdk-ui" version = "0.18.0" -source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +source = "git+https://github.com/matrix-org/matrix-rust-sdk?rev=6892cb217ae4a886571e928c8efcccfbec5490a6#6892cb217ae4a886571e928c8efcccfbec5490a6" dependencies = [ "as_variant", "async-rx", @@ -7163,6 +7163,7 @@ dependencies = [ "makepad-wm-api", "makepad-wm-theme", "octosense-app-hub-app", + "octosense-app-peers", "octosense-appcard", "octosense-llm-config", "octosense-llm-service", @@ -7202,6 +7203,17 @@ dependencies = [ "serde_json", ] +[[package]] +name = "octosense-app-peers" +version = "0.1.0" +dependencies = [ + "log", + "octosense-octos-core", + "serde_json", + "tokio", + "uuid", +] + [[package]] name = "octosense-app-policy" version = "0.1.0" @@ -8425,7 +8437,7 @@ dependencies = [ [[package]] name = "rinx" version = "1.0.0-beta.1" -source = "git+https://github.com/upstreamlabs/Rinx?rev=5ad2c1739e5b4776588518ab76440a5367c9968e#5ad2c1739e5b4776588518ab76440a5367c9968e" +source = "git+https://github.com/hagency-org/Rinx.git?rev=82391adf228692222462fc8696a04906a7aaf3eb#82391adf228692222462fc8696a04906a7aaf3eb" dependencies = [ "anyhow", "arboard", @@ -8461,10 +8473,16 @@ dependencies = [ "mime", "mime_guess", "num_cpus", + "octoscript-makepad", + "octoscript-ui-l0", + "octoscript-widgets", + "octosense-app-peers", + "octosense-app-policy", "pulldown-cmark 0.13.4", "rand 0.8.8", "rangemap", "rfd", + "rinx-miniapp-core", "robius-directories", "robius-file-picker", "robius-location", @@ -8483,10 +8501,20 @@ dependencies = [ "tracing-subscriber", "unicode-segmentation 1.13.3", "url", + "uuid", "webpki-root-certs", "winresource", ] +[[package]] +name = "rinx-miniapp-core" +version = "0.1.0" +source = "git+https://github.com/hagency-org/Rinx.git?rev=82391adf228692222462fc8696a04906a7aaf3eb#82391adf228692222462fc8696a04906a7aaf3eb" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "rmcp" version = "1.8.0" diff --git a/Cargo.toml b/Cargo.toml index af6d1963..bbeb645e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,19 +40,19 @@ path = "src/main.rs" [dependencies] # `appcard,maps`: the Octoscript-AppCard widget set (kit, text roles, weather, # finance, matplot) and the map navigation layer the appcard module renders. -makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d", features = ["appcard", "maps"] } -makepad-studio-protocol = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d" } -makepad-network = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d" } -makepad-wm-api = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d" } +makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f", features = ["appcard", "maps"] } +makepad-studio-protocol = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f" } +makepad-network = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f" } +makepad-wm-api = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f" } # The AI services wire only: the WM relays frames between the aichat child and the apps. -makepad-ai-services = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d" } -makepad-strict-json = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d" } +makepad-ai-services = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f" } +makepad-strict-json = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f" } # In-process hosting (aicontrol §3): the module contract, the palette applied # into each instance's isolate, and the modules this build links. -makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d" } -makepad-wm-theme = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d" } -makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d", default-features = false, optional = true } -makepad-photos = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d", default-features = false, optional = true } +makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f" } +makepad-wm-theme = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f" } +makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f", default-features = false, optional = true } +makepad-photos = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f", default-features = false, optional = true } octosense-reference = { path = "apps/reference", optional = true } # The whole Octoscript-AppCard app hosted in a tile, in-process (Phase B of # the AppCard port): octos-app's AppShell — routing brain, cards, kernel agent. @@ -84,13 +84,20 @@ octosense-llm-config = { path = "../OctoSense-System-Apps/apps/ai-providers/conf # Rinx) connects, configured by the AI providers app, restarted when the # providers change. Feature `octos-core`, on by default. octosense-octos-core = { path = "../OctoSense-System-Apps/crates/octos-core", optional = true } -# Rinx (the Matrix client with Moments and articles) as an in-process module. -# Built without its standalone window entry point. -rinx = { git = "https://github.com/upstreamlabs/Rinx", rev = "5ad2c1739e5b4776588518ab76440a5367c9968e", default-features = false, features = ["octosense-module"], optional = true } +# Apps' assistant access (Rinx ADR 0007): for each module whose declared +# `octos.*` services host policy grants, ONE octos peer owned by the shell's +# system agent and a scoped service injected at module creation +# (OctoSense-System-Apps crates/app-peers). Part of `octos-core`. +octosense-app-peers = { path = "../OctoSense-System-Apps/crates/app-peers", features = ["octos-core"], optional = true } +# Rinx (Matrix chats, Moments, articles and mini apps) as an in-process +# module, built without its standalone entry point or local kernel. Its +# assistant is the shell's, injected at creation: no kernel, no credentials, +# no AppCard. +rinx = { git = "https://github.com/hagency-org/Rinx.git", rev = "82391adf228692222462fc8696a04906a7aaf3eb", default-features = false, features = ["octosense-module"], optional = true } # The assistant as a module, seated in the pane in-process: the web build's # only chat, a desktop option. Without the engine feature: no model runtime, # NoModel and the tool console (the child process keeps the real models). -makepad-aichat = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d", default-features = false, optional = true } +makepad-aichat = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f", default-features = false, optional = true } # A phone cannot spawn the desktop catalog's Cargo binaries. Link the # available modules into native mobile builds without requiring CLI flags. @@ -99,13 +106,14 @@ octosense-reference = { path = "apps/reference" } # The octos kernel service: Android runs the APK's bundled liboctos.so # (MAKEPAD_ANDROID_EXTRA_LIBS, tools/android-kernel.py); iOS has none. octosense-octos-core = { path = "../OctoSense-System-Apps/crates/octos-core" } -makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d", default-features = false } +octosense-app-peers = { path = "../OctoSense-System-Apps/crates/app-peers", features = ["octos-core"] } +makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f", default-features = false } [features] # App Hub is part of the standard launcher: the store, the Card runner and # the system apps (News, Photos, Maps, Camera, Mail, AI providers) it runs; # the octos kernel is a shell service in every standard build. -default = ["app-hub", "octos-core"] +default = ["app-hub", "octos-core", "app-rinx"] # Also usable on desktop for testing the same embedded apps with --module. # Photos is the system app here; `app-photos` links Makepad's native Photos # module instead, for comparison (a linked module of the same id wins). @@ -123,7 +131,7 @@ app-reference = ["dep:octosense-reference"] app-sheets = ["dep:makepad-sheets"] # Photos (the picture wall) linked in as a module, the same way. app-photos = ["dep:makepad-photos"] -octos-core = ["dep:octosense-octos-core", "octosense-llm-service?/octos-core"] +octos-core = ["dep:octosense-octos-core", "dep:octosense-app-peers", "octosense-llm-service?/octos-core"] # The AppCard module (the assistant in a tile) linked in; it connects to the # shell's octos kernel, so it implies `octos-core`. Opt-in only, on desktop # and native mobile alike: AppCard is not shipped by default for now (it @@ -132,8 +140,9 @@ app-appcard = ["dep:octosense-appcard", "octos-core"] # The aichat module linked in (the pane seats it in-process where there are # no processes, or when `aichat` is switched to module hosting). app-aichat = ["dep:makepad-aichat"] -# Rinx linked in as a module (`--module rinx`). -app-rinx = ["dep:rinx"] +# Rinx linked in as a module; its assistant is the shell's (it implies +# `octos-core`). Rinx never starts a kernel of its own here. +app-rinx = ["dep:rinx", "octos-core"] [workspace] members = [".", "apps/reference", "apps/appcard"] @@ -196,6 +205,13 @@ makepad-widgets = { path = "../makepad/widgets" } makepad-code-editor = { path = "../makepad/code_editor" } +# Rinx takes the app-peers contract from OctoSense-System-Apps by git: the +# same checkout this workspace links, so one broker contract and one kernel. +[patch."https://github.com/OctoSense-org/OctoSense-System-Apps.git"] +octosense-app-peers = { path = "../OctoSense-System-Apps/crates/app-peers" } +octosense-octos-core = { path = "../OctoSense-System-Apps/crates/octos-core" } +octosense-llm-config = { path = "../OctoSense-System-Apps/apps/ai-providers/config" } + # Rinx's Matrix patches (see Rinx's Cargo.toml). [patch."https://github.com/ruma/ruma"] ruma = { git = "https://github.com/project-robius/ruma.git", branch = "tsp" } diff --git a/apps/appcard/Cargo.toml b/apps/appcard/Cargo.toml index d2774c6c..d83b8d8b 100644 --- a/apps/appcard/Cargo.toml +++ b/apps/appcard/Cargo.toml @@ -6,8 +6,8 @@ license = "Apache-2.0" publish = false [dependencies] -makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d", features = ["appcard", "maps"] } -makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d" } +makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f", features = ["appcard", "maps"] } +makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f" } # The whole AppCard assistant as a library, from the pinned # OctoSense-System-Apps checkout beside this repository (apps/appcard/app/app; # native-apps.lock.json, tools/setup-native.py): routing brain, card store and diff --git a/apps/reference/Cargo.toml b/apps/reference/Cargo.toml index c6252141..fd7f1dbc 100644 --- a/apps/reference/Cargo.toml +++ b/apps/reference/Cargo.toml @@ -6,5 +6,5 @@ license = "Apache-2.0" publish = false [dependencies] -makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d" } -makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d" } +makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f" } +makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f" } diff --git a/native-apps.lock.json b/native-apps.lock.json index e13c4602..f6e3b6fb 100644 --- a/native-apps.lock.json +++ b/native-apps.lock.json @@ -3,7 +3,7 @@ "repositories": { "system-apps": { "url": "https://github.com/OctoSense-org/OctoSense-System-Apps.git", - "revision": "d30c67cb8f470f4b0c9259c091a7efeae5098205" + "revision": "4fa0f122ab01d58e468d339f1920ec2758741012" } } } diff --git a/native-runtime.lock.json b/native-runtime.lock.json index 993be108..ec3be363 100644 --- a/native-runtime.lock.json +++ b/native-runtime.lock.json @@ -1,5 +1,5 @@ { "schema_version": 1, "url": "https://github.com/OctoSense-org/Octoscript-Makepad.git", - "revision": "99c1e5ee7925060ced59c66d6f8763c22217e4ad" + "revision": "c3d53ba8bdbc219478b98b34fc73066228d9dfb3" } diff --git a/src/app_peers_host.rs b/src/app_peers_host.rs new file mode 100644 index 00000000..dc698a94 --- /dev/null +++ b/src/app_peers_host.rs @@ -0,0 +1,170 @@ +//! Apps' assistant access (Rinx ADR 0007): the shell side of +//! `octosense-app-peers`. +//! +//! When the window manager creates a native module instance, the shell reads the +//! module's declared capabilities. If host policy grants any of the exact +//! `octos.*` assistant services, Home creates a scoped service for that +//! instance, backed by ONE octos peer per app and account. The peer is owned +//! by the shell's system agent (`_main:api:octosense#system`) and runs on the +//! shell's kernel (`octosense_octos_core`). The service is offered to the +//! instance for the duration of its `create` only. A module that declares or +//! is granted nothing gets no service, and no peer is ever allocated for it. +//! Tearing the instance down releases its leases and interrupts its peer's +//! running work. The kernel and other apps keep running. +//! +//! Host policy: native modules that ship with the shell are granted what they +//! declare from this list. The person's AI provider choice lives in +//! Settings → Accounts → AI providers; a per-app toggle is future work. + +use makepad_app_module::{AppModule, InstanceScope}; + +/// The native modules the shell lets use the assistant, and with what. +#[cfg(any(feature = "octos-core", target_os = "android", target_os = "ios"))] +pub fn policy() -> &'static octosense_app_peers::hosted::HostPolicy { + static POLICY: std::sync::OnceLock = + std::sync::OnceLock::new(); + POLICY.get_or_init(|| { + let policy = octosense_app_peers::hosted::HostPolicy::default(); + // Rinx: its native mini-app host serves these to reviewed mini apps. + policy.allow("rinx", octosense_app_peers::OCTOS_SERVICES); + policy + }) +} + +/// One instance's assistant service, held by the module host. +pub struct Assistant { + #[cfg(any(feature = "octos-core", target_os = "android", target_os = "ios"))] + broker: octosense_app_peers::broker::Broker, +} + +impl Assistant { + /// The instance is going away: release its leases and contexts. + pub fn release(&self) { + #[cfg(any(feature = "octos-core", target_os = "android", target_os = "ios"))] + octosense_app_peers::OctosAppService::release(&self.broker); + } +} + +/// Before `module.create`: offer the instance its service, if it gets one. +pub fn offer(module: &dyn AppModule, scope: &InstanceScope) -> Option { + #[cfg(any(feature = "octos-core", target_os = "android", target_os = "ios"))] + { + let broker = octosense_app_peers::hosted::launch( + module.id(), + module.label(), + module.capabilities().iter().copied(), + policy(), + )?; + octosense_app_peers::hosted::offer(module.id(), &scope.to_string(), &broker); + Some(Assistant { broker }) + } + #[cfg(not(any(feature = "octos-core", target_os = "android", target_os = "ios")))] + { + let _ = (module, scope); + None + } +} + +/// After `module.create`: drop an offer the module did not take. +pub fn withdraw(module: &dyn AppModule, scope: &InstanceScope) { + #[cfg(any(feature = "octos-core", target_os = "android", target_os = "ios"))] + octosense_app_peers::injection::withdraw(module.id(), &scope.to_string()); + #[cfg(not(any(feature = "octos-core", target_os = "android", target_os = "ios")))] + let _ = (module, scope); +} + +/// Rinx runs one instance per process: tests that create it take turns. +#[cfg(test)] +pub(crate) static RINX_INSTANCE_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); + +#[cfg(all(test, any(feature = "octos-core", target_os = "android", target_os = "ios")))] +mod tests { + use super::*; + use makepad_app_module::*; + use makepad_widgets::*; + use std::sync::atomic::{AtomicBool, Ordering}; + + static CLAIMED: AtomicBool = AtomicBool::new(false); + + /// A module that declares the assistant and records whether it got one. + struct Probe(&'static str, &'static [&'static str]); + impl AppModule for Probe { + fn id(&self) -> &'static str { + self.0 + } + fn label(&self) -> &'static str { + "Probe" + } + fn capabilities(&self) -> &'static [&'static str] { + self.1 + } + fn open_schema(&self) -> OpenSchema { + OpenSchema::new(1) + } + fn register(&self, _vm: &mut ScriptVm) {} + fn create(&self, vm: &mut ScriptVm, _open: ValidatedOpen, handles: InstanceHandles) -> InstanceParts { + let service = octosense_app_peers::injection::claim(self.0, &handles.scope.to_string()); + CLAIMED.store(service.is_some(), Ordering::SeqCst); + let value = script_eval!(vm, { use mod.prelude.widgets.* View {} }); + InstanceParts { + root: WidgetRef::script_from_value(vm, value), + executor: Box::new(NoExecutor), + shutdown: Box::new(|_| {}), + } + } + } + struct NoExecutor; + impl ServiceExecutor for NoExecutor { + fn manifest(&self) -> makepad_ai_services::wire::ServiceManifest { + makepad_ai_services::wire::ServiceManifest::new("probe", "Probe", "test") + } + fn execute(&mut self, _cx: &mut Cx, call: &makepad_ai_services::wire::ServiceCall) -> ExecOutcome { + ExecOutcome::Done(makepad_ai_services::wire::ToolResult::unavailable(&call.call_id, "test")) + } + } + + static AI_PROBE: Probe = Probe("assistant-probe", &["storage", "octos.session.open", "octos.turn.start"]); + static PLAIN_PROBE: Probe = Probe("plain-probe", &["storage", "net"]); + static UNGRANTED_PROBE: Probe = Probe("ungranted-probe", &["octos.turn.start"]); + + #[test] + fn a_granted_module_gets_its_service_at_creation_and_others_get_none() { + policy().allow("assistant-probe", ["octos.session.open", "octos.turn.start"]); + let mut cx = Cx::new(Box::new(|_, _| {})); + cx.with_vm(makepad_widgets::script_mod); + let mut host = crate::module_host::ModuleHost::default(); + host.create(&mut cx, 1, &AI_PROBE, AI_PROBE.open_schema().empty_open().unwrap(), dvec2(400.0, 700.0)).unwrap(); + assert!(CLAIMED.load(Ordering::SeqCst), "the granted module got a scoped service"); + assert!(host.assistant_of(1).is_some()); + host.create(&mut cx, 2, &PLAIN_PROBE, PLAIN_PROBE.open_schema().empty_open().unwrap(), dvec2(400.0, 700.0)).unwrap(); + assert!(!CLAIMED.load(Ordering::SeqCst), "a module without assistant services gets none"); + assert!(host.assistant_of(2).is_none(), "no peer is allocated for it"); + host.create(&mut cx, 3, &UNGRANTED_PROBE, UNGRANTED_PROBE.open_schema().empty_open().unwrap(), dvec2(400.0, 700.0)).unwrap(); + assert!(!CLAIMED.load(Ordering::SeqCst), "declaring is not being granted"); + assert!(host.assistant_of(3).is_none()); + // An offer never outlives its create: nothing is left to claim. + assert!(octosense_app_peers::injection::claim("assistant-probe", "i1g1").is_none()); + assert!(host.teardown(&mut cx, 1)); + } + + /// The real Rinx module: hosted from creation, with the shell's service, + /// and no kernel started by creating it (ADR 0007 criterion 7). + #[cfg(feature = "app-rinx")] + #[test] + fn rinx_is_hosted_with_the_shells_service_and_starts_no_kernel() { + let _one_rinx = super::RINX_INSTANCE_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner()); + let mut cx = Cx::new(Box::new(|_, _| {})); + cx.with_vm(makepad_widgets::script_mod); + let mut host = crate::module_host::ModuleHost::default(); + let module = &rinx::module::RINX_MODULE; + assert!(module.capabilities().contains(&"octos.turn.start"), "Rinx declares its assistant needs"); + host.create(&mut cx, 7, module, module.open_schema().empty_open().unwrap(), dvec2(400.0, 700.0)).unwrap(); + assert!(host.assistant_of(7).is_some(), "the shell gave Rinx a scoped service"); + assert!(rinx::octos_service::is_hosted(), "hosted mode comes from module creation"); + let service = rinx::octos_service::service().expect("Rinx took the injected service"); + assert_eq!(service.deployment(), octosense_app_peers::Deployment::Hosted); + assert_eq!(service.settings_entry(), octosense_app_peers::SettingsEntry::Host); + assert!(!octosense_octos_core::status().running, "creating Rinx starts no kernel"); + assert!(host.teardown(&mut cx, 7)); + } +} diff --git a/src/apps.rs b/src/apps.rs index 48d05d5f..3670ad7f 100644 --- a/src/apps.rs +++ b/src/apps.rs @@ -383,6 +383,7 @@ mod tests { #[test] fn bundled_apps_open_without_catalog_files_or_child_processes() { use makepad_widgets::*; + let _one_rinx = crate::app_peers_host::RINX_INSTANCE_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner()); let catalog = bundled_catalog(); // AppCard is opt-in (`app-appcard`), not part of `mobile-apps`. let expected: &[&str] = if cfg!(feature = "app-appcard") { @@ -390,6 +391,12 @@ mod tests { } else { &["reference", "sheets", "apphub", "news", "photos", "maps", "camera", "mail", "ai-providers"] }; + // Rinx (app-rinx, in `default`) is linked before App Hub. + let mut expected: Vec<&str> = expected.to_vec(); + if cfg!(feature = "app-rinx") { + let at = expected.iter().position(|id| *id == "apphub").unwrap(); + expected.insert(at, "rinx"); + } assert_eq!(catalog.iter().map(|app| app.id.as_str()).collect::>(), expected); assert!(catalog.iter().all(|app| app.manifest.is_none())); // The system apps have no native module: the Card runner hosts them, diff --git a/src/main.rs b/src/main.rs index 35dd0fe2..ce6d5529 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,7 @@ use makepad_widgets::makepad_platform::thread::{Lane, SignalToUI, TaskHandle}; use makepad_widgets::*; mod ai_bus; +mod app_peers_host; mod apps; mod binds; mod clients; diff --git a/src/module_host.rs b/src/module_host.rs index 59171e9d..1b6a3d7e 100644 --- a/src/module_host.rs +++ b/src/module_host.rs @@ -40,6 +40,9 @@ pub struct AppInstance { upstream: Receiver, /// The instance's requests for extra windows, and our reports of closes. pub windows: ModuleWindows, + /// The instance's scoped assistant service (Rinx ADR 0007), when the + /// module declares and is granted `octos.*` services. + assistant: Option, } impl AppInstance { @@ -119,6 +122,9 @@ impl ModuleHost { let windows = ModuleWindows::new(self.extra_windows); let handles = InstanceHandles { scope, storage, viewport: Viewport { size: viewport }, replies, windows: windows.clone() }; let vm_id = cx.alloc_splash_vm_with_network(false); + // The assistant is offered to THIS instance for the duration of its + // create only; the module takes it there or never gets it. + let assistant = crate::app_peers_host::offer(module, &scope); let parts = cx.with_script_vm_id_trusted(vm_id, |vm| { // The isolate came up with the stock theme; the WM's palette // retints it exactly as it retints a child process's. @@ -129,6 +135,7 @@ impl ModuleHost { module.register(vm); module.create(vm, open, handles) }); + crate::app_peers_host::withdraw(module, &scope); log!( "wm: module instance {}.{} for client {} in isolate {:?} (scope {})", module.id(), @@ -150,6 +157,7 @@ impl ModuleHost { shutdown: Some(parts.shutdown), upstream, windows, + assistant, }, ); Ok(()) @@ -171,6 +179,11 @@ impl ModuleHost { } } + /// The assistant service the shell gave this instance, if any. + pub fn assistant_of(&self, client: ClientId) -> Option<&crate::app_peers_host::Assistant> { + self.instances.get(&client)?.assistant.as_ref() + } + pub fn is_module(&self, client: ClientId) -> bool { self.instances.contains_key(&client) } @@ -284,6 +297,10 @@ impl ModuleHost { if let Some(shutdown) = instance.shutdown.take() { cx.with_script_vm_id_trusted(instance.vm_id, |vm| shutdown(vm)); } + // Release the app's assistant leases; the shared kernel stays. + if let Some(assistant) = instance.assistant.take() { + assistant.release(); + } let vm_id = instance.vm_id; let label = format!("{}.{}", instance.module.id(), instance.instance_no); // The last refs into the isolate's heap go before the heap does. From 56f18aa4c661b0ea8f4f0cc37bb768f60e18afcc Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Sat, 26 Sep 2026 21:21:59 -0700 Subject: [PATCH 37/42] System-Apps cc1cee3e: the kernel with host-owned app peers (octos 552767dd) Co-Authored-By: Claude Opus 5.5 (1M context) --- Cargo.lock | 32 ++++++++++++++++---------------- native-apps.lock.json | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9c66dd16..d75c50a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6646,7 +6646,7 @@ dependencies = [ [[package]] name = "octos-agent" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" +source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" dependencies = [ "async-trait", "base64 0.22.1", @@ -6774,7 +6774,7 @@ dependencies = [ [[package]] name = "octos-bus" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" +source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" dependencies = [ "aes 0.8.4", "async-trait", @@ -6803,7 +6803,7 @@ dependencies = [ [[package]] name = "octos-cli" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" +source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" dependencies = [ "agent-client-protocol", "argon2", @@ -6883,7 +6883,7 @@ dependencies = [ [[package]] name = "octos-core" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" +source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" dependencies = [ "chrono", "eyre", @@ -6897,7 +6897,7 @@ dependencies = [ [[package]] name = "octos-diagnostics" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" +source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" dependencies = [ "eyre", "octos-core", @@ -6909,7 +6909,7 @@ dependencies = [ [[package]] name = "octos-fleet" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" +source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" dependencies = [ "eyre", "octos-core", @@ -6925,7 +6925,7 @@ dependencies = [ [[package]] name = "octos-fleet-worker" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" +source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" dependencies = [ "async-trait", "eyre", @@ -6942,7 +6942,7 @@ dependencies = [ [[package]] name = "octos-llm" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" +source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" dependencies = [ "async-trait", "base64 0.22.1", @@ -6966,7 +6966,7 @@ dependencies = [ [[package]] name = "octos-memory" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" +source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" dependencies = [ "bincode", "chrono", @@ -6985,7 +6985,7 @@ dependencies = [ [[package]] name = "octos-pipeline" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" +source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" dependencies = [ "async-trait", "chrono", @@ -7007,7 +7007,7 @@ dependencies = [ [[package]] name = "octos-plugin" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" +source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" dependencies = [ "eyre", "metrics", @@ -7021,7 +7021,7 @@ dependencies = [ [[package]] name = "octos-sandbox" version = "0.1.0" -source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" +source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" dependencies = [ "clap", "eyre", @@ -7036,7 +7036,7 @@ dependencies = [ [[package]] name = "octos-services" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" +source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" dependencies = [ "chrono", "dirs", @@ -7057,7 +7057,7 @@ dependencies = [ [[package]] name = "octos-store" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" +source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" dependencies = [ "base64 0.22.1", "chrono", @@ -7077,7 +7077,7 @@ dependencies = [ [[package]] name = "octos-swarm" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" +source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" dependencies = [ "async-trait", "chrono", @@ -7096,7 +7096,7 @@ dependencies = [ [[package]] name = "octos-workflows" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" +source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" dependencies = [ "chrono", "eyre", diff --git a/native-apps.lock.json b/native-apps.lock.json index f6e3b6fb..6e66a5d8 100644 --- a/native-apps.lock.json +++ b/native-apps.lock.json @@ -3,7 +3,7 @@ "repositories": { "system-apps": { "url": "https://github.com/OctoSense-org/OctoSense-System-Apps.git", - "revision": "4fa0f122ab01d58e468d339f1920ec2758741012" + "revision": "cc1cee3ee308843aac569111cf2eb31f995646cd" } } } From 11ac9d4dae3bd92b2c91e86b5dbdc9a1b264ba88 Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Sun, 27 Sep 2026 00:07:11 -0700 Subject: [PATCH 38/42] Rinx assistant tools on the chat pane; runtime with self-confirmed tools - Pin Rinx a72e4b00 (hagency-org/Rinx#28): its ServiceExecutor serves status, list_rooms, open_room, draft_message, read_room (per-account room grant), open_mini_app and send_message (confirmed in Rinx's own sheet). - Runtime: makepad 6cf03859 (OctoSense-org/makepad#36) via OctoScript-Makepad 6881fb6c (#47). A tool declared confirmed_by_app skips the pane's confirm card. - ai_bus: a process client cannot vouch for the person's consent, so its registrations lose that claim; only in-process modules keep it. Tests cover the bus and, end to end through the pane's engine, that a module's self-confirmed tool runs at once while its other destructive tools and another process's claim still get the card. Co-Authored-By: Claude Opus 5.5 (1M context) --- Cargo.lock | 8 ++-- Cargo.toml | 26 +++++------ apps/appcard/Cargo.toml | 4 +- apps/reference/Cargo.toml | 4 +- native-runtime.lock.json | 2 +- src/ai_bus.rs | 95 ++++++++++++++++++++++++++++++++++++++- 6 files changed, 116 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8d98cab0..3b1d380f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -356,7 +356,7 @@ dependencies = [ [[package]] name = "article-core" version = "0.1.0" -source = "git+https://github.com/hagency-org/Rinx.git?rev=5cf70a822aadb0da626123affa67c814f6848b58#5cf70a822aadb0da626123affa67c814f6848b58" +source = "git+https://github.com/hagency-org/Rinx.git?rev=a72e4b003a05ebcb31bc86c346d7dd06cdffbceb#a72e4b003a05ebcb31bc86c346d7dd06cdffbceb" dependencies = [ "blake3", "image", @@ -373,7 +373,7 @@ dependencies = [ [[package]] name = "article-makepad" version = "0.1.0" -source = "git+https://github.com/hagency-org/Rinx.git?rev=5cf70a822aadb0da626123affa67c814f6848b58#5cf70a822aadb0da626123affa67c814f6848b58" +source = "git+https://github.com/hagency-org/Rinx.git?rev=a72e4b003a05ebcb31bc86c346d7dd06cdffbceb#a72e4b003a05ebcb31bc86c346d7dd06cdffbceb" dependencies = [ "article-core", "libc", @@ -8437,7 +8437,7 @@ dependencies = [ [[package]] name = "rinx" version = "1.0.0-beta.1" -source = "git+https://github.com/hagency-org/Rinx.git?rev=5cf70a822aadb0da626123affa67c814f6848b58#5cf70a822aadb0da626123affa67c814f6848b58" +source = "git+https://github.com/hagency-org/Rinx.git?rev=a72e4b003a05ebcb31bc86c346d7dd06cdffbceb#a72e4b003a05ebcb31bc86c346d7dd06cdffbceb" dependencies = [ "anyhow", "arboard", @@ -8509,7 +8509,7 @@ dependencies = [ [[package]] name = "rinx-miniapp-core" version = "0.1.0" -source = "git+https://github.com/hagency-org/Rinx.git?rev=5cf70a822aadb0da626123affa67c814f6848b58#5cf70a822aadb0da626123affa67c814f6848b58" +source = "git+https://github.com/hagency-org/Rinx.git?rev=a72e4b003a05ebcb31bc86c346d7dd06cdffbceb#a72e4b003a05ebcb31bc86c346d7dd06cdffbceb" dependencies = [ "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index e8eb3f9e..13bb4164 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,19 +40,19 @@ path = "src/main.rs" [dependencies] # `appcard,maps`: the Octoscript-AppCard widget set (kit, text roles, weather, # finance, matplot) and the map navigation layer the appcard module renders. -makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f", features = ["appcard", "maps"] } -makepad-studio-protocol = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f" } -makepad-network = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f" } -makepad-wm-api = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f" } +makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "6cf03859630761f5cb99ce7fcdfd8c30475d9ab8", features = ["appcard", "maps"] } +makepad-studio-protocol = { git = "https://github.com/OctoSense-org/makepad.git", rev = "6cf03859630761f5cb99ce7fcdfd8c30475d9ab8" } +makepad-network = { git = "https://github.com/OctoSense-org/makepad.git", rev = "6cf03859630761f5cb99ce7fcdfd8c30475d9ab8" } +makepad-wm-api = { git = "https://github.com/OctoSense-org/makepad.git", rev = "6cf03859630761f5cb99ce7fcdfd8c30475d9ab8" } # The AI services wire only: the WM relays frames between the aichat child and the apps. -makepad-ai-services = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f" } -makepad-strict-json = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f" } +makepad-ai-services = { git = "https://github.com/OctoSense-org/makepad.git", rev = "6cf03859630761f5cb99ce7fcdfd8c30475d9ab8" } +makepad-strict-json = { git = "https://github.com/OctoSense-org/makepad.git", rev = "6cf03859630761f5cb99ce7fcdfd8c30475d9ab8" } # In-process hosting (aicontrol §3): the module contract, the palette applied # into each instance's isolate, and the modules this build links. -makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f" } -makepad-wm-theme = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f" } -makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f", default-features = false, optional = true } -makepad-photos = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f", default-features = false, optional = true } +makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "6cf03859630761f5cb99ce7fcdfd8c30475d9ab8" } +makepad-wm-theme = { git = "https://github.com/OctoSense-org/makepad.git", rev = "6cf03859630761f5cb99ce7fcdfd8c30475d9ab8" } +makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "6cf03859630761f5cb99ce7fcdfd8c30475d9ab8", default-features = false, optional = true } +makepad-photos = { git = "https://github.com/OctoSense-org/makepad.git", rev = "6cf03859630761f5cb99ce7fcdfd8c30475d9ab8", default-features = false, optional = true } octosense-reference = { path = "apps/reference", optional = true } # The whole Octoscript-AppCard app hosted in a tile, in-process (Phase B of # the AppCard port): octos-app's AppShell — routing brain, cards, kernel agent. @@ -93,11 +93,11 @@ octosense-app-peers = { path = "../OctoSense-System-Apps/crates/app-peers", feat # module, built without its standalone entry point or local kernel. Its # assistant is the shell's, injected at creation: no kernel, no credentials, # no AppCard. -rinx = { git = "https://github.com/hagency-org/Rinx.git", rev = "5cf70a822aadb0da626123affa67c814f6848b58", default-features = false, features = ["octosense-module"], optional = true } +rinx = { git = "https://github.com/hagency-org/Rinx.git", rev = "a72e4b003a05ebcb31bc86c346d7dd06cdffbceb", default-features = false, features = ["octosense-module"], optional = true } # The assistant as a module, seated in the pane in-process: the web build's # only chat, a desktop option. Without the engine feature: no model runtime, # NoModel and the tool console (the child process keeps the real models). -makepad-aichat = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f", default-features = false, optional = true } +makepad-aichat = { git = "https://github.com/OctoSense-org/makepad.git", rev = "6cf03859630761f5cb99ce7fcdfd8c30475d9ab8", default-features = false, optional = true } # A phone cannot spawn the desktop catalog's Cargo binaries. Link the # available modules into native mobile builds without requiring CLI flags. @@ -107,7 +107,7 @@ octosense-reference = { path = "apps/reference" } # (MAKEPAD_ANDROID_EXTRA_LIBS, tools/android-kernel.py); iOS has none. octosense-octos-core = { path = "../OctoSense-System-Apps/crates/octos-core" } octosense-app-peers = { path = "../OctoSense-System-Apps/crates/app-peers", features = ["octos-core"] } -makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f", default-features = false } +makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "6cf03859630761f5cb99ce7fcdfd8c30475d9ab8", default-features = false } [features] # App Hub is part of the standard launcher: the store, the Card runner and diff --git a/apps/appcard/Cargo.toml b/apps/appcard/Cargo.toml index d83b8d8b..0a2bf335 100644 --- a/apps/appcard/Cargo.toml +++ b/apps/appcard/Cargo.toml @@ -6,8 +6,8 @@ license = "Apache-2.0" publish = false [dependencies] -makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f", features = ["appcard", "maps"] } -makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f" } +makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "6cf03859630761f5cb99ce7fcdfd8c30475d9ab8", features = ["appcard", "maps"] } +makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "6cf03859630761f5cb99ce7fcdfd8c30475d9ab8" } # The whole AppCard assistant as a library, from the pinned # OctoSense-System-Apps checkout beside this repository (apps/appcard/app/app; # native-apps.lock.json, tools/setup-native.py): routing brain, card store and diff --git a/apps/reference/Cargo.toml b/apps/reference/Cargo.toml index fd7f1dbc..b7587232 100644 --- a/apps/reference/Cargo.toml +++ b/apps/reference/Cargo.toml @@ -6,5 +6,5 @@ license = "Apache-2.0" publish = false [dependencies] -makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f" } -makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f" } +makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "6cf03859630761f5cb99ce7fcdfd8c30475d9ab8" } +makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "6cf03859630761f5cb99ce7fcdfd8c30475d9ab8" } diff --git a/native-runtime.lock.json b/native-runtime.lock.json index ec3be363..105eb060 100644 --- a/native-runtime.lock.json +++ b/native-runtime.lock.json @@ -1,5 +1,5 @@ { "schema_version": 1, "url": "https://github.com/OctoSense-org/Octoscript-Makepad.git", - "revision": "c3d53ba8bdbc219478b98b34fc73066228d9dfb3" + "revision": "6881fb6c3c3220e407633b0ba5211c3d42c7e625" } diff --git a/src/ai_bus.rs b/src/ai_bus.rs index 50f7eff7..819f9702 100644 --- a/src/ai_bus.rs +++ b/src/ai_bus.rs @@ -227,8 +227,13 @@ impl AiBus { let Some(mut up) = HostedUp::parse(json) else { return Route::Drop }; // The sender's claim is never used: the link IS the identity. up.from = Some(Self::endpoint_of(client)); - match &up.msg { + match &mut up.msg { ServiceUp::Register { manifest, .. } => { + // Another process cannot vouch for the person's consent: its + // destructive tools wait for the pane's own confirm card. Only + // in-process modules (`register_local`, trusted native code) + // keep a tool's claim that its own sheet confirms it. + manifest.clear_self_confirm(); self.manifests.insert(client, manifest.clone()); } ServiceUp::Unregister => { @@ -497,4 +502,92 @@ mod local_tests { assert!(bus.local_clients().is_empty()); assert!(bus.client_died(4).is_none()); } + + #[test] + fn only_an_in_process_module_keeps_a_self_confirmed_tool() { + let send = || { + sheets().with_tool( + ToolDef::new("send", "Send the sheet.", r#"{"type":"object","properties":{}}"#, Risk::Destructive) + .confirmed_by_app(), + ) + }; + let registered = |json: &str| match HostedUp::parse(json).expect("valid").msg { + ServiceUp::Register { manifest, .. } => manifest, + _ => panic!("expected a registration"), + }; + let mut bus = AiBus { pane_client: Some(9), ..Default::default() }; + // A module in this process: its own sheet is the one confirmation. + let local = registered(&bus.register_local(4, send())); + assert!(local.tool("send").unwrap().confirms_itself()); + // A process client's claim is dropped before the pane sees it, and in + // the replay, so the pane confirms its destructive tools itself. + let up = HostedUp { from: None, msg: ServiceUp::Register { manifest: send(), port_tag: 0 } }; + let Route::ToPane(json) = bus.on_custom(5, &up.to_json()) else { panic!("expected ToPane") }; + assert!(!registered(&json).tool("send").unwrap().confirms_itself()); + let replay = bus.replay(AiBus::os_manifest(&[])); + assert!(registered(&replay[1]).tool("send").unwrap().confirms_itself(), "m4 keeps it"); + assert!(!registered(&replay[2]).tool("send").unwrap().confirms_itself(), "w5 does not"); + } + + /// End to end through the pane's engine: a module's self-confirmed tool + /// reaches the module at once (its own sheet asks the person), while the + /// same claim from another process still gets the pane's confirm card. + #[test] + fn the_pane_skips_its_confirm_only_for_a_modules_self_confirmed_tool() { + use makepad_ai_services::engine::{EngineCore, EngineEvent, Model, ModelEvent, ServiceRegistry, ToolDefinition}; + use makepad_ai_services::port::ServiceLink; + /// A model that asks for one call. + struct OneCall(Vec); + impl Model for OneCall { + fn label(&self) -> String { + "test".into() + } + fn configure(&mut self, _: &str, _: &[ToolDefinition]) -> Result<(), String> { + Ok(()) + } + fn send_user(&mut self, _: &str, _: &str) {} + fn send_tool_result(&mut self, _: &str, _: &str, _: bool) {} + fn cancel(&mut self) {} + fn reset(&mut self) {} + fn poll(&mut self) -> Vec { + std::mem::take(&mut self.0) + } + } + let manifest = || { + sheets() + .with_tool( + ToolDef::new("send", "Send the sheet.", r#"{"type":"object","properties":{}}"#, Risk::Destructive) + .confirmed_by_app(), + ) + .with_tool(ToolDef::new("delete", "Delete the sheet.", r#"{"type":"object","properties":{}}"#, Risk::Destructive)) + }; + let mut bus = AiBus { pane_client: Some(9), ..Default::default() }; + let local = bus.register_local(4, manifest()); + let up = HostedUp { from: None, msg: ServiceUp::Register { manifest: manifest(), port_tag: 0 } }; + let Route::ToPane(process) = bus.on_custom(5, &up.to_json()) else { panic!("expected ToPane") }; + for (frame, endpoint, tool, pane_confirms) in [ + (&local, "m4", "send", false), + (&local, "m4", "delete", true), + (&process, "w5", "send", true), + ] { + let up = HostedUp::parse(frame).expect("valid registration"); + let ServiceUp::Register { manifest, .. } = up.msg.clone() else { panic!("expected a registration") }; + let registry = ServiceRegistry::new(); + let (link, host) = ServiceLink::pair(manifest); + registry.register_as(link, EndpointId(endpoint.into()), "test", None).unwrap(); + host.up.send(up).unwrap(); + registry.pump(); + while host.down.try_recv().is_ok() {} + let call = ModelEvent::ToolCall { call_id: "m1".into(), name: format!("sheets.{tool}"), args: "{}".into() }; + let model = OneCall(vec![call, ModelEvent::TurnDone { tool_calls: 1 }]); + let mut core = EngineCore::new(registry, Box::new(model), None, 1); + core.send("do it", 0.0); + let events = core.pump(0.1); + let confirm = events.contains(&EngineEvent::Confirm { call_id: "m1".into() }); + assert_eq!(confirm, pane_confirms, "{endpoint} {tool}: {events:?}"); + let reached = std::iter::from_fn(|| host.down.try_recv().ok()) + .any(|down| matches!(down.msg, ServiceDown::Call(ref c) if c.tool == tool)); + assert_eq!(reached, !pane_confirms, "{endpoint} {tool}: the call reaches the app only without a card"); + } + } } From 46e93353bd437d98ff20c1eedcd55902f0591f1c Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Sun, 27 Sep 2026 00:31:36 -0700 Subject: [PATCH 39/42] System-Apps 59d0806d: app peers' approvals stay the person's (octos a6ea8505) octos-org/octos#2560: the system agent can no longer approve a hosted app peer's tool approvals (nor is it woken for them), and a second peer_send_input is no longer dropped when a provider reuses tool-call ids. Co-Authored-By: Claude Opus 5.5 (1M context) --- Cargo.lock | 32 ++++++++++++++++---------------- native-apps.lock.json | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8d98cab0..a0aa9a4b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6646,7 +6646,7 @@ dependencies = [ [[package]] name = "octos-agent" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" +source = "git+https://github.com/octos-org/octos.git?rev=a6ea8505170735f191a12bb47629a6728415d417#a6ea8505170735f191a12bb47629a6728415d417" dependencies = [ "async-trait", "base64 0.22.1", @@ -6774,7 +6774,7 @@ dependencies = [ [[package]] name = "octos-bus" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" +source = "git+https://github.com/octos-org/octos.git?rev=a6ea8505170735f191a12bb47629a6728415d417#a6ea8505170735f191a12bb47629a6728415d417" dependencies = [ "aes 0.8.4", "async-trait", @@ -6803,7 +6803,7 @@ dependencies = [ [[package]] name = "octos-cli" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" +source = "git+https://github.com/octos-org/octos.git?rev=a6ea8505170735f191a12bb47629a6728415d417#a6ea8505170735f191a12bb47629a6728415d417" dependencies = [ "agent-client-protocol", "argon2", @@ -6883,7 +6883,7 @@ dependencies = [ [[package]] name = "octos-core" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" +source = "git+https://github.com/octos-org/octos.git?rev=a6ea8505170735f191a12bb47629a6728415d417#a6ea8505170735f191a12bb47629a6728415d417" dependencies = [ "chrono", "eyre", @@ -6897,7 +6897,7 @@ dependencies = [ [[package]] name = "octos-diagnostics" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" +source = "git+https://github.com/octos-org/octos.git?rev=a6ea8505170735f191a12bb47629a6728415d417#a6ea8505170735f191a12bb47629a6728415d417" dependencies = [ "eyre", "octos-core", @@ -6909,7 +6909,7 @@ dependencies = [ [[package]] name = "octos-fleet" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" +source = "git+https://github.com/octos-org/octos.git?rev=a6ea8505170735f191a12bb47629a6728415d417#a6ea8505170735f191a12bb47629a6728415d417" dependencies = [ "eyre", "octos-core", @@ -6925,7 +6925,7 @@ dependencies = [ [[package]] name = "octos-fleet-worker" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" +source = "git+https://github.com/octos-org/octos.git?rev=a6ea8505170735f191a12bb47629a6728415d417#a6ea8505170735f191a12bb47629a6728415d417" dependencies = [ "async-trait", "eyre", @@ -6942,7 +6942,7 @@ dependencies = [ [[package]] name = "octos-llm" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" +source = "git+https://github.com/octos-org/octos.git?rev=a6ea8505170735f191a12bb47629a6728415d417#a6ea8505170735f191a12bb47629a6728415d417" dependencies = [ "async-trait", "base64 0.22.1", @@ -6966,7 +6966,7 @@ dependencies = [ [[package]] name = "octos-memory" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" +source = "git+https://github.com/octos-org/octos.git?rev=a6ea8505170735f191a12bb47629a6728415d417#a6ea8505170735f191a12bb47629a6728415d417" dependencies = [ "bincode", "chrono", @@ -6985,7 +6985,7 @@ dependencies = [ [[package]] name = "octos-pipeline" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" +source = "git+https://github.com/octos-org/octos.git?rev=a6ea8505170735f191a12bb47629a6728415d417#a6ea8505170735f191a12bb47629a6728415d417" dependencies = [ "async-trait", "chrono", @@ -7007,7 +7007,7 @@ dependencies = [ [[package]] name = "octos-plugin" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" +source = "git+https://github.com/octos-org/octos.git?rev=a6ea8505170735f191a12bb47629a6728415d417#a6ea8505170735f191a12bb47629a6728415d417" dependencies = [ "eyre", "metrics", @@ -7021,7 +7021,7 @@ dependencies = [ [[package]] name = "octos-sandbox" version = "0.1.0" -source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" +source = "git+https://github.com/octos-org/octos.git?rev=a6ea8505170735f191a12bb47629a6728415d417#a6ea8505170735f191a12bb47629a6728415d417" dependencies = [ "clap", "eyre", @@ -7036,7 +7036,7 @@ dependencies = [ [[package]] name = "octos-services" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" +source = "git+https://github.com/octos-org/octos.git?rev=a6ea8505170735f191a12bb47629a6728415d417#a6ea8505170735f191a12bb47629a6728415d417" dependencies = [ "chrono", "dirs", @@ -7057,7 +7057,7 @@ dependencies = [ [[package]] name = "octos-store" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" +source = "git+https://github.com/octos-org/octos.git?rev=a6ea8505170735f191a12bb47629a6728415d417#a6ea8505170735f191a12bb47629a6728415d417" dependencies = [ "base64 0.22.1", "chrono", @@ -7077,7 +7077,7 @@ dependencies = [ [[package]] name = "octos-swarm" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" +source = "git+https://github.com/octos-org/octos.git?rev=a6ea8505170735f191a12bb47629a6728415d417#a6ea8505170735f191a12bb47629a6728415d417" dependencies = [ "async-trait", "chrono", @@ -7096,7 +7096,7 @@ dependencies = [ [[package]] name = "octos-workflows" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" +source = "git+https://github.com/octos-org/octos.git?rev=a6ea8505170735f191a12bb47629a6728415d417#a6ea8505170735f191a12bb47629a6728415d417" dependencies = [ "chrono", "eyre", diff --git a/native-apps.lock.json b/native-apps.lock.json index 6e66a5d8..861d7273 100644 --- a/native-apps.lock.json +++ b/native-apps.lock.json @@ -3,7 +3,7 @@ "repositories": { "system-apps": { "url": "https://github.com/OctoSense-org/OctoSense-System-Apps.git", - "revision": "cc1cee3ee308843aac569111cf2eb31f995646cd" + "revision": "59d0806deb7b966c3d7e9ee5008de6e6df208b91" } } } From c1bbe157d76e398a8ce9939751de56811cd017fc Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Sun, 27 Sep 2026 01:13:52 -0700 Subject: [PATCH 40/42] System-Apps 03350348: AppCard on the shells' runtime (6881fb6c) System-Apps now pins Octoscript-Makepad 6881fb6c / makepad 6cf03859 for AppCard, matching this shell; octos stays a6ea8505. Co-Authored-By: Claude Opus 5.5 (1M context) --- native-apps.lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native-apps.lock.json b/native-apps.lock.json index 861d7273..bbc4d287 100644 --- a/native-apps.lock.json +++ b/native-apps.lock.json @@ -3,7 +3,7 @@ "repositories": { "system-apps": { "url": "https://github.com/OctoSense-org/OctoSense-System-Apps.git", - "revision": "59d0806deb7b966c3d7e9ee5008de6e6df208b91" + "revision": "033503482b1beb6e6f1d79c5fddaa3254b7dfc85" } } } From 7d1f00ccabc6ee6f6192fd8997123cf12719d31d Mon Sep 17 00:00:00 2001 From: "Jing.Li" Date: Sun, 27 Sep 2026 07:27:07 -0700 Subject: [PATCH 41/42] docs: how to open & add the full Makepad catalog Two guides under docs/ plus README links: - open-apps.md: launch the desktop, open an app in a tile, fix an empty catalog (offline cargo metadata), add a new catalog entry. - add-all-makepad-apps.md: point the desktop at apps.makepad.json and open any Makepad app. Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 1 + docs/add-all-makepad-apps.md | 192 +++++++++++++++++++++++++++++++++++ docs/open-apps.md | 147 +++++++++++++++++++++++++++ 3 files changed, 340 insertions(+) create mode 100644 docs/add-all-makepad-apps.md create mode 100644 docs/open-apps.md diff --git a/README.md b/README.md index c6863e01..238e2714 100644 --- a/README.md +++ b/README.md @@ -225,6 +225,7 @@ Catalog lookup: `--apps ` if given, else `~/.octosense/apps.json` if it ex ``` - `"source": "makepad"` resolves through Cargo to the same Makepad checkout as the host; such builds go to `~/.octosense/build/makepad`. +- Step-by-step guides: [docs/open-apps.md](docs/open-apps.md) and [docs/add-all-makepad-apps.md](docs/add-all-makepad-apps.md). - Relative paths resolve from the catalog's directory. Arguments are passed literally, without a shell. - `policy`: `"new"` opens another instance; `"focus"` (default) focuses a running one. - Do not add `--stdin-loop` or Studio variables; the shell adds them. Restart after editing. diff --git a/docs/add-all-makepad-apps.md b/docs/add-all-makepad-apps.md new file mode 100644 index 00000000..c7e8634e --- /dev/null +++ b/docs/add-all-makepad-apps.md @@ -0,0 +1,192 @@ +# 把 Makepad 全家桶应用到 OctoSense + +本文说明如何把 Makepad 仓库里的全部应用(浏览器、文件、终端、天气、金融、邮件、笔记、日历、提醒、计算器、Fabric、Score、视频、导航、VJ、Fab、Director、Image、PDF、AI 等)一次性加进 OctoSense 桌面,让它们全部出现在 **⌘Space** 应用菜单里并可随时打开。 + +--- + +## 一、前提条件 + +1. **Makepad 同级框架仓库就位** + + ```sh + cd /path/to/OctoSense + python3 tools/setup-native.py --update + ``` + + 把 `../makepad`、`../octoscript`、`../octoscript-makepad` 检出到锁定版本。 + 目录里 `"source": "makepad"` 的行要靠 `cargo metadata --offline` 解析出 + 这个 checkout 才能生效。 + +2. **Cargo 缓存完整**(离线 metadata 能成功) + + 第一次构建宿主后,跑一次联网 `cargo metadata` 把缺失依赖补进缓存: + + ```sh + cargo metadata --format-version 1 --manifest-path Cargo.toml > /dev/null + ``` + + 之后 `cargo metadata --offline` 正常,目录解析出 Makepad 根。 + (缓存不全时所有 `source: makepad` 行会被静默跳过,日志只显示 + `no app 'X' in the registry`。) + +3. **编译宿主** + + ```sh + cargo build --release + ``` + +--- + +## 二、两个应用目录 + +| 文件 | 内容 | +|---|---| +| `config/apps.json`(默认) | 只有 `reference`(本项目自带的示例) | +| `config/apps.makepad.json` | Reference + Makepad 全家桶(20+ 个应用) | + +`config/apps.makepad.json` 已经包含所有 Makepad 应用,你**不需要手写**。 +启动时指定它: + +```sh +cargo run --release -- --apps config/apps.makepad.json +``` + +如果想把它变成默认,改 `config/apps.json` 的内容(或建 +`~/.octosense/apps.json` 个人目录覆盖),重启即可。 + +--- + +## 三、目录里 Makepad 全家桶的完整条目 + +`config/apps.makepad.json` 的 `source: makepad` 行(每行 3 要素: +`package` = Makepad 的 Cargo 包名,`bin` = 可执行文件名,`policy` = 多实例策略): + +| id | label | package | bin | policy | +|---|---|---|---|---| +| `browser` | Browser | makepad-browser | browser | focus | +| `files` | Files | makepad-files | files | focus (args: `--demo`) | +| `terminal` | Terminal | makepad-terminal | terminal | new | +| `mixer` | Mixer | makepad-mixer | makepad-mixer | focus | +| `task` | Task Manager | makepad-task | task | focus | +| `sheets` | Sheets | makepad-sheets | sheets | focus | +| `photos` | Photos | makepad-photos | photos | focus | +| `clock` | Clock | makepad-clock | clock | focus | +| **`weather`** | **Weather** | **makepad-weather** | **weather** | **focus** | +| `finance` | Finance | makepad-finance | finance | focus | +| `mail` | Mail | makepad-mail | mail | focus | +| `notes` | Notes | makepad-notes | notes | focus | +| `calendar` | Calendar | makepad-calendar | calendar | focus | +| `reminders` | Reminders | makepad-reminders | reminders | focus | +| `calculator` | Calculator | makepad-calculator | calculator | focus | +| `fabric` | Fabric | makepad-fabric | makepad-fabric | focus | +| `score` | Score | makepad-app-score | makepad-app-score | focus | +| `video` | Video Player | makepad-video | video | new | +| `route` | Route | makepad-app-route | route | focus | +| `vj` | VJ | makepad-vj | makepad-vj | focus | +| `fab` | Fab | makepad-fab | makepad-fab | focus | +| `studio` | Director | makepad-director | director | focus | +| `image` | Image Viewer | makepad-image | image | new | +| `pdf` | PDF Viewer | makepad-pdf | pdf | new | +| `aichat` | AI | makepad-aichat | aichat | new | + +`policy: focus` = 已有实例就聚焦,`policy: new` = 每次开新实例。 + +--- + +## 四、启动并打开全家桶 + +```sh +# 1. 启动桌面,加载 Makepad 全家桶目录 +cargo run --release -- --apps config/apps.makepad.json +``` + +启动后用任一方式打开应用: + +**脚本化**(每个应用独立一行,适合自动化): + +```sh +# 在启动参数里同时开多个(可叠加) +cargo run --release -- \ + --apps config/apps.makepad.json \ + --test-action launch-weather \ + --test-action launch-browser + +# 或者在桌面已运行时,另开一个 shell 发 test-action +# (test-action 只在启动时执行一次,所以每次要新起进程) +``` + +**手动**(在桌面里操作,最常用): + +- **⌘Space** → 应用菜单,搜索并点击任意应用名 +- 或 **⌘1…0** 切工作区,**⌘W** 关 tile,**⌘F** 当前 tile 全屏 + +每个应用首次打开会现场 `cargo run` 编译(进度显示在 tile 里, +首次冷构建约 90 秒);之后复用 Cargo 缓存,秒开。 + +--- + +## 五、验证 + +启动后在桌面主日志(重定向 stdout 可得)里找: + +``` +wm: desktop style octosense applied to N clients and 0 modules +# N = 已打开的应用 tile 数 + +# 对每个打开的应用: +wm: --test-action launch weather +wm: launched weather as client 1 +wm: weather client 1 first frame in NNN ms (cold) ← 出画面 + +# 打开多个应用: +wm: launched browser as client 2 +wm: launched terminal as client 3 +... +``` + +`first frame in NNN ms (cold)` 表示该应用的首帧已渲染,tile 里能看到画面。 +每个应用自己的日志在 `~/.octosense/wm/logs/.log`。 + +--- + +## 六、常见问题 + +**打开应用时报 `no app 'X' in the registry`** + +Cargo 离线缓存不全。联网跑一次: + +```sh +cargo metadata --format-version 1 --manifest-path Cargo.toml > /dev/null +``` + +再重启 OctoSense。 + +**应用 tile 里一直显示 "building…" 不出画面** + +首次冷构建,进度正常。等 90 秒左右出首帧。之后秒开。 + +**应用打不开(构建失败)** + +看 `~/.octosense/wm/logs/.log` 里的编译错误。 +Makepad 全家桶应用都依赖同一个 Makepad checkout,版本不匹配会失败。 + +**某个应用不想要,隐藏它的启动器图标** + +在 `~/.octosense/wm/launcher.hides` 里加一行应用 id,例如: + +```json +["image", "pdf", "aichat"] +``` + +Image/PDF/AI 这三个 helper 应用默认出现在启动器里(除非被隐藏)。 + +--- + +## 相关 + +- 单个应用目录格式:README「Add an app」章节 +- 目录解析原理与离线 metadata 踩坑:[open-apps.md](open-apps.md) 第四、五章 +- Makepad 全家桶目录是生成物:由上游 `scripts/upstream.py catalog`(读 + Makepad checkout 的依赖图生成)产出,本地在 `config/apps.overlay.json` + 叠加适配。检查漂移或重新生成: + `python3 scripts/upstream.py catalog`(加 `--apply` 写回)。 diff --git a/docs/open-apps.md b/docs/open-apps.md new file mode 100644 index 00000000..a148124f --- /dev/null +++ b/docs/open-apps.md @@ -0,0 +1,147 @@ +# 在 OctoSense 中打开托管应用(操作记录 + 参考) + +这份文档记录 2026-09-26 实际跑通的流程,并整理成可复用的参考。 + +OctoSense 桌面能把 Makepad 应用作为独立子进程托管在自己的窗口(tile)里。 +下面先按时间顺序记录当时怎么做的,再给可照抄的命令。 + +## 一、当时是怎么操作的(按时间) + +1. **准备同级框架仓库**:`python3 tools/setup-native.py --update` + —— 把 `../makepad`、`../octoscript`、`../octoscript-makepad` 检出到锁定版本。 +2. **编译宿主**:`cargo build`(debug 或 release),产物在 `CARGO_TARGET_DIR` + 指定的 target 目录下(本机是 `/Volumes/PSSD/dev/rust-target`)。 +3. **启动桌面**:跑编译出的 `octosense` 可执行文件。 + 第一次用 `--test-action launch-weather` 打 weather,日志报 + `no app 'weather' in the registry` —— 应用目录里查不到 weather。 +4. **查原因**:目录的 `"source": "makepad"` 行要靠 + `cargo metadata --offline` 解析出 Makepad checkout; + 而离线 metadata 因为缓存缺依赖(`agent-client-protocol` 1.3.0)而失败, + 导致所有 makepad 应用行被静默跳过。 +5. **修复**:联网跑一次 `cargo metadata` 把缺的依赖补进缓存; + 之后离线 metadata 正常,目录解析出 `/Volumes/PSSD/CodeProjects/makepad`。 +6. **再开 weather**:桌面把 weather 作为 **client 1** 托管,现场 + `cargo run` 编译;首次冷构建 91 秒出首帧(`first frame in 91473 ms (cold)`), + 天气应用成功渲染进桌面 tile。 + +## 二、可照抄的命令 + +### 启动并打开天气 + +```sh +# 启动桌面(用 Makepad 全家桶目录) +cargo run --release -- --apps config/apps.makepad.json + +# 在另一个 shell 里,或直接加在启动参数上: +cargo run --release -- --test-action launch-weather +``` + +`--test-action launch-` 是脚本化打开应用的方式, +app-id 就是目录里的 `id`(weather、browser、files、terminal、aichat……)。 +首次启动会现场 `cargo run` 编译应用,进度显示在 tile 里; +之后复用 Cargo 缓存,秒开。 + +### 手动操作(不用 test-action) + +- **⌘Space** 打开应用菜单/搜索,直接搜 "Weather" +- **⌘W** 关 tile,**⌘F** 当前 tile 全屏,**⌘1…0** 切工作区 +- 菜单里 Learn → Keybindings 列全部快捷键 + +### 验证日志 + +启动后在桌面主日志(重定向 stdout 可得)里找这几行: + +``` +wm: --test-action launch weather +wm: launched weather as client 1 +wm: weather client 1 first frame in NNN ms (cold) ← 应用已出画面 +``` + +## 三、两个应用目录 + +| 文件 | 内容 | 何时用 | +|---|---|---| +| `config/apps.json`(默认) | 只有 `reference` | 默认桌面 | +| `config/apps.makepad.json` | Reference + Makepad 全家桶(Browser、Files、Terminal、Weather、Finance、Mail、Notes、Calendar、Reminders、Calculator、Fabric、Score、Video、Route、VJ、Fab、Director、Image、PDF、AI 等) | 想开满应用时用 | + +用 `--apps` 选目录: + +```sh +cargo run --release -- --apps config/apps.makepad.json +``` + +个人目录 `~/.octosense/apps.json` 优先级高于项目默认;`--apps` 永远指向你点名的文件。 + +## 四、目录里为什么应用会"缺席" + +`"source": "makepad"` 的行不是按路径找,而是让 Cargo 报告它用哪个 Makepad checkout +来编译本宿主,然后去那个 checkout 里找应用。 + +1. 宿主启动时跑一次 `cargo metadata --offline --locked`, + 在依赖图里找带 `apps/wm/Cargo.toml` 标记的 Makepad 仓库根。 + (见 `src/octosense/makepad_source.rs`、`src/octosense/catalog.rs`。) +2. 找不到 checkout(比如离线依赖不全),这些行会被**静默跳过**, + 不会报错 —— 日志里只会看到 `no app 'X' in the registry`。 + +症状:启动后用 `--test-action launch-weather` 打开应用, +日志打 `octosense: no app 'weather' in the registry`,目录里却没有 weather。 + +原因:Cargo 缓存里缺少 lock 文件锁定的依赖版本(本次是 +`agent-client-protocol` 要 1.3.0,缓存只有 1.2.0), +`cargo metadata --offline` 直接失败 → 解析不出 Makepad 根 → +所有 `source: makepad` 行被丢弃。 + +修复(联网跑一次 metadata 把依赖补进缓存): + +```sh +cd OctoSense +cargo metadata --format-version 1 --manifest-path Cargo.toml > /dev/null +``` + +之后离线 metadata 正常,目录解析出 `/Volumes/PSSD/CodeProjects/makepad`。 +(前提:同级框架仓库已经用 `python3 tools/setup-native.py` 准备好。) + +## 五、往目录里加一个新应用 + +目录是一组 JSON 数组,每行三选一: + +- `"manifest"` + `"package"` + `"bin"` —— 指到某个 Cargo 工程(相对目录所在目录) +- `"executable"` —— 指到已安装的可执行文件 +- `"source": "makepad"` + `"package"` + `"bin"` —— 让宿主按本构建的 Makepad checkout 解析 + +```json +{ + "id": "notes", + "label": "Notes", + "manifest": "../notes/Cargo.toml", + "package": "my-notes", + "bin": "notes", + "policy": "new", + "args": [] +} +``` + +规则(与 `src/octosense/catalog.rs` 校验一致): + +- `id` 只允许小写字母、数字、`-`、`_`;目录内不允许重复 +- `label` 必填;`policy` 缺省是 `focus`(已有实例就聚焦),`new` 强制开新实例 +- `args` 是字符串数组,原样传给子进程(不经 shell) +- 应用必须支持 `--stdin-loop` 托管协议,且与本宿主同一 Makepad 版本 +- 可执行文件托管的条目不要写 `package` / `bin` + +放哪: + +```sh +# 项目默认目录:改 config/apps.json,重启 OctoSense +# 个人目录(优先于项目默认):~/.octosense/apps.json +# 临时选目录:--apps /path/to/your.json +``` + +写完之后,应用就会出现在 **⌘Space** 应用菜单里;也可以 `--test-action launch-` 直接打开。 +不可用(找不到 manifest / 构建失败)的应用会在桌面日志里报原因,不会崩溃。 + +## 相关 + +- 应用目录格式:README「Add an app」章节 +- 每个应用托管时自己的日志:`~/.octosense/wm/logs/.log` +- 目录里不存在的 app 快捷键会提示「unavailable」,不会崩溃 From f03d2398cfd9ac1bb059fe3de7f738d9f90eb628 Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Sun, 27 Sep 2026 07:27:07 -0700 Subject: [PATCH 42/42] docs: bring the app-opening guides up to date - The default catalog (config/apps.json) already lists the full Makepad set; apps.makepad.json is an identical copy for --apps. - Makepad's Photos and Mail left the catalogs; the system apps replace them (note added). - Machine-specific paths replaced with the standard layout. - README points to both guides (Chinese). Co-Authored-By: Claude Opus 5.5 (1M context) --- README.md | 2 +- docs/add-all-makepad-apps.md | 15 +++++++-------- docs/open-apps.md | 12 ++++++------ 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 238e2714..178085cc 100644 --- a/README.md +++ b/README.md @@ -225,7 +225,7 @@ Catalog lookup: `--apps ` if given, else `~/.octosense/apps.json` if it ex ``` - `"source": "makepad"` resolves through Cargo to the same Makepad checkout as the host; such builds go to `~/.octosense/build/makepad`. -- Step-by-step guides: [docs/open-apps.md](docs/open-apps.md) and [docs/add-all-makepad-apps.md](docs/add-all-makepad-apps.md). +- Step-by-step guides (Chinese): [open a hosted app](docs/open-apps.md), including the fix for an empty catalog when `cargo metadata --offline` fails, and [open the full Makepad catalog](docs/add-all-makepad-apps.md). - Relative paths resolve from the catalog's directory. Arguments are passed literally, without a shell. - `policy`: `"new"` opens another instance; `"focus"` (default) focuses a running one. - Do not add `--stdin-loop` or Studio variables; the shell adds them. Restart after editing. diff --git a/docs/add-all-makepad-apps.md b/docs/add-all-makepad-apps.md index c7e8634e..f38d736e 100644 --- a/docs/add-all-makepad-apps.md +++ b/docs/add-all-makepad-apps.md @@ -41,18 +41,19 @@ | 文件 | 内容 | |---|---| -| `config/apps.json`(默认) | 只有 `reference`(本项目自带的示例) | -| `config/apps.makepad.json` | Reference + Makepad 全家桶(20+ 个应用) | +| `config/apps.json`(默认) | Reference + Makepad 全家桶(20+ 个应用),桌面默认就读它 | +| `config/apps.makepad.json` | 与 `config/apps.json` 相同的副本,供 `--apps` 显式指定 | -`config/apps.makepad.json` 已经包含所有 Makepad 应用,你**不需要手写**。 -启动时指定它: +默认目录已经包含所有 Makepad 应用,你**不需要手写**,直接 `cargo run --release` 即可。 +也可以显式指定副本: ```sh cargo run --release -- --apps config/apps.makepad.json ``` -如果想把它变成默认,改 `config/apps.json` 的内容(或建 -`~/.octosense/apps.json` 个人目录覆盖),重启即可。 +想换成自己的目录,建 `~/.octosense/apps.json` 个人目录覆盖默认目录,重启即可。 + +> 注意:Makepad 自带的 Photos 和 Mail 已从目录中移除,由 OctoSense 的系统应用(News、Photos、Maps、Camera、Mail、AI providers,经 App Hub 打包进 Shell)取代,它们不在开发者程序目录里。 --- @@ -69,11 +70,9 @@ cargo run --release -- --apps config/apps.makepad.json | `mixer` | Mixer | makepad-mixer | makepad-mixer | focus | | `task` | Task Manager | makepad-task | task | focus | | `sheets` | Sheets | makepad-sheets | sheets | focus | -| `photos` | Photos | makepad-photos | photos | focus | | `clock` | Clock | makepad-clock | clock | focus | | **`weather`** | **Weather** | **makepad-weather** | **weather** | **focus** | | `finance` | Finance | makepad-finance | finance | focus | -| `mail` | Mail | makepad-mail | mail | focus | | `notes` | Notes | makepad-notes | notes | focus | | `calendar` | Calendar | makepad-calendar | calendar | focus | | `reminders` | Reminders | makepad-reminders | reminders | focus | diff --git a/docs/open-apps.md b/docs/open-apps.md index a148124f..dd891f92 100644 --- a/docs/open-apps.md +++ b/docs/open-apps.md @@ -1,6 +1,6 @@ # 在 OctoSense 中打开托管应用(操作记录 + 参考) -这份文档记录 2026-09-26 实际跑通的流程,并整理成可复用的参考。 +这份文档整理了在 OctoSense 桌面中打开托管应用的步骤(2026-09-26 实测)。 OctoSense 桌面能把 Makepad 应用作为独立子进程托管在自己的窗口(tile)里。 下面先按时间顺序记录当时怎么做的,再给可照抄的命令。 @@ -10,7 +10,7 @@ OctoSense 桌面能把 Makepad 应用作为独立子进程托管在自己的窗 1. **准备同级框架仓库**:`python3 tools/setup-native.py --update` —— 把 `../makepad`、`../octoscript`、`../octoscript-makepad` 检出到锁定版本。 2. **编译宿主**:`cargo build`(debug 或 release),产物在 `CARGO_TARGET_DIR` - 指定的 target 目录下(本机是 `/Volumes/PSSD/dev/rust-target`)。 + 指定的 target 目录下(默认 `target/`)。 3. **启动桌面**:跑编译出的 `octosense` 可执行文件。 第一次用 `--test-action launch-weather` 打 weather,日志报 `no app 'weather' in the registry` —— 应用目录里查不到 weather。 @@ -19,7 +19,7 @@ OctoSense 桌面能把 Makepad 应用作为独立子进程托管在自己的窗 而离线 metadata 因为缓存缺依赖(`agent-client-protocol` 1.3.0)而失败, 导致所有 makepad 应用行被静默跳过。 5. **修复**:联网跑一次 `cargo metadata` 把缺的依赖补进缓存; - 之后离线 metadata 正常,目录解析出 `/Volumes/PSSD/CodeProjects/makepad`。 + 之后离线 metadata 正常,目录解析出同级的 `../makepad` checkout。 6. **再开 weather**:桌面把 weather 作为 **client 1** 托管,现场 `cargo run` 编译;首次冷构建 91 秒出首帧(`first frame in 91473 ms (cold)`), 天气应用成功渲染进桌面 tile。 @@ -61,8 +61,8 @@ wm: weather client 1 first frame in NNN ms (cold) ← 应用已出画面 | 文件 | 内容 | 何时用 | |---|---|---| -| `config/apps.json`(默认) | 只有 `reference` | 默认桌面 | -| `config/apps.makepad.json` | Reference + Makepad 全家桶(Browser、Files、Terminal、Weather、Finance、Mail、Notes、Calendar、Reminders、Calculator、Fabric、Score、Video、Route、VJ、Fab、Director、Image、PDF、AI 等) | 想开满应用时用 | +| `config/apps.json`(默认) | Reference + Makepad 全家桶(Browser、Files、Terminal、Weather、Finance、Notes、Calendar、Reminders、Calculator、Fabric、Score、Video、Route、VJ、Fab、Director、Image、PDF、AI 等) | 默认桌面 | +| `config/apps.makepad.json` | 与默认目录相同的副本 | 用 `--apps` 显式指定时 | 用 `--apps` 选目录: @@ -98,7 +98,7 @@ cd OctoSense cargo metadata --format-version 1 --manifest-path Cargo.toml > /dev/null ``` -之后离线 metadata 正常,目录解析出 `/Volumes/PSSD/CodeProjects/makepad`。 +之后离线 metadata 正常,目录解析出同级的 `../makepad` checkout。 (前提:同级框架仓库已经用 `python3 tools/setup-native.py` 准备好。) ## 五、往目录里加一个新应用