Host: workspaces (always) and encrypted folder sync (AppConfig::with_sync) commands; SiteHooks::backup_sections and backup_section_names say what a backup and sync carry; commands::WORKSPACES, commands::SYNC, testkit::assert_sync_guards

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
CRTkafa 2026-10-01 04:26:03 +07:00
parent 385ca189da
commit 48ff509b15
5 changed files with 435 additions and 5 deletions

View file

@ -90,9 +90,28 @@ pub const GFONTS: &[&str] = &["gfonts_installed", "gfonts_install", "gfonts_remo
/// state), served when the app sets `AppConfig::updates`; needs the `net` feature.
pub const UPDATE: &[&str] = &["update_status", "update_check", "apply_update", "app_ready"];
/// Shell-only, optional: saved workspaces (named tab, pane and deck layouts). State-free: served
/// for every app; an app that uses them grants them to the shell.
pub const WORKSPACES: &[&str] = &["workspaces_get", "workspaces_set"];
/// Shell-only, optional: encrypted folder sync of the settings backup, served when the app sets
/// `AppConfig::sync`.
pub const SYNC: &[&str] = &[
"sync_status",
"sync_choose_folder",
"sync_unlock",
"sync_lock",
"sync_disable",
"sync_configure",
"sync_push",
"sync_check",
"sync_open",
"sync_mark_pulled",
];
/// Whether `command` is one of the host's (dispatched to the host, never to the app's handler).
pub fn is_host(command: &str) -> bool {
SHELL.contains(&command) || TAB.contains(&command) || SITE.contains(&command) || STORE.contains(&command) || MEDIA.contains(&command) || GFONTS.contains(&command) || UPDATE.contains(&command)
SHELL.contains(&command) || TAB.contains(&command) || SITE.contains(&command) || STORE.contains(&command) || MEDIA.contains(&command) || GFONTS.contains(&command) || UPDATE.contains(&command) || WORKSPACES.contains(&command) || SYNC.contains(&command)
}
/// The capability permission tauri-build generates for `command` (`tabs_sync` → `allow-tabs-sync`).
@ -107,8 +126,8 @@ pub fn permission(command: &str) -> String {
/// tauri_build::AppManifest::new().commands(waybetter_host::commands::manifest(&["draft_list"]))
/// ```
pub fn manifest(app: &[&'static str]) -> &'static [&'static str] {
let mut all: Vec<&'static str> = Vec::with_capacity(app.len() + SHELL.len() + TAB.len() + SITE.len() + STORE.len() + MEDIA.len() + GFONTS.len() + UPDATE.len());
for command in app.iter().chain(SHELL).chain(TAB).chain(SITE).chain(STORE).chain(MEDIA).chain(GFONTS).chain(UPDATE) {
let mut all: Vec<&'static str> = Vec::with_capacity(app.len() + SHELL.len() + TAB.len() + SITE.len() + STORE.len() + MEDIA.len() + GFONTS.len() + UPDATE.len() + WORKSPACES.len() + SYNC.len());
for command in app.iter().chain(SHELL).chain(TAB).chain(SITE).chain(STORE).chain(MEDIA).chain(GFONTS).chain(UPDATE).chain(WORKSPACES).chain(SYNC) {
if !all.contains(command) {
all.push(command);
}
@ -125,7 +144,7 @@ mod tests {
let all = manifest(&["draft_list", "tabs_sync"]);
assert_eq!(all[0], "draft_list");
assert_eq!(all.iter().filter(|c| **c == "tabs_sync").count(), 1);
for command in SHELL.iter().chain(TAB).chain(SITE).chain(STORE).chain(MEDIA).chain(GFONTS).chain(UPDATE) {
for command in SHELL.iter().chain(TAB).chain(SITE).chain(STORE).chain(MEDIA).chain(GFONTS).chain(UPDATE).chain(WORKSPACES).chain(SYNC) {
assert!(all.contains(command), "{command}");
assert!(is_host(command));
}
@ -135,7 +154,7 @@ mod tests {
#[test]
fn shell_and_tab_commands_do_not_overlap() {
assert!(!SHELL.iter().any(|command| TAB.contains(command) || SITE.contains(command) || STORE.contains(command) || MEDIA.contains(command) || GFONTS.contains(command) || UPDATE.contains(command)));
assert!(!SHELL.iter().any(|command| TAB.contains(command) || SITE.contains(command) || STORE.contains(command) || MEDIA.contains(command) || GFONTS.contains(command) || UPDATE.contains(command) || WORKSPACES.contains(command) || SYNC.contains(command)));
assert!(!TAB.iter().any(|command| SITE.contains(command)));
}
}