The shared Tauri app-shell + release tooling for curator, warden, and lector — one release pipeline, many apps.
curator (browser keeper-tabs), warden (terminals), and lector (local doc sites) are sibling macOS
Tauri apps. They already share a view (chrome-core)
and config primitives (config-core). shell-core is
the third shared layer: the
build/release tooling and the sliver of Tauri runtime setup that is identical for any such
app — extracted once so a new sibling app inherits it instead of copy-pasting (and drifting).
It carries two concerns, split by Cargo feature so a build-dependency stays light:
- Build/release tooling (default, zero-dependency). The three release scripts live here in
scripts/as the source of truth, embedded as string constants (RELEASE_SH,GEN_LATEST_SH,INSTALL_APP_SH). A consuming app'sbuild.rswrites them into its ownscripts/(git-ignored) — the same embed-and-materialize pattern chrome-core uses for its CSS/JS. The scripts are generic; every app-specific value is read from a tracked per-appscripts/tooling.env(three keys:APP_NAME,TAURI_CRATE_DIR,UPDATER_REPO).build_stamp()is the other build-time helper — a git sha/date stamp for the About box. - Runtime (
runtimefeature). Four pieces of app-shell every app would otherwise hand-roll:register_plugins()installs the plugins every app registers identically:geometry(per-window size/position persistence), the updater, and the process plugin (for the updater's relaunch).geometrypersists each window's size and position in AppKit points, clamped to the target monitor's work area on restore, and never recorded while a window is fullscreen or minimized. It replacestauri-plugin-window-state, whose physical-pixel model is wrong across monitors of differing scale factor — a rect saved on a 2x display and applied on a 1x one comes back out by the ratio.menu::build_spine()builds the menu spine — the App submenu (About, Check for Updates…), the Config submenu (Edit Config / Reveal in Finder), and the Window submenu (a checked per-window selector), plus the family-standard close accelerators (⌘W a tab, ⌘⇧W the window) as constants so they can't drift per app again. It returns the submenus for the app to interleave with its own genuinely-per-app items; it does not set the menu.home::{home_state, show_home, close_home}is the home surface — what an app shows when it would otherwise have no window (no config / a load error / a valid config's window list), so a fresh install never launches to nothing. shell-core owns the surface; the app wires the actions (the "Create a starter config" button is the app's own command, which is how shell-core stays free of any dependency on config-core).
In use. Extracted from warden and curator's copy-pasted tooling; all three apps (warden, curator, and lector) now consume it. Deliberately NOT shared (each diverges per app): IPC fan-out, the config watcher, the apps' own menu items (only the spine above is shared), and the chrome-caller command gate (curator-only — warden hosts no content webviews at all, and lector's remote-origin content webviews are denied by Tauri's own IPC dispatch without needing an explicit gate).
Each app pins shell-core by git rev, using the light default features for build.rs and the
runtime feature for the app. Under Cargo's resolver 2 the two resolve features independently, so
the build-dependency never pulls in tauri:
[build-dependencies]
shell-core = { git = "https://github.com/Lockyc/shell-core", rev = "<commit>", default-features = false }
[dependencies]
shell-core = { git = "https://github.com/Lockyc/shell-core", rev = "<commit>", features = ["runtime"] }build.rs materializes the scripts + stamps the build:
shell_core::materialize_scripts(std::path::Path::new("../scripts"))?; // path from the crate to repo scripts/
shell_core::build_stamp();and the app commits a three-line scripts/tooling.env (see scripts/tooling.env.example).
just gate # pre-merge: fmt check + clippy + tests
just test # the test suite
just zero-dep # prove the default feature set still pulls no dependencies
menu and home are gated behind the runtime feature, so a bare cargo test compiles neither
and passes having checked almost nothing — the recipes pass --features runtime for you. The
rust-toolchain.toml pin makes those checks reproducible; it governs standalone development only,
since each consuming app compiles shell-core with its own pin.
MIT © Lachlan Collins