Repair: the Google Fonts host commands as 7f9e172 meant them (a staging race split them from 43fc8a1)

Two agents staged the same host files at once: 7f9e172 went in with the diagnostics' hunks of app.rs, commands.rs and lib.rs instead of its own, and 43fc8a1 then dropped fonts_commands.rs. This puts back exactly the Google Fonts wiring from the working tree (AppConfig::with_fonts, SiteHooks::fonts_changed, commands::GFONTS, the fonts handler, fonts_commands.rs); the diagnostics stay as committed. theme.rs, still in progress, is not included.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
CRTkafa 2026-10-01 04:12:43 +07:00
parent 43fc8a15f5
commit 825742b964
4 changed files with 112 additions and 5 deletions

View file

@ -86,6 +86,9 @@ pub struct AppConfig {
pub store: Option<&'static crate::store::StoreConfig>, pub store: Option<&'static crate::store::StoreConfig>,
/// The shell's media panel (gallery folders, the clipboard history): serves [`commands::MEDIA`]. /// The shell's media panel (gallery folders, the clipboard history): serves [`commands::MEDIA`].
pub media: bool, pub media: bool,
/// Google Fonts (install, remove, preview; the cache in app data): serves [`commands::GFONTS`]
/// and calls [`SiteHooks::fonts_changed`]. Needs the `net` feature.
pub fonts: bool,
} }
impl AppConfig { impl AppConfig {
@ -109,9 +112,16 @@ impl AppConfig {
home_url: None, home_url: None,
store: None, store: None,
media: false, media: false,
fonts: false,
} }
} }
/// Serves the Google Fonts commands ([`commands::GFONTS`]).
pub const fn with_fonts(mut self) -> Self {
self.fonts = true;
self
}
/// Serves the media panel's commands ([`commands::MEDIA`]). /// Serves the media panel's commands ([`commands::MEDIA`]).
pub const fn with_media(mut self) -> Self { pub const fn with_media(mut self) -> Self {
self.media = true; self.media = true;
@ -202,6 +212,10 @@ pub trait SiteHooks: Send + Sync + 'static {
/// A tab webview was created. /// A tab webview was created.
fn tab_created(&self, _app: &AppHandle) {} fn tab_created(&self, _app: &AppHandle) {}
/// A Google font was installed or removed ([`commands::GFONTS`]): give the site tabs their
/// theme, and the font bytes, again.
fn fonts_changed(&self, _app: &AppHandle) {}
/// `tab` was laid out for `item` (existing or new), before it is placed. /// `tab` was laid out for `item` (existing or new), before it is placed.
fn tab_placed(&self, _app: &AppHandle, _tab: &Webview, _item: &TabLayout) {} fn tab_placed(&self, _app: &AppHandle, _tab: &Webview, _item: &TabLayout) {}
@ -248,6 +262,11 @@ fn host_state(app: &AppHandle) -> tauri::State<'_, Host> {
app.state::<Host>() app.state::<Host>()
} }
/// The app's site hooks ([`run_app`] must be running).
pub(crate) fn hooks(app: &AppHandle) -> Arc<dyn SiteHooks> {
host_state(app).hooks.clone()
}
/// The app's config ([`run_app`] must be running). /// The app's config ([`run_app`] must be running).
pub fn config(app: &AppHandle) -> AppConfig { pub fn config(app: &AppHandle) -> AppConfig {
host_state(app).config host_state(app).config
@ -1115,6 +1134,17 @@ fn store_handler() -> impl Fn(tauri::ipc::Invoke<Wry>) -> bool + Send + Sync + '
|_| false |_| false
} }
/// The Google Fonts commands ([`commands::GFONTS`]): with the `net` feature (downloads) only.
#[cfg(feature = "net")]
fn fonts_handler() -> impl Fn(tauri::ipc::Invoke<Wry>) -> bool + Send + Sync + 'static {
crate::fonts_commands::handler()
}
#[cfg(not(feature = "net"))]
fn fonts_handler() -> impl Fn(tauri::ipc::Invoke<Wry>) -> bool + Send + Sync + 'static {
|_| false
}
/// The media panel's commands ([`commands::MEDIA`]). /// The media panel's commands ([`commands::MEDIA`]).
fn media_handler() -> impl Fn(tauri::ipc::Invoke<Wry>) -> bool + Send + Sync + 'static { fn media_handler() -> impl Fn(tauri::ipc::Invoke<Wry>) -> bool + Send + Sync + 'static {
crate::media_commands::handler() crate::media_commands::handler()
@ -1152,6 +1182,7 @@ impl App {
let host_handler = host_handler(); let host_handler = host_handler();
let store_handler = store_handler(); let store_handler = store_handler();
let media_handler = media_handler(); let media_handler = media_handler();
let fonts_handler = fonts_handler();
let builder = tauri::Builder::<Wry>::default() let builder = tauri::Builder::<Wry>::default()
// Must be first: a second launch focuses the running window instead of fighting over // Must be first: a second launch focuses the running window instead of fighting over
// the app's data and WebView profile (and hands over its link). // the app's data and WebView profile (and hands over its link).
@ -1232,6 +1263,8 @@ impl App {
#[cfg(feature = "net")] #[cfg(feature = "net")]
crate::store_commands::init(&handle, &config); crate::store_commands::init(&handle, &config);
crate::media_commands::init(&handle); crate::media_commands::init(&handle);
#[cfg(feature = "net")]
crate::fonts_commands::init(&handle);
// Safe mode for this run: the launch argument, or Shift held while starting (Windows). // Safe mode for this run: the launch argument, or Shift held while starting (Windows).
crate::safe::set(crate::safe::at_launch(std::env::args(), host::shift_held())); crate::safe::set(crate::safe::at_launch(std::env::args(), host::shift_held()));
// Focus and layering diagnostics for this run: `--diag` or WAYBETTER_DIAG=1 (app log folder, diag.log). // Focus and layering diagnostics for this run: `--diag` or WAYBETTER_DIAG=1 (app log folder, diag.log).
@ -1250,6 +1283,8 @@ impl App {
store_handler(invoke) store_handler(invoke)
} else if commands::MEDIA.contains(&invoke.message.command()) { } else if commands::MEDIA.contains(&invoke.message.command()) {
media_handler(invoke) media_handler(invoke)
} else if commands::GFONTS.contains(&invoke.message.command()) {
fonts_handler(invoke)
} else if commands::is_host(invoke.message.command()) { } else if commands::is_host(invoke.message.command()) {
host_handler(invoke) host_handler(invoke)
} else { } else {

View file

@ -82,9 +82,13 @@ pub const MEDIA: &[&str] = &[
"clip_remove", "clip_remove",
]; ];
/// Shell-only, optional: Google Fonts (what is installed, install, remove, the faces of a family and
/// a preview), served when the app sets `AppConfig::fonts`; needs the `net` feature.
pub const GFONTS: &[&str] = &["gfonts_installed", "gfonts_install", "gfonts_remove", "gfonts_faces", "gfonts_preview"];
/// Whether `command` is one of the host's (dispatched to the host, never to the app's handler). /// 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 { pub fn is_host(command: &str) -> bool {
SHELL.contains(&command) || TAB.contains(&command) || SITE.contains(&command) || STORE.contains(&command) || MEDIA.contains(&command) SHELL.contains(&command) || TAB.contains(&command) || SITE.contains(&command) || STORE.contains(&command) || MEDIA.contains(&command) || GFONTS.contains(&command)
} }
/// The capability permission tauri-build generates for `command` (`tabs_sync` → `allow-tabs-sync`). /// The capability permission tauri-build generates for `command` (`tabs_sync` → `allow-tabs-sync`).
@ -99,8 +103,8 @@ pub fn permission(command: &str) -> String {
/// tauri_build::AppManifest::new().commands(waybetter_host::commands::manifest(&["draft_list"])) /// tauri_build::AppManifest::new().commands(waybetter_host::commands::manifest(&["draft_list"]))
/// ``` /// ```
pub fn manifest(app: &[&'static str]) -> &'static [&'static str] { 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()); let mut all: Vec<&'static str> = Vec::with_capacity(app.len() + SHELL.len() + TAB.len() + SITE.len() + STORE.len() + MEDIA.len() + GFONTS.len());
for command in app.iter().chain(SHELL).chain(TAB).chain(SITE).chain(STORE).chain(MEDIA) { for command in app.iter().chain(SHELL).chain(TAB).chain(SITE).chain(STORE).chain(MEDIA).chain(GFONTS) {
if !all.contains(command) { if !all.contains(command) {
all.push(command); all.push(command);
} }
@ -117,7 +121,7 @@ mod tests {
let all = manifest(&["draft_list", "tabs_sync"]); let all = manifest(&["draft_list", "tabs_sync"]);
assert_eq!(all[0], "draft_list"); assert_eq!(all[0], "draft_list");
assert_eq!(all.iter().filter(|c| **c == "tabs_sync").count(), 1); assert_eq!(all.iter().filter(|c| **c == "tabs_sync").count(), 1);
for command in SHELL.iter().chain(TAB).chain(SITE).chain(STORE).chain(MEDIA) { for command in SHELL.iter().chain(TAB).chain(SITE).chain(STORE).chain(MEDIA).chain(GFONTS) {
assert!(all.contains(command), "{command}"); assert!(all.contains(command), "{command}");
assert!(is_host(command)); assert!(is_host(command));
} }
@ -127,7 +131,7 @@ mod tests {
#[test] #[test]
fn shell_and_tab_commands_do_not_overlap() { 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))); assert!(!SHELL.iter().any(|command| TAB.contains(command) || SITE.contains(command) || STORE.contains(command) || MEDIA.contains(command) || GFONTS.contains(command)));
assert!(!TAB.iter().any(|command| SITE.contains(command))); assert!(!TAB.iter().any(|command| SITE.contains(command)));
} }
} }

View file

@ -0,0 +1,66 @@
//! Google Fonts, native side, for every app that sets [`crate::app::AppConfig::fonts`]: the
//! shell-only `gfonts_*` commands ([`crate::commands::GFONTS`]) over [`crate::fonts`]'s downloads
//! and cache (`fonts.googleapis.com` and `fonts.gstatic.com` only, app-data `fonts/`). Site tabs
//! never request fonts and gain no command: native code hands them cached bytes
//! ([`crate::fonts::page_script_for`]).
//!
//! When a font is installed or removed the app's [`crate::app::SiteHooks::fonts_changed`] runs, so
//! it can give every site tab its theme (and the font bytes) again.
use crate::fonts::{self, InstalledFont};
use crate::gfonts::Weights;
use crate::host::shell_only;
use tauri::{AppHandle, Manager, Webview};
/// Opens the font cache in app data. Does nothing unless the app set [`crate::app::AppConfig::fonts`].
pub(crate) fn init(app: &AppHandle) {
if !crate::app::config(app).fonts {
return;
}
if let Ok(dir) = app.path().app_data_dir() {
fonts::init(&dir);
}
}
fn changed(app: &AppHandle) {
crate::app::hooks(app).fonts_changed(app);
}
#[tauri::command]
pub(crate) async fn gfonts_installed(webview: Webview) -> Result<Vec<InstalledFont>, String> {
shell_only(&webview)?;
fonts::installed()
}
#[tauri::command]
pub(crate) async fn gfonts_install(webview: Webview, app: AppHandle, family: String, weights: Weights) -> Result<InstalledFont, String> {
shell_only(&webview)?;
let installed = fonts::install(family, weights).await?;
changed(&app);
Ok(installed)
}
#[tauri::command]
pub(crate) async fn gfonts_remove(webview: Webview, app: AppHandle, family: String) -> Result<(), String> {
shell_only(&webview)?;
fonts::remove(&family)?;
changed(&app);
Ok(())
}
#[tauri::command]
pub(crate) async fn gfonts_faces(webview: Webview, family: String) -> Result<serde_json::Value, String> {
shell_only(&webview)?;
fonts::faces(&family)
}
#[tauri::command]
pub(crate) async fn gfonts_preview(webview: Webview, family: String, weights: Weights, text: String) -> Result<Vec<serde_json::Value>, String> {
shell_only(&webview)?;
fonts::preview(family, weights, text).await
}
/// The fonts commands ([`crate::commands::GFONTS`]).
pub(crate) fn handler() -> impl Fn(tauri::ipc::Invoke<tauri::Wry>) -> bool + Send + Sync + 'static {
tauri::generate_handler![gfonts_installed, gfonts_install, gfonts_remove, gfonts_faces, gfonts_preview]
}

View file

@ -85,6 +85,8 @@ pub mod app;
mod store_commands; mod store_commands;
#[cfg(feature = "app")] #[cfg(feature = "app")]
pub mod media_commands; pub mod media_commands;
#[cfg(all(feature = "app", feature = "net"))]
mod fonts_commands;
#[cfg(all(feature = "app", debug_assertions))] #[cfg(all(feature = "app", debug_assertions))]
pub mod devloop; pub mod devloop;
#[cfg(feature = "tauri")] #[cfg(feature = "tauri")]