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 four release/deploy scripts live here
in
scripts/as the source of truth, embedded as string constants (RELEASE_SH,GEN_LATEST_SH,INSTALL_APP_SH,LAUNCH_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.tabs::pick_live_neighbour(which sibling tab becomes active after one unloads) also sits on this zero-dependency surface. - Runtime (
runtimefeature). The app-shell every app would otherwise hand-roll:register_plugins(builder, config_path, skip_labels)installs the plugins every app registers identically —geometry, the updater, and the process plugin (for the updater's relaunch) — and the custom protocols that serve the home and detach surfaces.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. The file is scoped to the app's config path (geometry_filename, over the reusableconfig_scoped_filename). 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 Close Tab / Pop Out Tab items, tab-navigation accelerators, and the family-standard close accelerators (⌘W a tab, ⌘⇧W the window) as constants. It returns the submenus for the app to interleave with its own 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).detachpops a tab out into its own window — the label scheme and the banner-shell page; the app moves the tab's content.compositingplaces content webviews into the chrome's reported hole (HoleRect,layout_webviews) — curator and lector.watchis the config-file hot-reload watcher (parent-dir watch, file-name match, format-on-save echo swallowed); the app parses — curator and lector.mouse_navdrives WKWebView back/forward from the mouse side buttons, andprogress_bardraws the page-load bar on each content webview — curator and lector.
In use by warden, curator, and lector. Deliberately NOT shared (each diverges per app): IPC fan-out, warden's config watcher and native compositing, 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
MIT © Lachlan Collins