diff --git a/Cargo.lock b/Cargo.lock
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -861,6 +861,29 @@ name = "craft-docpath"
 version = "0.1.0"
 source = "git+https://github.com/storytold/craft-libs?rev=82a24b006eefa036b43c09a67fb9a3fbd17ffe63#82a24b006eefa036b43c09a67fb9a3fbd17ffe63"
 
+[[package]]
+name = "craft-menu"
+version = "0.1.0"
+source = "git+https://github.com/storytold/craft-libs?rev=2bff0abbfc6046a700128c2710d78a58c6c53c39#2bff0abbfc6046a700128c2710d78a58c6c53c39"
+dependencies = [
+ "craft-quit",
+ "egui",
+ "muda",
+ "objc2 0.6.5",
+ "objc2-app-kit 0.3.2",
+]
+
+[[package]]
+name = "craft-quit"
+version = "0.1.0"
+source = "git+https://github.com/storytold/craft-libs?rev=2bff0abbfc6046a700128c2710d78a58c6c53c39#2bff0abbfc6046a700128c2710d78a58c6c53c39"
+dependencies = [
+ "dispatch2",
+ "egui",
+ "objc2 0.6.5",
+ "objc2-app-kit 0.3.2",
+]
+
 [[package]]
 name = "crc32fast"
 version = "1.5.2"
@@ -870,6 +893,15 @@ dependencies = [
  "cfg-if",
 ]
 
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1"
+dependencies = [
+ "crossbeam-utils",
+]
+
 [[package]]
 name = "crossbeam-deque"
 version = "0.8.8"
@@ -1416,6 +1448,7 @@ name = "figcraft"
 version = "0.7.0"
 dependencies = [
  "arboard",
+ "craft-menu",
  "eframe",
  "egui",
  "env_logger",
@@ -1702,6 +1735,7 @@ name = "figcraft-ui-egui"
 version = "0.7.0"
 dependencies = [
  "base64 0.22.1",
+ "craft-menu",
  "egui",
  "egui_extras",
  "figcraft-color",
@@ -2540,6 +2574,16 @@ dependencies = [
  "wasm-bindgen",
 ]
 
+[[package]]
+name = "keyboard-types"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fbe853b403ae61a04233030ae8a79d94975281ed9770a1f9e246732b534b28d"
+dependencies = [
+ "bitflags 2.13.2",
+ "serde",
+]
+
 [[package]]
 name = "khronos-egl"
 version = "6.0.0"
@@ -2750,6 +2794,24 @@ dependencies = [
  "pxfm",
 ]
 
+[[package]]
+name = "muda"
+version = "0.21.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4340e89c178916e62879c5c36b7f699019369be413e496178a6983ce91b8bc03"
+dependencies = [
+ "crossbeam-channel",
+ "dpi",
+ "keyboard-types",
+ "objc2 0.6.5",
+ "objc2-app-kit 0.3.2",
+ "objc2-core-foundation",
+ "objc2-foundation 0.3.2",
+ "png",
+ "thiserror 2.0.21",
+ "windows-sys 0.60.2",
+]
+
 [[package]]
 name = "naga"
 version = "30.0.1"
diff --git a/Cargo.toml b/Cargo.toml
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -34,6 +34,8 @@ figcraft-plugin = { path = "crates/plugin" }
 
 # Which paths stored in a document may be followed (craft-libs).
 craft-docpath = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "82a24b006eefa036b43c09a67fb9a3fbd17ffe63" }
+# The macOS menu bar (craft-libs; at storytold/craft-libs#66 until it lands).
+craft-menu = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "2bff0abbfc6046a700128c2710d78a58c6c53c39" }
 
 # foundation
 kurbo = { version = "0.13", features = ["serde"] }
diff --git a/apps/figcraft/Cargo.toml b/apps/figcraft/Cargo.toml
--- a/apps/figcraft/Cargo.toml
+++ b/apps/figcraft/Cargo.toml
@@ -37,6 +37,8 @@ windows = { version = "0.62", default-features = false, features = ["UI_ViewMana
 # unsafe, which the workspace forbids). Pinned exactly: review the diff before bumping.
 [target.'cfg(target_os = "macos")'.dependencies]
 fmv-macos-events = "=0.1.0"
+# The main menu in the macOS menu bar.
+craft-menu = { workspace = true }
 
 [dev-dependencies]
 wat = { workspace = true }
diff --git a/apps/figcraft/src/mac_menu.rs b/apps/figcraft/src/mac_menu.rs
new file mode 100644
--- /dev/null
+++ b/apps/figcraft/src/mac_menu.rs
@@ -0,0 +1,64 @@
+//! The main menu in the macOS menu bar (craft-menu): installed with the window, synced each frame,
+//! acting on the window in front.
+
+use craft_menu::{Layout, LayoutOptions, NativeMenu};
+use egui::ViewportId;
+use figcraft_ui_egui::FigApp;
+use figcraft_ui_egui::native_menu;
+use figcraft_ui_egui::windows::with_app;
+
+/// The Mac layout of the main menu for the window in front, with the labels the layout writes
+/// translated.
+fn layout(fig: &mut FigApp, windows: bool) -> Layout {
+    let label = |english: &'static str| native_menu::mac_label(english);
+    craft_menu::mac_layout(&native_menu::menu_bar_in_front(fig, windows), &LayoutOptions { label: &label, ..LayoutOptions::new("FigCraft") })
+}
+
+/// Put the main menu in the menu bar. `None` when AppKit refused it: the in-window menu stays.
+pub fn install(ctx: &egui::Context) -> Option<NativeMenu> {
+    let bar = with_app(|fig| layout(fig, !ctx.embed_viewports()).bar)?;
+    craft_menu::mac::install(ctx, &bar)
+}
+
+/// Before a window's frame: the menu bar's key equivalents go into the window in front's input
+/// only. In the main window's input while another window is in front, a paste the menu bar asked
+/// for goes on to that window.
+pub fn raw_input(menu: &mut NativeMenu, ctx: &egui::Context, raw: &mut egui::RawInput) {
+    let front = with_app(|fig| native_menu::front(fig)).unwrap_or(ViewportId::ROOT);
+    if raw.viewport_id == front {
+        menu.raw_input(raw);
+    } else if raw.viewport_id == ViewportId::ROOT {
+        let pastes = native_menu::take_pastes(&mut raw.events);
+        if !pastes.is_empty() {
+            with_app(|fig| native_menu::send_keys(fig, ctx, pastes));
+        }
+    }
+}
+
+/// One frame (the main window's): hand the window in front the key equivalents waiting for it, run
+/// the items chosen since the last frame there, then bring the menus up to date (laid out again
+/// only after input, a menu event or another window coming to the front).
+pub fn frame(menu: &mut NativeMenu, ctx: &egui::Context) {
+    let front = with_app(|fig| native_menu::front(fig)).unwrap_or(ViewportId::ROOT);
+    if front != ViewportId::ROOT {
+        // A menu event wakes only the main window: the window in front may not draw until its next input.
+        let mut keys = egui::RawInput { viewport_id: front, ..Default::default() };
+        menu.raw_input(&mut keys);
+        if !keys.events.is_empty() {
+            with_app(|fig| native_menu::send_keys(fig, ctx, keys.events));
+        }
+    }
+    for id in menu.take_clicks(ctx) {
+        with_app(|fig| native_menu::run_in_front(fig, &id));
+    }
+    let input = if craft_menu::had_input(ctx) { ctx.cumulative_frame_nr() } else { 0 };
+    let state = input ^ front.0.value();
+    let windows = !ctx.embed_viewports();
+    let clashes = menu.sync_with(state, || {
+        with_app(|fig| layout(fig, windows)).unwrap_or_else(|| Layout { bar: craft_menu::MenuBar { menus: Vec::new() }, clashes: Vec::new() })
+    });
+    for c in clashes {
+        log::info!("menu: {} is {} on macOS and {} in FigCraft; {}", c.shortcut, c.system, c.command, c.resolution);
+    }
+    menu.set_text_editing(ctx.egui_wants_keyboard_input());
+}
diff --git a/apps/figcraft/src/main.rs b/apps/figcraft/src/main.rs
--- a/apps/figcraft/src/main.rs
+++ b/apps/figcraft/src/main.rs
@@ -22,6 +22,8 @@ mod gamepad;
 mod graphics;
 #[cfg(target_os = "linux")]
 mod linux_window;
+#[cfg(target_os = "macos")]
+mod mac_menu;
 mod recovery;
 mod services;
 mod startup;
@@ -60,6 +62,9 @@ struct App {
     /// Documents and quit requests from Finder, the Dock and Open With.
     #[cfg(target_os = "macos")]
     apple_events: fmv_macos_events::Inbox,
+    /// The main menu in the menu bar (`None` when AppKit refused it).
+    #[cfg(target_os = "macos")]
+    native_menu: Option<craft_menu::NativeMenu>,
 }
 
 impl eframe::App for App {
@@ -87,6 +92,10 @@ impl eframe::App for App {
             ctx.send_viewport_cmd(egui::ViewportCommand::Title(title.clone()));
             self.title = title;
         }
+        #[cfg(target_os = "macos")]
+        if let Some(menu) = &mut self.native_menu {
+            mac_menu::frame(menu, ctx);
+        }
         let now = ctx.input(|i| i.time);
         if let Some(wait) = with_app(|fig| recovery::lock(&self.recovery).as_mut().and_then(|r| r.tick(&fig.session, now))).flatten() {
             ctx.request_repaint_after(std::time::Duration::from_secs_f64(wait));
@@ -94,6 +103,12 @@ impl eframe::App for App {
     }
 
     fn raw_input_hook(&mut self, _ctx: &egui::Context, raw_input: &mut egui::RawInput) {
+        // The menu bar's key equivalents, as the key presses egui would have had without it (for
+        // the window in front).
+        #[cfg(target_os = "macos")]
+        if let Some(menu) = &mut self.native_menu {
+            mac_menu::raw_input(menu, _ctx, raw_input);
+        }
         // Pointer and key events sent over the control channel (`ui.input`), for the window
         // about to draw.
         if let Some(events) = with_app(|fig| figcraft_ui_egui::windows::take_input(fig, raw_input.viewport_id)) {
@@ -251,6 +266,9 @@ fn main() -> eframe::Result {
                 recovery,
                 #[cfg(target_os = "macos")]
                 apple_events: apple_events.connect(&_cc.egui_ctx),
+                // In the creator, on the main thread, so winit's default menu doesn't stay up.
+                #[cfg(target_os = "macos")]
+                native_menu: mac_menu::install(&_cc.egui_ctx),
             }))
         }),
     );
diff --git a/crates/ui-egui/Cargo.toml b/crates/ui-egui/Cargo.toml
--- a/crates/ui-egui/Cargo.toml
+++ b/crates/ui-egui/Cargo.toml
@@ -9,6 +9,7 @@ description = "FigCraft egui frontend (the Figma-style UI: canvas, toolbar, laye
 workspace = true
 
 [dependencies]
+craft-menu = { workspace = true }
 figcraft-geom = { workspace = true }
 figcraft-color = { workspace = true }
 figcraft-doc = { workspace = true }
diff --git a/crates/ui-egui/locales/cs.toml b/crates/ui-egui/locales/cs.toml
--- a/crates/ui-egui/locales/cs.toml
+++ b/crates/ui-egui/locales/cs.toml
@@ -22,6 +22,7 @@ status = "draft — needs native review"
 "About" = "O aplikaci"
 "About FigCraft" = "O aplikaci FigCraft"
 "About this server" = "O tomto serveru"
+"About {app}" = "O aplikaci {app}"
 "Action" = "Akce"
 "Actions" = "Akce"
 "Actions for {name}" = "Akce pro {name}"
@@ -178,6 +179,7 @@ status = "draft — needs native review"
 "Branch {name}" = "Větev {name}"
 "Branches" = "Větve"
 "Bound to {name}. Click to detach the variable." = "Navázáno na {name}. Kliknutím odpojíte proměnnou."
+"Bring All to Front" = "Přenést vše dopředu"
 "Bring forward" = "Přenést o úroveň výš"
 "Bring the main file's changes into the branch" = "Přenést změny hlavního souboru do větve"
 "Bring to front" = "Přenést do popředí"
@@ -195,6 +197,7 @@ status = "draft — needs native review"
 "Can view" = "Může prohlížet"
 "Checking the server…" = "Kontrola serveru…"
 "Clear from this list" = "Vymazat z tohoto seznamu"
+"Close Window" = "Zavřít okno"
 "Code or invite link" = "Kód nebo odkaz s pozvánkou"
 "Color profile: {name}" = "Barevný profil: {name}"
 "Colour and text styles, Light and Dark variables, buttons, inputs and cards" = "Barevné a textové styly, proměnné pro světlý a tmavý režim, tlačítka, vstupní pole a karty"
@@ -567,11 +570,13 @@ status = "draft — needs native review"
 "Help page" = "Stránka nápovědy"
 "Here: {names}" = "Zde: {names}"
 "Hidden" = "Skryté"
+"Hide Others" = "Skrýt ostatní"
 "Hide contents" = "Skrýt obsah"
 "Hide from publishing" = "Skrýt při publikování"
 "Hide section contents" = "Skrýt obsah sekce"
 "Hide these tips (Getting started in the Help menu shows them again)" = "Skrýt tyto tipy (znovu je zobrazíte volbou Začínáme v nabídce Nápověda)"
 "Hide when publishing" = "Skrýt při publikování"
+"Hide {app}" = "Skrýt {app}"
 "Highlight layers on hover" = "Zvýraznit vrstvy pod ukazatelem"
 "Highlights" = "Světla"
 "Hold Alt" = "Podržte Alt"
@@ -1021,6 +1026,7 @@ status = "draft — needs native review"
 "Push" = "Vytlačení"
 "Push changes to main component" = "Přenést změny do hlavní komponenty"
 "Put text on path" = "Umístit text na cestu"
+"Quit {app}" = "Ukončit {app}"
 "R" = "R"
 "Radial" = "Kruhový"
 "Radius" = "Poloměr"
@@ -1183,11 +1189,13 @@ status = "draft — needs native review"
 "Server address (ws://host:47900/)" = "Adresa serveru (ws://host:47900/)"
 "Server address" = "Adresa serveru"
 "Server for online sharing" = "Server pro online sdílení"
+"Services" = "Služby"
 "Set as default" = "Nastavit jako výchozí"
 "Set variable" = "Nastavit proměnnou"
 "Set variable mode" = "Nastavit režim proměnných"
 "Set variable {name}" = "Nastavit proměnnou {name}"
 "Set {name} to {value}" = "Nastavit {name} na {value}"
+"Settings…" = "Nastavení…"
 "Shadows" = "Stíny"
 "Shape" = "Tvar"
 "Shape builder" = "Tvorba tvarů"
@@ -1204,6 +1212,7 @@ status = "draft — needs native review"
 "Sharing online · {people}" = "Sdílení online · {people}"
 "Sharing stopped: the file stays on the server, open only to you" = "Sdílení zastaveno: soubor zůstává na serveru, přístupný pouze vám"
 "Show" = "Zobrazit"
+"Show All" = "Zobrazit vše"
 "Show all templates" = "Zobrazit všechny šablony"
 "Show as grid" = "Zobrazit jako mřížku"
 "Show as list" = "Zobrazit jako seznam"
@@ -1473,6 +1482,7 @@ status = "draft — needs native review"
 "Width" = "Šířka"
 "Width point" = "Bod šířky"
 "Width profile" = "Profil šířky"
+"Window" = "Okno"
 "Wireframe kit" = "Sada pro wireframy"
 "With the layers' export settings" = "S nastavením exportu vrstev"
 "Wrap" = "Zalamovat"
diff --git a/crates/ui-egui/locales/de.toml b/crates/ui-egui/locales/de.toml
--- a/crates/ui-egui/locales/de.toml
+++ b/crates/ui-egui/locales/de.toml
@@ -23,6 +23,7 @@ status = "draft — needs native review"
 "About" = "Über"
 "About FigCraft" = "Über FigCraft"
 "About this server" = "Über diesen Server"
+"About {app}" = "Über {app}"
 "Action" = "Aktion"
 "Actions" = "Aktionen"
 "Actions for {name}" = "Aktionen für {name}"
@@ -174,6 +175,7 @@ status = "draft — needs native review"
 "Branch updated from main" = "Branch aus der Hauptdatei aktualisiert"
 "Branch {name}" = "Branch {name}"
 "Branches" = "Branches"
+"Bring All to Front" = "Alle nach vorne bringen"
 "Bring forward" = "Eine Ebene nach vorne"
 "Bring the main file's changes into the branch" = "Übernimmt die Änderungen der Hauptdatei in den Branch"
 "Bring to front" = "In den Vordergrund"
@@ -191,6 +193,7 @@ status = "draft — needs native review"
 "Can view" = "Kann ansehen"
 "Checking the server…" = "Server wird geprüft…"
 "Clear from this list" = "Aus dieser Liste entfernen"
+"Close Window" = "Fenster schließen"
 "Code or invite link" = "Code oder Einladungslink"
 "Color profile: {name}" = "Farbprofil: {name}"
 "CSS custom properties…" = "Benutzerdefinierte CSS-Eigenschaften…"
@@ -555,9 +558,11 @@ status = "draft — needs native review"
 "Help page" = "Hilfeseite"
 "Here: {names}" = "Hier: {names}"
 "Hidden" = "Ausgeblendet"
+"Hide Others" = "Andere ausblenden"
 "Hide contents" = "Inhalt ausblenden"
 "Hide from publishing" = "Nicht veröffentlichen"
 "Hide section contents" = "Abschnittsinhalt ausblenden"
+"Hide {app}" = "{app} ausblenden"
 "Highlight layers on hover" = "Ebenen beim Überfahren hervorheben"
 "Highlights" = "Lichter"
 "Hold Alt" = "Alt gedrückt halten"
@@ -987,6 +992,7 @@ status = "draft — needs native review"
 "Push" = "Schieben"
 "Push changes to main component" = "Änderungen an Hauptkomponente übertragen"
 "Put text on path" = "Text auf Pfad setzen"
+"Quit {app}" = "{app} beenden"
 "R" = "R"
 "Radial" = "Radial"
 "Radius" = "Radius"
@@ -1146,11 +1152,13 @@ status = "draft — needs native review"
 "Server address" = "Serveradresse"
 "Server address (ws://host:47900/)" = "Serveradresse (ws://host:47900/)"
 "Server for online sharing" = "Server für das Online-Teilen"
+"Services" = "Dienste"
 "Set as default" = "Als Standard festlegen"
 "Set variable" = "Variable festlegen"
 "Set variable mode" = "Variablenmodus festlegen"
 "Set variable {name}" = "Variable {name} festlegen"
 "Set {name} to {value}" = "{name} auf {value} setzen"
+"Settings…" = "Einstellungen…"
 "Shadows" = "Tiefen"
 "Shape" = "Form"
 "Shape builder" = "Formerstellung"
@@ -1166,6 +1174,7 @@ status = "draft — needs native review"
 "Sharing online · {people}" = "Wird online geteilt · {people}"
 "Sharing stopped: the file stays on the server, open only to you" = "Teilen beendet: Die Datei bleibt auf dem Server, nur für Sie zugänglich"
 "Show" = "Anzeigen"
+"Show All" = "Alle einblenden"
 "Show as grid" = "Als Raster anzeigen"
 "Show as list" = "Als Liste anzeigen"
 "Show autosaves" = "Automatische Speicherungen anzeigen"
@@ -1425,6 +1434,7 @@ status = "draft — needs native review"
 "Width" = "Breite"
 "Width point" = "Breitenpunkt"
 "Width profile" = "Breitenprofil"
+"Window" = "Fenster"
 "With the layers' export settings" = "Mit den Exporteinstellungen der Ebenen"
 "Wrap" = "Umbrechen"
 "Wrap in new section" = "In neuen Abschnitt einschließen"
diff --git a/crates/ui-egui/locales/en.toml b/crates/ui-egui/locales/en.toml
--- a/crates/ui-egui/locales/en.toml
+++ b/crates/ui-egui/locales/en.toml
@@ -25,6 +25,7 @@ name = "English"
 "About" = "About"
 "About FigCraft" = "About FigCraft"
 "About this server" = "About this server"
+"About {app}" = "About {app}"
 "Action" = "Action"
 "Actions" = "Actions"
 "Actions for {name}" = "Actions for {name}"
@@ -183,6 +184,7 @@ name = "English"
 "Branch {name}" = "Branch {name}"
 "Branches" = "Branches"
 "Bound to {name}. Click to detach the variable." = "Bound to {name}. Click to detach the variable."
+"Bring All to Front" = "Bring All to Front"
 "Bring forward" = "Bring forward"
 "Bring the main file's changes into the branch" = "Bring the main file's changes into the branch"
 "Bring to front" = "Bring to front"
@@ -201,6 +203,7 @@ name = "English"
 "Checking the fonts installed on this computer…" = "Checking the fonts installed on this computer…"
 "Checking the server…" = "Checking the server…"
 "Clear from this list" = "Clear from this list"
+"Close Window" = "Close Window"
 "Code or invite link" = "Code or invite link"
 "Color profile: {name}" = "Color profile: {name}"
 "Colour and text styles, Light and Dark variables, buttons, inputs and cards" = "Colour and text styles, Light and Dark variables, buttons, inputs and cards"
@@ -581,11 +584,13 @@ name = "English"
 "Help page" = "Help page"
 "Here: {names}" = "Here: {names}"
 "Hidden" = "Hidden"
+"Hide Others" = "Hide Others"
 "Hide contents" = "Hide contents"
 "Hide from publishing" = "Hide from publishing"
 "Hide section contents" = "Hide section contents"
 "Hide these tips (Getting started in the Help menu shows them again)" = "Hide these tips (Getting started in the Help menu shows them again)"
 "Hide when publishing" = "Hide when publishing"
+"Hide {app}" = "Hide {app}"
 "Highlight layers on hover" = "Highlight layers on hover"
 "Highlights" = "Highlights"
 "Hold Alt" = "Hold Alt"
@@ -1044,6 +1049,7 @@ name = "English"
 "Push" = "Push"
 "Push changes to main component" = "Push changes to main component"
 "Put text on path" = "Put text on path"
+"Quit {app}" = "Quit {app}"
 "R" = "R"
 "Radial" = "Radial"
 "Radius" = "Radius"
@@ -1210,11 +1216,13 @@ name = "English"
 "Server address (ws://host:47900/)" = "Server address (ws://host:47900/)"
 "Server address" = "Server address"
 "Server for online sharing" = "Server for online sharing"
+"Services" = "Services"
 "Set as default" = "Set as default"
 "Set variable" = "Set variable"
 "Set variable mode" = "Set variable mode"
 "Set variable {name}" = "Set variable {name}"
 "Set {name} to {value}" = "Set {name} to {value}"
+"Settings…" = "Settings…"
 "Shadows" = "Shadows"
 "Shape" = "Shape"
 "Shape builder" = "Shape builder"
@@ -1231,6 +1239,7 @@ name = "English"
 "Sharing online · {people}" = "Sharing online · {people}"
 "Sharing stopped: the file stays on the server, open only to you" = "Sharing stopped: the file stays on the server, open only to you"
 "Show" = "Show"
+"Show All" = "Show All"
 "Show all templates" = "Show all templates"
 "Show as grid" = "Show as grid"
 "Show as list" = "Show as list"
@@ -1509,6 +1518,7 @@ name = "English"
 "Width" = "Width"
 "Width point" = "Width point"
 "Width profile" = "Width profile"
+"Window" = "Window"
 "Wireframe kit" = "Wireframe kit"
 "With the layers' export settings" = "With the layers' export settings"
 "Wrap" = "Wrap"
diff --git a/crates/ui-egui/locales/es.toml b/crates/ui-egui/locales/es.toml
--- a/crates/ui-egui/locales/es.toml
+++ b/crates/ui-egui/locales/es.toml
@@ -8,6 +8,9 @@ name = "Español"
 status = "draft — needs native review"
 
 [ui]
+"About {app}" = "Acerca de {app}"
+"Bring All to Front" = "Traer todo al frente"
+"Close Window" = "Cerrar ventana"
 "Compatibility notes" = "Notas de compatibilidad"
 "Couldn't download the variables" = "No se pudieron descargar las variables"
 
@@ -582,11 +585,13 @@ status = "draft — needs native review"
 "Help page" = "Página de ayuda"
 "Here: {names}" = "Aquí: {names}"
 "Hidden" = "Oculta"
+"Hide Others" = "Ocultar otros"
 "Hide contents" = "Ocultar contenido"
 "Hide from publishing" = "Ocultar al publicar"
 "Hide section contents" = "Ocultar contenido de la sección"
 "Hide these tips (Getting started in the Help menu shows them again)" = "Ocultar estos consejos (Primeros pasos en el menú Ayuda los vuelve a mostrar)"
 "Hide when publishing" = "Ocultar al publicar"
+"Hide {app}" = "Ocultar {app}"
 "Highlight layers on hover" = "Resaltar capas al pasar el cursor"
 "Highlights" = "Luces"
 "Hold Alt" = "Mantén Alt"
@@ -1045,6 +1050,7 @@ status = "draft — needs native review"
 "Push" = "Empujar"
 "Push changes to main component" = "Enviar cambios al componente principal"
 "Put text on path" = "Poner texto en trazado"
+"Quit {app}" = "Salir de {app}"
 "R" = "R"
 "Radial" = "Radial"
 "Radius" = "Radio"
@@ -1211,11 +1217,13 @@ status = "draft — needs native review"
 "Server address (ws://host:47900/)" = "Dirección del servidor (ws://host:47900/)"
 "Server address" = "Dirección del servidor"
 "Server for online sharing" = "Servidor para compartir en línea"
+"Services" = "Servicios"
 "Set as default" = "Establecer como predeterminado"
 "Set variable" = "Establecer variable"
 "Set variable mode" = "Establecer modo de variable"
 "Set variable {name}" = "Establecer variable {name}"
 "Set {name} to {value}" = "Establecer {name} en {value}"
+"Settings…" = "Ajustes…"
 "Shadows" = "Sombras"
 "Shape" = "Forma"
 "Shape builder" = "Generador de formas"
@@ -1232,6 +1240,7 @@ status = "draft — needs native review"
 "Sharing online · {people}" = "Compartiendo en línea · {people}"
 "Sharing stopped: the file stays on the server, open only to you" = "Se dejó de compartir: el archivo se queda en el servidor, abierto solo para ti"
 "Show" = "Mostrar"
+"Show All" = "Mostrar todo"
 "Show all templates" = "Mostrar todas las plantillas"
 "Show as grid" = "Mostrar como cuadrícula"
 "Show as list" = "Mostrar como lista"
@@ -1510,6 +1519,7 @@ status = "draft — needs native review"
 "Width" = "Ancho"
 "Width point" = "Punto de ancho"
 "Width profile" = "Perfil de ancho"
+"Window" = "Ventana"
 "Wireframe kit" = "Kit de wireframes"
 "With the layers' export settings" = "Con los ajustes de exportación de las capas"
 "Wrap" = "Ajustar"
diff --git a/crates/ui-egui/locales/fi.toml b/crates/ui-egui/locales/fi.toml
--- a/crates/ui-egui/locales/fi.toml
+++ b/crates/ui-egui/locales/fi.toml
@@ -27,6 +27,7 @@ status = "draft — needs native review"
 "About" = "Tietoja"
 "About FigCraft" = "Tietoja FigCraftista"
 "About this server" = "Tietoja tästä palvelimesta"
+"About {app}" = "Tietoja: {app}"
 "Action" = "Toiminto"
 "Actions" = "Toiminnot"
 "Actions for {name}" = "Kohteen {name} toiminnot"
@@ -185,6 +186,7 @@ status = "draft — needs native review"
 "Branch updated from main" = "Haara päivitetty päähaarasta"
 "Branch {name}" = "Haara {name}"
 "Branches" = "Haarat"
+"Bring All to Front" = "Tuo kaikki eteen"
 "Bring forward" = "Tuo eteenpäin"
 "Bring the main file's changes into the branch" = "Tuo päätiedoston muutokset haaraan"
 "Bring to front" = "Tuo etummaiseksi"
@@ -240,6 +242,7 @@ status = "draft — needs native review"
 "Clip content" = "Leikkaa sisältö rajoihin"
 "Clip to shape" = "Leikkaa muotoon"
 "Close" = "Sulje"
+"Close Window" = "Sulje ikkuna"
 "Close overlay" = "Sulje peittokuva"
 "Close preview" = "Sulje esikatselu"
 "Close tab" = "Sulje välilehti"
@@ -583,11 +586,13 @@ status = "draft — needs native review"
 "Help page" = "Ohjesivu"
 "Here: {names}" = "Paikalla: {names}"
 "Hidden" = "Kätketty"
+"Hide Others" = "Kätke muut"
 "Hide contents" = "Kätke sisältö"
 "Hide from publishing" = "Jätä julkaisematta"
 "Hide section contents" = "Kätke osion sisältö"
 "Hide these tips (Getting started in the Help menu shows them again)" = "Kätke nämä vinkit (Ohje-valikon Aloittaminen näyttää ne uudelleen)"
 "Hide when publishing" = "Kätke julkaistaessa"
+"Hide {app}" = "Kätke {app}"
 "Highlight layers on hover" = "Korosta tasot osoitettaessa"
 "Highlights" = "Huippuvalot"
 "Hold Alt" = "Pidä Alt painettuna"
@@ -1046,6 +1051,7 @@ status = "draft — needs native review"
 "Push" = "Työntö"
 "Push changes to main component" = "Siirrä muutokset pääkomponenttiin"
 "Put text on path" = "Sijoita teksti polulle"
+"Quit {app}" = "Lopeta {app}"
 "R" = "R"
 "Radial" = "Säteittäinen"
 "Radius" = "Säde"
@@ -1212,11 +1218,13 @@ status = "draft — needs native review"
 "Server address" = "Palvelimen osoite"
 "Server address (ws://host:47900/)" = "Palvelimen osoite (ws://host:47900/)"
 "Server for online sharing" = "Verkkojakamisen palvelin"
+"Services" = "Palvelut"
 "Set as default" = "Aseta oletukseksi"
 "Set variable" = "Aseta muuttuja"
 "Set variable mode" = "Aseta muuttujan tila"
 "Set variable {name}" = "Aseta muuttuja {name}"
 "Set {name} to {value}" = "Aseta {name} arvoon {value}"
+"Settings…" = "Asetukset…"
 "Shadows" = "Varjot"
 "Shape" = "Muoto"
 "Shape builder" = "Muodonrakennin"
@@ -1233,6 +1241,7 @@ status = "draft — needs native review"
 "Sharing online · {people}" = "Jaetaan verkossa · {people}"
 "Sharing stopped: the file stays on the server, open only to you" = "Jakaminen lopetettu: tiedosto jää palvelimelle vain sinun käyttöösi"
 "Show" = "Näytä"
+"Show All" = "Näytä kaikki"
 "Show all templates" = "Näytä kaikki mallit"
 "Show as grid" = "Näytä ruudukkona"
 "Show as list" = "Näytä luettelona"
@@ -1511,6 +1520,7 @@ status = "draft — needs native review"
 "Width" = "Leveys"
 "Width point" = "Leveyspiste"
 "Width profile" = "Leveysprofiili"
+"Window" = "Ikkuna"
 "Wireframe kit" = "Rautalankapaketti"
 "With the layers' export settings" = "Tasojen vientiasetuksilla"
 "Wrap" = "Rivitys"
diff --git a/crates/ui-egui/locales/fr.toml b/crates/ui-egui/locales/fr.toml
--- a/crates/ui-egui/locales/fr.toml
+++ b/crates/ui-egui/locales/fr.toml
@@ -26,6 +26,7 @@ status = "draft — needs native review"
 "About" = "À propos"
 "About FigCraft" = "À propos de FigCraft"
 "About this server" = "À propos de ce serveur"
+"About {app}" = "À propos de {app}"
 "Action" = "Action"
 "Actions" = "Actions"
 "Actions for {name}" = "Actions pour {name}"
@@ -183,6 +184,7 @@ status = "draft — needs native review"
 "Branch updated from main" = "Branche mise à jour depuis le fichier principal"
 "Branch {name}" = "Branche {name}"
 "Branches" = "Branches"
+"Bring All to Front" = "Tout ramener au premier plan"
 "Bring forward" = "Avancer"
 "Bring the main file's changes into the branch" = "Apporte dans la branche les modifications du fichier principal"
 "Bring to front" = "Mettre au premier plan"
@@ -200,6 +202,7 @@ status = "draft — needs native review"
 "Can view" = "Peut consulter"
 "Checking the server…" = "Vérification auprès du serveur…"
 "Clear from this list" = "Effacer de cette liste"
+"Close Window" = "Fermer la fenêtre"
 "Code or invite link" = "Code ou lien d’invitation"
 "Color profile: {name}" = "Profil colorimétrique : {name}"
 "CSS custom properties…" = "Propriétés personnalisées CSS…"
@@ -580,11 +583,13 @@ status = "draft — needs native review"
 "Help page" = "Page d’aide"
 "Here: {names}" = "Présents : {names}"
 "Hidden" = "Masqué"
+"Hide Others" = "Masquer les autres"
 "Hide contents" = "Masquer le contenu"
 "Hide from publishing" = "Exclure de la publication"
 "Hide section contents" = "Masquer le contenu de la section"
 "Hide these tips (Getting started in the Help menu shows them again)" = "Masquer ces astuces (Premiers pas dans le menu Aide les affiche de nouveau)"
 "Hide when publishing" = "Masquer à la publication"
+"Hide {app}" = "Masquer {app}"
 "Highlight layers on hover" = "Mettre en évidence les calques au survol"
 "Highlights" = "Hautes lumières"
 "Hold Alt" = "Maintenir Alt"
@@ -1041,6 +1046,7 @@ status = "draft — needs native review"
 "Push" = "Pousser"
 "Push changes to main component" = "Transmettre les modifications au composant principal"
 "Put text on path" = "Placer le texte sur un tracé"
+"Quit {app}" = "Quitter {app}"
 "R" = "R"
 "Radial" = "Radial"
 "Radius" = "Rayon"
@@ -1205,11 +1211,13 @@ status = "draft — needs native review"
 "Server address" = "Adresse du serveur"
 "Server address (ws://host:47900/)" = "Adresse du serveur (ws://host:47900/)"
 "Server for online sharing" = "Serveur pour le partage en ligne"
+"Services" = "Services"
 "Set as default" = "Définir par défaut"
 "Set variable" = "Définir la variable"
 "Set variable mode" = "Définir le mode de variable"
 "Set variable {name}" = "Définir la variable {name}"
 "Set {name} to {value}" = "Définir {name} sur {value}"
+"Settings…" = "Réglages…"
 "Shadows" = "Ombres"
 "Shape" = "Forme"
 "Shape builder" = "Générateur de formes"
@@ -1226,6 +1234,7 @@ status = "draft — needs native review"
 "Sharing online · {people}" = "Partage en ligne · {people}"
 "Sharing stopped: the file stays on the server, open only to you" = "Partage arrêté : le fichier reste sur le serveur, accessible à vous seul"
 "Show" = "Afficher"
+"Show All" = "Tout afficher"
 "Show all templates" = "Afficher tous les modèles"
 "Show as grid" = "Afficher en grille"
 "Show as list" = "Afficher en liste"
@@ -1502,6 +1511,7 @@ status = "draft — needs native review"
 "Width" = "Largeur"
 "Width point" = "Point de largeur"
 "Width profile" = "Profil de largeur"
+"Window" = "Fenêtre"
 "Wireframe kit" = "Kit de wireframes"
 "With the layers' export settings" = "Avec les réglages d’exportation des calques"
 "Wrap" = "Renvoi à la ligne"
diff --git a/crates/ui-egui/locales/id.toml b/crates/ui-egui/locales/id.toml
--- a/crates/ui-egui/locales/id.toml
+++ b/crates/ui-egui/locales/id.toml
@@ -26,6 +26,7 @@ status = "draft — needs native review"
 "About" = "Tentang"
 "About FigCraft" = "Tentang FigCraft"
 "About this server" = "Tentang server ini"
+"About {app}" = "Tentang {app}"
 "Action" = "Tindakan"
 "Actions" = "Tindakan"
 "Actions for {name}" = "Tindakan untuk {name}"
@@ -182,6 +183,7 @@ status = "draft — needs native review"
 "Branch {name}" = "Branch {name}"
 "Branches" = "Branch"
 "Bound to {name}. Click to detach the variable." = "Terikat ke {name}. Klik untuk melepas variabel."
+"Bring All to Front" = "Bawa semua ke depan"
 "Bring forward" = "Bawa maju"
 "Bring the main file's changes into the branch" = "Bawa perubahan file utama ke branch"
 "Bring to front" = "Bawa ke paling depan"
@@ -199,6 +201,7 @@ status = "draft — needs native review"
 "Can view" = "Dapat melihat"
 "Checking the server…" = "Memeriksa server…"
 "Clear from this list" = "Hapus dari daftar ini"
+"Close Window" = "Tutup jendela"
 "Code or invite link" = "Kode atau link undangan"
 "Color profile: {name}" = "Profil warna: {name}"
 "Colour and text styles, Light and Dark variables, buttons, inputs and cards" = "Gaya warna dan teks, variabel Light dan Dark, tombol, input, dan kartu"
@@ -571,11 +574,13 @@ status = "draft — needs native review"
 "Help page" = "Halaman bantuan"
 "Here: {names}" = "Di sini: {names}"
 "Hidden" = "Tersembunyi"
+"Hide Others" = "Sembunyikan lainnya"
 "Hide contents" = "Sembunyikan konten"
 "Hide from publishing" = "Sembunyikan dari publikasi"
 "Hide section contents" = "Sembunyikan konten section"
 "Hide these tips (Getting started in the Help menu shows them again)" = "Sembunyikan tips ini (tampilkan lagi lewat Memulai di menu Bantuan)"
 "Hide when publishing" = "Sembunyikan saat dipublikasikan"
+"Hide {app}" = "Sembunyikan {app}"
 "Highlight layers on hover" = "Sorot layer saat hover"
 "Highlights" = "Sorotan"
 "Hold Alt" = "Tahan Alt"
@@ -1025,6 +1030,7 @@ status = "draft — needs native review"
 "Push" = "Dorong"
 "Push changes to main component" = "Terapkan perubahan ke main component"
 "Put text on path" = "Tempatkan teks di path"
+"Quit {app}" = "Keluar dari {app}"
 "R" = "R"
 "Radial" = "Radial"
 "Radius" = "Radius"
@@ -1187,11 +1193,13 @@ status = "draft — needs native review"
 "Server address (ws://host:47900/)" = "Alamat server (ws://host:47900/)"
 "Server address" = "Alamat server"
 "Server for online sharing" = "Server untuk berbagi online"
+"Services" = "Layanan"
 "Set as default" = "Jadikan default"
 "Set variable" = "Atur variabel"
 "Set variable mode" = "Atur mode variabel"
 "Set variable {name}" = "Atur variabel {name}"
 "Set {name} to {value}" = "Atur {name} ke {value}"
+"Settings…" = "Pengaturan…"
 "Shadows" = "Bayangan"
 "Shape" = "Bentuk"
 "Shape builder" = "Shape builder"
@@ -1208,6 +1216,7 @@ status = "draft — needs native review"
 "Sharing online · {people}" = "Berbagi online · {people}"
 "Sharing stopped: the file stays on the server, open only to you" = "Berbagi dihentikan: file tetap di server, hanya dapat dibuka oleh Anda"
 "Show" = "Tampilkan"
+"Show All" = "Tampilkan semua"
 "Show all templates" = "Tampilkan semua templat"
 "Show as grid" = "Tampilkan sebagai grid"
 "Show as list" = "Tampilkan sebagai daftar"
@@ -1477,6 +1486,7 @@ status = "draft — needs native review"
 "Width" = "Lebar"
 "Width point" = "Titik lebar"
 "Width profile" = "Profil lebar"
+"Window" = "Jendela"
 "Wireframe kit" = "Kit wireframe"
 "With the layers' export settings" = "Dengan pengaturan ekspor layer"
 "Wrap" = "Wrap"
diff --git a/crates/ui-egui/locales/ja.toml b/crates/ui-egui/locales/ja.toml
--- a/crates/ui-egui/locales/ja.toml
+++ b/crates/ui-egui/locales/ja.toml
@@ -28,6 +28,7 @@ status = "draft — needs native review"
 "About" = "情報"
 "About FigCraft" = "FigCraft について"
 "About this server" = "このサーバーについて"
+"About {app}" = "{app} について"
 "Action" = "アクション"
 "Actions" = "アクション"
 "Actions for {name}" = "{name} の操作"
@@ -184,6 +185,7 @@ status = "draft — needs native review"
 "Branch updated from main" = "メインからブランチを更新済み"
 "Branch {name}" = "ブランチ {name}"
 "Branches" = "ブランチ"
+"Bring All to Front" = "すべてを手前に移動"
 "Bring forward" = "前面へ"
 "Bring the main file's changes into the branch" = "メインファイルの変更をブランチに取り込む"
 "Bring to front" = "最前面へ"
@@ -201,6 +203,7 @@ status = "draft — needs native review"
 "Can view" = "閲覧可"
 "Checking the server…" = "サーバーを確認中…"
 "Clear from this list" = "このリストから消去"
+"Close Window" = "ウインドウを閉じる"
 "Code or invite link" = "コードまたは招待リンク"
 "Color profile: {name}" = "カラープロファイル：{name}"
 "CSS custom properties…" = "CSS カスタムプロパティ…"
@@ -575,11 +578,13 @@ status = "draft — needs native review"
 "Help page" = "ヘルプページ"
 "Here: {names}" = "参加中：{names}"
 "Hidden" = "非表示"
+"Hide Others" = "ほかを隠す"
 "Hide contents" = "コンテンツを非表示"
 "Hide from publishing" = "公開対象から除外"
 "Hide section contents" = "セクションのコンテンツを非表示"
 "Hide these tips (Getting started in the Help menu shows them again)" = "ヒントを非表示（ヘルプメニューの「使い始めるには」で再表示）"
 "Hide when publishing" = "公開時に非表示"
+"Hide {app}" = "{app} を隠す"
 "Highlight layers on hover" = "ホバー時にレイヤーをハイライト"
 "Highlights" = "ハイライト"
 "Hold Alt" = "Alt を押したまま"
@@ -1030,6 +1035,7 @@ status = "draft — needs native review"
 "Push" = "プッシュ"
 "Push changes to main component" = "変更をメインコンポーネントにプッシュ"
 "Put text on path" = "テキストをパスに沿わせる"
+"Quit {app}" = "{app} を終了"
 "R" = "R"
 "Radial" = "放射状"
 "Radius" = "半径"
@@ -1194,11 +1200,13 @@ status = "draft — needs native review"
 "Server address" = "サーバーのアドレス"
 "Server address (ws://host:47900/)" = "サーバーのアドレス（ws://host:47900/）"
 "Server for online sharing" = "オンライン共有用のサーバー"
+"Services" = "サービス"
 "Set as default" = "デフォルトに設定"
 "Set variable" = "変数を設定"
 "Set variable mode" = "変数モードを設定"
 "Set variable {name}" = "変数 {name} を設定"
 "Set {name} to {value}" = "{name} を {value} に設定"
+"Settings…" = "設定…"
 "Shadows" = "シャドウ"
 "Shape" = "シェイプ"
 "Shape builder" = "シェイプビルダー"
@@ -1215,6 +1223,7 @@ status = "draft — needs native review"
 "Sharing online · {people}" = "オンラインで共有中 · {people}"
 "Sharing stopped: the file stays on the server, open only to you" = "共有を停止：ファイルはサーバーに残り、開けるのはあなただけ"
 "Show" = "表示"
+"Show All" = "すべてを表示"
 "Show all templates" = "すべてのテンプレートを表示"
 "Show as grid" = "グリッドで表示"
 "Show as list" = "リストで表示"
@@ -1487,6 +1496,7 @@ status = "draft — needs native review"
 "Width" = "幅"
 "Width point" = "幅のポイント"
 "Width profile" = "幅プロファイル"
+"Window" = "ウインドウ"
 "Wireframe kit" = "ワイヤーフレームキット"
 "With the layers' export settings" = "レイヤーの書き出し設定で"
 "Wrap" = "折り返し"
diff --git a/crates/ui-egui/locales/ko.toml b/crates/ui-egui/locales/ko.toml
--- a/crates/ui-egui/locales/ko.toml
+++ b/crates/ui-egui/locales/ko.toml
@@ -24,6 +24,7 @@ status = "draft — needs native review"
 "About" = "정보"
 "About FigCraft" = "FigCraft 정보"
 "About this server" = "이 서버 정보"
+"About {app}" = "{app} 정보"
 "Action" = "액션"
 "Actions" = "액션"
 "Actions for {name}" = "{name} 작업"
@@ -174,6 +175,7 @@ status = "draft — needs native review"
 "Branch updated from main" = "메인에서 브랜치 업데이트됨"
 "Branch {name}" = "브랜치 {name}"
 "Branches" = "브랜치"
+"Bring All to Front" = "모두 앞으로 가져오기"
 "Bring forward" = "앞으로 가져오기"
 "Bring the main file's changes into the branch" = "메인 파일의 변경 사항을 브랜치로 가져오기"
 "Bring to front" = "맨 앞으로 가져오기"
@@ -191,6 +193,7 @@ status = "draft — needs native review"
 "Can view" = "보기 가능"
 "Checking the server…" = "서버 확인 중…"
 "Clear from this list" = "이 목록에서 지우기"
+"Close Window" = "윈도우 닫기"
 "Code or invite link" = "코드 또는 초대 링크"
 "Color profile: {name}" = "색상 프로필: {name}"
 "CSS custom properties…" = "CSS 사용자 정의 속성…"
@@ -551,9 +554,11 @@ status = "draft — needs native review"
 "Help page" = "도움말 페이지"
 "Here: {names}" = "참여 중: {names}"
 "Hidden" = "숨김"
+"Hide Others" = "기타 가리기"
 "Hide contents" = "콘텐츠 숨기기"
 "Hide from publishing" = "게시에서 숨기기"
 "Hide section contents" = "섹션 콘텐츠 숨기기"
+"Hide {app}" = "{app} 가리기"
 "Highlight layers on hover" = "마우스를 올리면 레이어 강조"
 "Highlights" = "밝은 영역"
 "Hold Alt" = "Alt 누른 채로"
@@ -981,6 +986,7 @@ status = "draft — needs native review"
 "Push" = "밀어내기"
 "Push changes to main component" = "변경 사항을 메인 컴포넌트에 푸시"
 "Put text on path" = "텍스트를 패스에 배치"
+"Quit {app}" = "{app} 종료"
 "R" = "R"
 "Radial" = "방사형"
 "Radius" = "반경"
@@ -1140,11 +1146,13 @@ status = "draft — needs native review"
 "Server address" = "서버 주소"
 "Server address (ws://host:47900/)" = "서버 주소(ws://host:47900/)"
 "Server for online sharing" = "온라인 공유용 서버"
+"Services" = "서비스"
 "Set as default" = "기본값으로 설정"
 "Set variable" = "변수 설정"
 "Set variable mode" = "변수 모드 설정"
 "Set variable {name}" = "변수 {name} 설정"
 "Set {name} to {value}" = "{name}을(를) {value}(으)로 설정"
+"Settings…" = "설정…"
 "Shadows" = "어두운 영역"
 "Shape" = "도형"
 "Shape builder" = "도형 빌더"
@@ -1160,6 +1168,7 @@ status = "draft — needs native review"
 "Sharing online · {people}" = "온라인으로 공유 중 · {people}"
 "Sharing stopped: the file stays on the server, open only to you" = "공유 중지됨: 파일은 서버에 남으며 나만 열 수 있음"
 "Show" = "표시"
+"Show All" = "모두 보기"
 "Show as grid" = "그리드로 보기"
 "Show as list" = "목록으로 보기"
 "Show autosaves" = "자동 저장 표시"
@@ -1416,6 +1425,7 @@ status = "draft — needs native review"
 "Width" = "너비"
 "Width point" = "너비 점"
 "Width profile" = "너비 프로필"
+"Window" = "윈도우"
 "With the layers' export settings" = "레이어의 내보내기 설정으로"
 "Wrap" = "줄 바꿈"
 "Wrap in new section" = "새 섹션으로 감싸기"
diff --git a/crates/ui-egui/locales/nl.toml b/crates/ui-egui/locales/nl.toml
--- a/crates/ui-egui/locales/nl.toml
+++ b/crates/ui-egui/locales/nl.toml
@@ -27,6 +27,7 @@ status = "draft — needs native review"
 "About" = "Over"
 "About FigCraft" = "Over FigCraft"
 "About this server" = "Over deze server"
+"About {app}" = "Over {app}"
 "Action" = "Actie"
 "Actions" = "Acties"
 "Actions for {name}" = "Acties voor {name}"
@@ -185,6 +186,7 @@ status = "draft — needs native review"
 "Branch updated from main" = "Branch bijgewerkt vanaf main"
 "Branch {name}" = "Branch {name}"
 "Branches" = "Branches"
+"Bring All to Front" = "Alles op voorgrond"
 "Bring forward" = "Naar voren brengen"
 "Bring the main file's changes into the branch" = "De wijzigingen uit het hoofdbestand in de branch brengen"
 "Bring to front" = "Op de voorgrond plaatsen"
@@ -240,6 +242,7 @@ status = "draft — needs native review"
 "Clip content" = "Inhoud bijsnijden"
 "Clip to shape" = "Bijsnijden op vorm"
 "Close" = "Sluiten"
+"Close Window" = "Sluit venster"
 "Close overlay" = "Overlay sluiten"
 "Close preview" = "Voorvertoning sluiten"
 "Close tab" = "Tabblad sluiten"
@@ -583,11 +586,13 @@ status = "draft — needs native review"
 "Help page" = "Helppagina"
 "Here: {names}" = "Hier: {names}"
 "Hidden" = "Verborgen"
+"Hide Others" = "Verberg andere"
 "Hide contents" = "Inhoud verbergen"
 "Hide from publishing" = "Verbergen bij publiceren"
 "Hide section contents" = "Sectie-inhoud verbergen"
 "Hide these tips (Getting started in the Help menu shows them again)" = "Deze tips verbergen (Aan de slag in het menu Help toont ze opnieuw)"
 "Hide when publishing" = "Verbergen bij publiceren"
+"Hide {app}" = "Verberg {app}"
 "Highlight layers on hover" = "Lagen uitlichten bij aanwijzen"
 "Highlights" = "Hooglichten"
 "Hold Alt" = "Houd Alt ingedrukt"
@@ -1046,6 +1051,7 @@ status = "draft — needs native review"
 "Push" = "Pushen"
 "Push changes to main component" = "Wijzigingen naar hoofdcomponent doorvoeren"
 "Put text on path" = "Tekst op pad plaatsen"
+"Quit {app}" = "Stop {app}"
 "R" = "R"
 "Radial" = "Radiaal"
 "Radius" = "Radius"
@@ -1212,11 +1218,13 @@ status = "draft — needs native review"
 "Server address" = "Serveradres"
 "Server address (ws://host:47900/)" = "Serveradres (ws://host:47900/)"
 "Server for online sharing" = "Server voor online delen"
+"Services" = "Voorzieningen"
 "Set as default" = "Als standaard instellen"
 "Set variable" = "Variabele instellen"
 "Set variable mode" = "Variabelemodus instellen"
 "Set variable {name}" = "Variabele {name} instellen"
 "Set {name} to {value}" = "{name} instellen op {value}"
+"Settings…" = "Instellingen…"
 "Shadows" = "Schaduwen"
 "Shape" = "Vorm"
 "Shape builder" = "Shape builder"
@@ -1233,6 +1241,7 @@ status = "draft — needs native review"
 "Sharing online · {people}" = "Delen online · {people}"
 "Sharing stopped: the file stays on the server, open only to you" = "Delen gestopt: het bestand blijft op de server staan, alleen voor jou toegankelijk"
 "Show" = "Weergeven"
+"Show All" = "Toon alles"
 "Show all templates" = "Alle sjablonen tonen"
 "Show as grid" = "Als raster weergeven"
 "Show as list" = "Weergeven als lijst"
@@ -1511,6 +1520,7 @@ status = "draft — needs native review"
 "Width" = "Breedte"
 "Width point" = "Breedtepunt"
 "Width profile" = "Breedteprofiel"
+"Window" = "Venster"
 "Wireframe kit" = "Wireframekit"
 "With the layers' export settings" = "Met de exportinstellingen van de lagen"
 "Wrap" = "Omloop"
diff --git a/crates/ui-egui/locales/pt-BR.toml b/crates/ui-egui/locales/pt-BR.toml
--- a/crates/ui-egui/locales/pt-BR.toml
+++ b/crates/ui-egui/locales/pt-BR.toml
@@ -22,6 +22,7 @@ status = "draft — needs native review"
 "About" = "Sobre"
 "About FigCraft" = "Sobre o FigCraft"
 "About this server" = "Sobre este servidor"
+"About {app}" = "Sobre o {app}"
 "Action" = "Ação"
 "Actions" = "Ações"
 "Actions for {name}" = "Ações para {name}"
@@ -172,6 +173,7 @@ status = "draft — needs native review"
 "Branch updated from main" = "Branch atualizada a partir do principal"
 "Branch {name}" = "Branch {name}"
 "Branches" = "Branches"
+"Bring All to Front" = "Trazer tudo para a frente"
 "Bring forward" = "Trazer para a frente"
 "Bring the main file's changes into the branch" = "Traz para a branch as alterações do arquivo principal"
 "Bring to front" = "Trazer para o primeiro plano"
@@ -189,6 +191,7 @@ status = "draft — needs native review"
 "Can view" = "Pode visualizar"
 "Checking the server…" = "Verificando o servidor…"
 "Clear from this list" = "Limpar desta lista"
+"Close Window" = "Fechar janela"
 "Code or invite link" = "Código ou link de convite"
 "Color profile: {name}" = "Perfil de cor: {name}"
 "CSS custom properties…" = "Propriedades personalizadas CSS…"
@@ -550,9 +553,11 @@ status = "draft — needs native review"
 "Help page" = "Página de ajuda"
 "Here: {names}" = "Aqui: {names}"
 "Hidden" = "Oculta"
+"Hide Others" = "Ocultar outros"
 "Hide contents" = "Ocultar conteúdo"
 "Hide from publishing" = "Não publicar"
 "Hide section contents" = "Ocultar conteúdo da seção"
+"Hide {app}" = "Ocultar {app}"
 "Highlight layers on hover" = "Destacar camadas ao passar o cursor"
 "Highlights" = "Realces"
 "Hold Alt" = "Segure Alt"
@@ -980,6 +985,7 @@ status = "draft — needs native review"
 "Push" = "Empurrar"
 "Push changes to main component" = "Enviar alterações ao componente principal"
 "Put text on path" = "Colocar texto no caminho"
+"Quit {app}" = "Encerrar {app}"
 "R" = "R"
 "Radial" = "Radial"
 "Radius" = "Raio"
@@ -1139,11 +1145,13 @@ status = "draft — needs native review"
 "Server address" = "Endereço do servidor"
 "Server address (ws://host:47900/)" = "Endereço do servidor (ws://host:47900/)"
 "Server for online sharing" = "Servidor para compartilhamento online"
+"Services" = "Serviços"
 "Set as default" = "Definir como padrão"
 "Set variable" = "Definir variável"
 "Set variable mode" = "Definir modo de variável"
 "Set variable {name}" = "Definir variável {name}"
 "Set {name} to {value}" = "Definir {name} como {value}"
+"Settings…" = "Ajustes…"
 "Shadows" = "Sombras"
 "Shape" = "Forma"
 "Shape builder" = "Construtor de formas"
@@ -1159,6 +1167,7 @@ status = "draft — needs native review"
 "Sharing online · {people}" = "Compartilhando online · {people}"
 "Sharing stopped: the file stays on the server, open only to you" = "Compartilhamento encerrado: o arquivo continua no servidor, aberto somente para você"
 "Show" = "Mostrar"
+"Show All" = "Mostrar tudo"
 "Show as grid" = "Mostrar em grade"
 "Show as list" = "Mostrar em lista"
 "Show autosaves" = "Mostrar salvamentos automáticos"
@@ -1415,6 +1424,7 @@ status = "draft — needs native review"
 "Width" = "Largura"
 "Width point" = "Ponto de largura"
 "Width profile" = "Perfil de largura"
+"Window" = "Janela"
 "With the layers' export settings" = "Com as configurações de exportação das camadas"
 "Wrap" = "Quebrar linha"
 "Wrap in new section" = "Envolver em uma nova seção"
diff --git a/crates/ui-egui/locales/ru.toml b/crates/ui-egui/locales/ru.toml
--- a/crates/ui-egui/locales/ru.toml
+++ b/crates/ui-egui/locales/ru.toml
@@ -23,6 +23,7 @@ name = "Русский"
 "About" = "О программе"
 "About FigCraft" = "О FigCraft"
 "About this server" = "Об этом сервере"
+"About {app}" = "О программе {app}"
 "Action" = "Действие"
 "Actions" = "Действия"
 "Actions for {name}" = "Действия для {name}"
@@ -179,6 +180,7 @@ name = "Русский"
 "Branch {name}" = "Ветка {name}"
 "Branches" = "Ветки"
 "Bound to {name}. Click to detach the variable." = "Привязано к {name}. Нажмите, чтобы отвязать переменную."
+"Bring All to Front" = "Все окна на передний план"
 "Bring forward" = "На уровень вперёд"
 "Bring the main file's changes into the branch" = "Перенести изменения основного файла в ветку"
 "Bring to front" = "На передний план"
@@ -196,6 +198,7 @@ name = "Русский"
 "Can view" = "Может просматривать"
 "Checking the server…" = "Проверка сервера…"
 "Clear from this list" = "Убрать из этого списка"
+"Close Window" = "Закрыть окно"
 "Code or invite link" = "Код или ссылка приглашения"
 "Color profile: {name}" = "Цветовой профиль: {name}"
 "Colour and text styles, Light and Dark variables, buttons, inputs and cards" = "Цветовые и текстовые стили, переменные светлой и тёмной тем, кнопки, поля ввода и карточки"
@@ -568,11 +571,13 @@ name = "Русский"
 "Help page" = "Страница справки"
 "Here: {names}" = "Здесь: {names}"
 "Hidden" = "Скрыто"
+"Hide Others" = "Скрыть остальные"
 "Hide contents" = "Скрыть содержимое"
 "Hide from publishing" = "Исключить из публикации"
 "Hide section contents" = "Скрыть содержимое секции"
 "Hide these tips (Getting started in the Help menu shows them again)" = "Скрыть подсказки (их можно вернуть через «Начало работы» в меню справки)"
 "Hide when publishing" = "Скрывать при публикации"
+"Hide {app}" = "Скрыть {app}"
 "Highlight layers on hover" = "Подсвечивать слои при наведении"
 "Highlights" = "Света"
 "Hold Alt" = "Удерживайте Alt"
@@ -1024,6 +1029,7 @@ name = "Русский"
 "Push" = "Вытеснение"
 "Push changes to main component" = "Перенести изменения в основной компонент"
 "Put text on path" = "Разместить текст на пути"
+"Quit {app}" = "Завершить {app}"
 "R" = "R"
 "Radial" = "Радиальный"
 "Radius" = "Радиус"
@@ -1188,11 +1194,13 @@ name = "Русский"
 "Server address (ws://host:47900/)" = "Адрес сервера (ws://host:47900/)"
 "Server address" = "Адрес сервера"
 "Server for online sharing" = "Сервер для онлайн-публикации"
+"Services" = "Службы"
 "Set as default" = "Задать по умолчанию"
 "Set variable" = "Задать переменную"
 "Set variable mode" = "Задать режим переменных"
 "Set variable {name}" = "Задать переменную {name}"
 "Set {name} to {value}" = "Задать {name}: {value}"
+"Settings…" = "Настройки…"
 "Shadows" = "Тени"
 "Shape" = "Фигура"
 "Shape builder" = "Конструктор фигур"
@@ -1209,6 +1217,7 @@ name = "Русский"
 "Sharing online · {people}" = "Общий доступ онлайн · {people}"
 "Sharing stopped: the file stays on the server, open only to you" = "Общий доступ остановлен: файл остаётся на сервере, доступен только вам"
 "Show" = "Показать"
+"Show All" = "Показать все"
 "Show all templates" = "Показать все шаблоны"
 "Show as grid" = "Показать сеткой"
 "Show as list" = "Показать списком"
@@ -1479,6 +1488,7 @@ name = "Русский"
 "Width" = "Ширина"
 "Width point" = "Точка ширины"
 "Width profile" = "Профиль ширины"
+"Window" = "Окно"
 "Wireframe kit" = "Набор вайрфреймов"
 "With the layers' export settings" = "С настройками экспорта слоёв"
 "Wrap" = "Перенос"
diff --git a/crates/ui-egui/locales/sv.toml b/crates/ui-egui/locales/sv.toml
--- a/crates/ui-egui/locales/sv.toml
+++ b/crates/ui-egui/locales/sv.toml
@@ -25,6 +25,7 @@ name = "Svenska"
 "About" = "Om"
 "About FigCraft" = "Om FigCraft"
 "About this server" = "Om den här servern"
+"About {app}" = "Om {app}"
 "Action" = "Åtgärd"
 "Actions" = "Åtgärder"
 "Actions for {name}" = "Åtgärder för {name}"
@@ -182,6 +183,7 @@ name = "Svenska"
 "Branch {name}" = "Gren {name}"
 "Branches" = "Grenar"
 "Bound to {name}. Click to detach the variable." = "Bunden till {name}. Klicka för att koppla bort variabeln."
+"Bring All to Front" = "Lägg alla överst"
 "Bring forward" = "Flytta framåt"
 "Bring the main file's changes into the branch" = "Hämta huvudfilens ändringar till grenen"
 "Bring to front" = "Flytta längst fram"
@@ -199,6 +201,7 @@ name = "Svenska"
 "Can view" = "Kan visa"
 "Checking the server…" = "Kontrollerar servern…"
 "Clear from this list" = "Ta bort från listan"
+"Close Window" = "Stäng fönster"
 "Code or invite link" = "Kod eller inbjudningslänk"
 "Color profile: {name}" = "Färgprofil: {name}"
 "Colour and text styles, Light and Dark variables, buttons, inputs and cards" = "Färg- och textstilar, ljusa och mörka variabler, knappar, inmatningsfält och kort"
@@ -577,11 +580,13 @@ name = "Svenska"
 "Help page" = "Hjälpsida"
 "Here: {names}" = "Här: {names}"
 "Hidden" = "Dold"
+"Hide Others" = "Göm övriga"
 "Hide contents" = "Dölj innehåll"
 "Hide from publishing" = "Dölj vid publicering"
 "Hide section contents" = "Dölj avsnittets innehåll"
 "Hide these tips (Getting started in the Help menu shows them again)" = "Dölj de här tipsen (Kom igång i Hjälp-menyn visar dem igen)"
 "Hide when publishing" = "Dölj vid publicering"
+"Hide {app}" = "Göm {app}"
 "Highlight layers on hover" = "Framhäv lager vid hovring"
 "Highlights" = "Högdagrar"
 "Hold Alt" = "Håll ned Alt"
@@ -1034,6 +1039,7 @@ name = "Svenska"
 "Push" = "Skjut"
 "Push changes to main component" = "Skicka ändringar till huvudkomponenten"
 "Put text on path" = "Placera text på bana"
+"Quit {app}" = "Avsluta {app}"
 "R" = "R"
 "Radial" = "Radiell"
 "Radius" = "Radie"
@@ -1198,11 +1204,13 @@ name = "Svenska"
 "Server address (ws://host:47900/)" = "Serveradress (ws://host:47900/)"
 "Server address" = "Serveradress"
 "Server for online sharing" = "Server för delning online"
+"Services" = "Tjänster"
 "Set as default" = "Ange som standard"
 "Set variable" = "Ange variabel"
 "Set variable mode" = "Ange variabelläge"
 "Set variable {name}" = "Ange variabel {name}"
 "Set {name} to {value}" = "Ange {name} till {value}"
+"Settings…" = "Inställningar…"
 "Shadows" = "Skuggor"
 "Shape" = "Form"
 "Shape builder" = "Formbyggare"
@@ -1219,6 +1227,7 @@ name = "Svenska"
 "Sharing online · {people}" = "Delar online · {people}"
 "Sharing stopped: the file stays on the server, open only to you" = "Delningen stoppades: filen ligger kvar på servern och är bara öppen för dig"
 "Show" = "Visa"
+"Show All" = "Visa alla"
 "Show all templates" = "Visa alla mallar"
 "Show as grid" = "Visa som rutnät"
 "Show as list" = "Visa som lista"
@@ -1494,6 +1503,7 @@ name = "Svenska"
 "Width" = "Bredd"
 "Width point" = "Breddpunkt"
 "Width profile" = "Breddprofil"
+"Window" = "Fönster"
 "Wireframe kit" = "Trådramspaket"
 "With the layers' export settings" = "Med lagrens exportinställningar"
 "Wrap" = "Radbryt"
diff --git a/crates/ui-egui/locales/tr.toml b/crates/ui-egui/locales/tr.toml
--- a/crates/ui-egui/locales/tr.toml
+++ b/crates/ui-egui/locales/tr.toml
@@ -26,6 +26,7 @@ name = "Türkçe"
 "About" = "Hakkında"
 "About FigCraft" = "FigCraft hakkında"
 "About this server" = "Bu sunucu hakkında"
+"About {app}" = "{app} hakkında"
 "Action" = "Eylem"
 "Actions" = "Eylemler"
 "Actions for {name}" = "{name} için eylemler"
@@ -182,6 +183,7 @@ name = "Türkçe"
 "Branch {name}" = "Dal: {name}"
 "Branches" = "Dallar"
 "Bound to {name}. Click to detach the variable." = "{name} değişkenine bağlı. Değişkeni ayırmak için tıklayın."
+"Bring All to Front" = "Tümünü öne getir"
 "Bring forward" = "Öne getir"
 "Bring the main file's changes into the branch" = "Ana dosyanın değişikliklerini dala getir"
 "Bring to front" = "En öne getir"
@@ -199,6 +201,7 @@ name = "Türkçe"
 "Can view" = "Görüntüleyebilir"
 "Checking the server…" = "Sunucu denetleniyor…"
 "Clear from this list" = "Bu listeden kaldır"
+"Close Window" = "Pencereyi kapat"
 "Code or invite link" = "Kod veya davet bağlantısı"
 "Color profile: {name}" = "Renk profili: {name}"
 "Colour and text styles, Light and Dark variables, buttons, inputs and cards" = "Renk ve metin stilleri, Açık ve Koyu değişkenler, düğmeler, giriş alanları ve kartlar"
@@ -571,11 +574,13 @@ name = "Türkçe"
 "Help page" = "Yardım sayfası"
 "Here: {names}" = "Burada: {names}"
 "Hidden" = "Gizli"
+"Hide Others" = "Diğerlerini gizle"
 "Hide contents" = "İçeriği gizle"
 "Hide from publishing" = "Yayımlamadan gizle"
 "Hide section contents" = "Bölüm içeriğini gizle"
 "Hide these tips (Getting started in the Help menu shows them again)" = "Bu ipuçlarını gizle (Yardım menüsündeki Başlarken onları yeniden gösterir)"
 "Hide when publishing" = "Yayımlarken gizle"
+"Hide {app}" = "{app} uygulamasını gizle"
 "Highlight layers on hover" = "Üzerine gelince katmanları vurgula"
 "Highlights" = "Açık tonlar"
 "Hold Alt" = "Alt tuşunu basılı tutun"
@@ -1025,6 +1030,7 @@ name = "Türkçe"
 "Push" = "İt"
 "Push changes to main component" = "Değişiklikleri ana bileşene gönder"
 "Put text on path" = "Metni yola yerleştir"
+"Quit {app}" = "{app} uygulamasından çık"
 "R" = "R"
 "Radial" = "Dairesel"
 "Radius" = "Yarıçap"
@@ -1187,11 +1193,13 @@ name = "Türkçe"
 "Server address (ws://host:47900/)" = "Sunucu adresi (ws://host:47900/)"
 "Server address" = "Sunucu adresi"
 "Server for online sharing" = "Çevrimiçi paylaşım sunucusu"
+"Services" = "Hizmetler"
 "Set as default" = "Varsayılan yap"
 "Set variable" = "Değişken ayarla"
 "Set variable mode" = "Değişken modunu ayarla"
 "Set variable {name}" = "{name} değişkenini ayarla"
 "Set {name} to {value}" = "{name} değerini {value} yap"
+"Settings…" = "Ayarlar…"
 "Shadows" = "Gölgeler"
 "Shape" = "Şekil"
 "Shape builder" = "Şekil oluşturucu"
@@ -1208,6 +1216,7 @@ name = "Türkçe"
 "Sharing online · {people}" = "Çevrimiçi paylaşılıyor · {people}"
 "Sharing stopped: the file stays on the server, open only to you" = "Paylaşım durduruldu: dosya sunucuda kalır, yalnızca size açık"
 "Show" = "Göster"
+"Show All" = "Tümünü göster"
 "Show all templates" = "Tüm şablonları göster"
 "Show as grid" = "Izgara olarak göster"
 "Show as list" = "Liste olarak göster"
@@ -1477,6 +1486,7 @@ name = "Türkçe"
 "Width" = "Genişlik"
 "Width point" = "Genişlik noktası"
 "Width profile" = "Genişlik profili"
+"Window" = "Pencere"
 "Wireframe kit" = "Tel kafes seti"
 "With the layers' export settings" = "Katmanların dışa aktarma ayarlarıyla"
 "Wrap" = "Kaydır"
diff --git a/crates/ui-egui/locales/zh-Hans.toml b/crates/ui-egui/locales/zh-Hans.toml
--- a/crates/ui-egui/locales/zh-Hans.toml
+++ b/crates/ui-egui/locales/zh-Hans.toml
@@ -25,6 +25,7 @@ status = "draft — needs native review"
 "About" = "关于"
 "About FigCraft" = "关于 FigCraft"
 "About this server" = "关于此服务器"
+"About {app}" = "关于 {app}"
 "Action" = "操作"
 "Actions" = "操作"
 "Actions for {name}" = "{name} 的操作"
@@ -175,6 +176,7 @@ status = "draft — needs native review"
 "Branch updated from main" = "已从主文件更新分支"
 "Branch {name}" = "分支 {name}"
 "Branches" = "分支"
+"Bring All to Front" = "前置全部窗口"
 "Bring forward" = "上移一层"
 "Bring the main file's changes into the branch" = "将主文件的更改合入分支"
 "Bring to front" = "置于顶层"
@@ -192,6 +194,7 @@ status = "draft — needs native review"
 "Can view" = "可查看"
 "Checking the server…" = "正在检查服务器…"
 "Clear from this list" = "从此列表中清除"
+"Close Window" = "关闭窗口"
 "Code or invite link" = "代码或邀请链接"
 "Color profile: {name}" = "颜色配置文件：{name}"
 "CSS custom properties…" = "CSS 自定义属性…"
@@ -552,9 +555,11 @@ status = "draft — needs native review"
 "Help page" = "帮助页面"
 "Here: {names}" = "在场：{names}"
 "Hidden" = "已隐藏"
+"Hide Others" = "隐藏其他"
 "Hide contents" = "隐藏内容"
 "Hide from publishing" = "发布时隐藏"
 "Hide section contents" = "隐藏分区内容"
+"Hide {app}" = "隐藏 {app}"
 "Highlight layers on hover" = "悬停时高亮图层"
 "Highlights" = "高光"
 "Hold Alt" = "按住 Alt"
@@ -982,6 +987,7 @@ status = "draft — needs native review"
 "Push" = "推入"
 "Push changes to main component" = "将更改推送到主组件"
 "Put text on path" = "将文本放到路径上"
+"Quit {app}" = "退出 {app}"
 "R" = "R"
 "Radial" = "径向"
 "Radius" = "半径"
@@ -1141,11 +1147,13 @@ status = "draft — needs native review"
 "Server address" = "服务器地址"
 "Server address (ws://host:47900/)" = "服务器地址（ws://host:47900/）"
 "Server for online sharing" = "在线分享所用的服务器"
+"Services" = "服务"
 "Set as default" = "设为默认"
 "Set variable" = "设置变量"
 "Set variable mode" = "设置变量模式"
 "Set variable {name}" = "设置变量 {name}"
 "Set {name} to {value}" = "将 {name} 设为 {value}"
+"Settings…" = "设置…"
 "Shadows" = "阴影"
 "Shape" = "形状"
 "Shape builder" = "形状生成器"
@@ -1161,6 +1169,7 @@ status = "draft — needs native review"
 "Sharing online · {people}" = "正在在线分享 · {people}"
 "Sharing stopped: the file stays on the server, open only to you" = "已停止分享：文件保留在服务器上，只有你能打开"
 "Show" = "显示"
+"Show All" = "全部显示"
 "Show as grid" = "以网格显示"
 "Show as list" = "以列表显示"
 "Show autosaves" = "显示自动保存"
@@ -1417,6 +1426,7 @@ status = "draft — needs native review"
 "Width" = "宽度"
 "Width point" = "宽度点"
 "Width profile" = "宽度配置"
+"Window" = "窗口"
 "With the layers' export settings" = "使用图层的导出设置"
 "Wrap" = "换行"
 "Wrap in new section" = "包裹到新分区"
diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs
--- a/crates/ui-egui/src/lib.rs
+++ b/crates/ui-egui/src/lib.rs
@@ -41,6 +41,9 @@ pub mod live_text;
 pub mod measure_overlay;
 pub mod menus;
 pub mod missing_fonts;
+pub mod native_menu;
+#[cfg(test)]
+mod native_menu_tests;
 pub mod online;
 pub mod pending_open;
 pub mod plugins_ui;
diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs
--- a/crates/ui-egui/src/menus.rs
+++ b/crates/ui-egui/src/menus.rs
@@ -719,7 +719,8 @@ fn over_menu(ctx: &egui::Context) -> bool {
     ctx.input(|i| i.pointer.interact_pos()).and_then(|p| ctx.layer_id_at(p)).is_some_and(|l| l.order == egui::Order::Foreground)
 }
 
-const TOP_MENUS: &[&str] = &[
+/// The main menu's menus, in order (the in-window menu and the native menu bar).
+pub const TOP_MENUS: &[&str] = &[
     tr_noop("File"),
     tr_noop("Edit"),
     tr_noop("View"),
diff --git a/crates/ui-egui/src/native_menu.rs b/crates/ui-egui/src/native_menu.rs
new file mode 100644
--- /dev/null
+++ b/crates/ui-egui/src/native_menu.rs
@@ -0,0 +1,204 @@
+//! The main menu as a native menu bar (macOS, through `craft-menu`): the in-window menu's items,
+//! labels, shortcuts and enabled and checked state, built from the same catalog, with Open recent
+//! and the installed plugins. With several windows it acts on the one in front.
+
+use egui::ViewportId;
+use figcraft_engine::catalog::{MENU, SEP};
+use figcraft_engine::keymap::{self, Platform};
+use serde_json::json;
+
+use crate::FigApp;
+use crate::i18n::{self, tr, tr_noop};
+use crate::menus::TOP_MENUS;
+use crate::plugins_ui::{self, MenuEntry};
+use crate::windows;
+
+/// Item ids that carry a parameter: a recent file (`file.recent:<path>`), Clear recent, and a
+/// plugin's command (`plugin:<plugin id>:<command id>`). A native menu reports only the id.
+pub const RECENT: &str = "file.recent:";
+pub const CLEAR_RECENT: &str = "file.recent.clear";
+pub const PLUGIN: &str = "plugin:";
+/// A plugin's widget (`widget:<plugin id>`, Plugins ▸ Widgets).
+pub const WIDGET: &str = "widget:";
+
+/// The labels the Mac layout writes itself (the app menu, the Window menu), for
+/// [`craft_menu::LayoutOptions::label`]; `{app}` is the app's name.
+pub const MAC_LABELS: &[&str] = &[
+    tr_noop("About {app}"),
+    tr_noop("Settings…"),
+    tr_noop("Services"),
+    tr_noop("Hide {app}"),
+    tr_noop("Hide Others"),
+    tr_noop("Show All"),
+    tr_noop("Quit {app}"),
+    tr_noop("Window"),
+    tr_noop("Minimize"),
+    tr_noop("Zoom"),
+    tr_noop("Bring All to Front"),
+    tr_noop("Close Window"),
+];
+
+/// A label the Mac layout writes ([`MAC_LABELS`]), translated.
+pub fn mac_label(english: &'static str) -> String {
+    tr(english).to_string()
+}
+
+/// The main menu as a menu bar, with the Mac's shortcuts (a native menu bar is the Mac's).
+/// `windows`: whether the app can open more windows (New window).
+pub fn menu_bar(app: &FigApp, windows: bool) -> craft_menu::MenuBar {
+    let mut rows = Vec::new();
+    let path = |menu: &str| menu.split('/').map(|p| tr(p).to_string()).collect::<Vec<String>>();
+    let row = |path: Vec<String>, id: String, label: String, shortcut: Option<&str>, enabled: bool, checked: Option<bool>| craft_menu::Row {
+        path,
+        id,
+        label,
+        shortcut: shortcut.map(str::to_string),
+        enabled,
+        checked,
+    };
+    let sep = |path: Vec<String>| row(path, String::new(), "---".into(), None, false, None);
+    plugin_rows(app, &mut rows);
+    for m in MENU {
+        if m.label == SEP {
+            rows.push(sep(path(m.menu)));
+            continue;
+        }
+        if m.id == "file.openRecent" {
+            recent_rows(app, path(&format!("{}/{}", m.menu, i18n::menu_item(m.id, m.label))), &mut rows);
+            continue;
+        }
+        let enabled = crate::menus::available(m.id) && (m.id != "window.new" || windows);
+        let checked = figcraft_engine::cmd::view::checked(&app.session, m.id);
+        rows.push(row(
+            path(m.menu),
+            m.id.into(),
+            i18n::menu_item(m.id, m.label).into_owned(),
+            keymap::primary(m.id, Platform::Mac),
+            enabled,
+            checked,
+        ));
+    }
+    let tops: Vec<&str> = TOP_MENUS.iter().map(|t| tr(t)).collect();
+    let mut bar = craft_menu::from_items(&tops, &rows);
+    bar.tag_item("help.about", craft_menu::ItemRole::About);
+    bar.tag_item("app.preferences", craft_menu::ItemRole::Settings);
+    bar.tag_menu(tr("File"), craft_menu::MenuRole::File);
+    bar.tag_menu(tr("Help"), craft_menu::MenuRole::Help);
+    bar
+}
+
+/// File ▸ Open recent: the recent files (greyed when the file is gone), then Clear recent.
+fn recent_rows(app: &FigApp, path: Vec<String>, rows: &mut Vec<craft_menu::Row>) {
+    let recents: Vec<&String> = crate::home_recents::listed(&app.session.prefs.recent_files).collect();
+    for p in &recents {
+        let file = std::path::Path::new(p.as_str());
+        let name = file.file_name().map_or_else(|| p.to_string(), |f| f.to_string_lossy().into_owned());
+        rows.push(craft_menu::Row {
+            path: path.clone(),
+            id: format!("{RECENT}{p}"),
+            label: name,
+            shortcut: None,
+            enabled: file.exists(),
+            checked: None,
+        });
+    }
+    if !recents.is_empty() {
+        rows.push(craft_menu::Row { path: path.clone(), id: String::new(), label: "---".into(), shortcut: None, enabled: false, checked: None });
+    }
+    rows.push(craft_menu::Row {
+        path,
+        id: CLEAR_RECENT.into(),
+        label: tr("Clear recent").into(),
+        shortcut: None,
+        enabled: !recents.is_empty(),
+        checked: None,
+    });
+}
+
+/// The installed plugins' commands and widgets, heading the Plugins menu as in the in-window menu
+/// ([`plugins_ui::menu_entries`]).
+fn plugin_rows(app: &FigApp, rows: &mut Vec<craft_menu::Row>) {
+    let plugins = &app.session.prefs.plugins;
+    if plugins.is_empty() {
+        return;
+    }
+    let top = tr("Plugins").to_string();
+    let item = |path: Vec<String>, id: String, label: &str| craft_menu::Row {
+        path,
+        id,
+        label: label.to_string(),
+        shortcut: None,
+        enabled: true,
+        checked: None,
+    };
+    let (entries, widgets) = plugins_ui::menu_entries(plugins);
+    for e in &entries {
+        match e {
+            MenuEntry::Command { plugin, command, label } => rows.push(item(vec![top.clone()], format!("{PLUGIN}{plugin}:{command}"), label)),
+            MenuEntry::Submenu { plugin, name, commands } => {
+                for (command, label) in commands {
+                    rows.push(item(vec![top.clone(), name.to_string()], format!("{PLUGIN}{plugin}:{command}"), label));
+                }
+            }
+        }
+    }
+    for (plugin, name) in &widgets {
+        rows.push(item(vec![top.clone(), tr("Widgets").to_string()], format!("{WIDGET}{plugin}"), name));
+    }
+    rows.push(craft_menu::Row { path: vec![top], id: String::new(), label: "---".into(), shortcut: None, enabled: false, checked: None });
+}
+
+/// Run the item `id` chosen in the native menu bar, as the in-window menu would.
+pub fn run(app: &mut FigApp, id: &str) {
+    if id == CLEAR_RECENT {
+        app.run_user("file.openRecent", json!({"clear": true}));
+    } else if let Some(path) = id.strip_prefix(RECENT) {
+        // Through the user path, so an import report shows; `file.openRecent` also drops a file that is gone.
+        if app.run_user("file.openRecent", json!({"path": path})).is_some_and(|r| r.get("index").is_some()) {
+            app.ui.home = false;
+        }
+    } else if let Some((plugin, command)) = id.strip_prefix(PLUGIN).and_then(|rest| rest.split_once(':')) {
+        plugins_ui::start(app, plugin, command);
+    } else if let Some(plugin) = id.strip_prefix(WIDGET) {
+        plugins_ui::insert_widget(app, plugin);
+    } else if crate::menus::available(id) {
+        crate::menus::invoke(app, id);
+    }
+}
+
+/// The viewport of the window in front: the menu bar's key equivalents go to it.
+pub fn front(app: &FigApp) -> ViewportId {
+    windows::viewport(app.windows.focused)
+}
+
+/// The menu bar for the window in front (its file, selection and panels).
+pub fn menu_bar_in_front(app: &mut FigApp, windows: bool) -> craft_menu::MenuBar {
+    let id = app.windows.focused;
+    crate::windows::as_window(app, id, |app| menu_bar(app, windows)).unwrap_or_else(|| menu_bar(app, windows))
+}
+
+/// Run the item `id` chosen in the menu bar in the window in front.
+pub fn run_in_front(app: &mut FigApp, id: &str) {
+    let front = app.windows.focused;
+    if windows::as_window(app, front, |app| run(app, id)).is_none() {
+        run(app, id);
+    }
+}
+
+/// Key presses from the menu bar's key equivalents that arrived while another window drew: the
+/// window in front takes them in its next frame.
+pub fn send_keys(app: &mut FigApp, ctx: &egui::Context, events: Vec<egui::Event>) {
+    let id = app.windows.focused;
+    if windows::as_window(app, id, |app| app.control_input.extend(events)).is_some() {
+        ctx.request_repaint_of(windows::viewport(id));
+    }
+}
+
+/// The pastes in the main window's input while another window is in front: the menu bar's ⌘V
+/// asks the main window for the clipboard ([`craft_menu::NativeMenu::take_clicks`]), so its
+/// answer goes on to the window in front ([`send_keys`]).
+pub fn take_pastes(events: &mut Vec<egui::Event>) -> Vec<egui::Event> {
+    let (pastes, rest) = std::mem::take(events).into_iter().partition(|e| matches!(e, egui::Event::Paste(_)));
+    *events = rest;
+    pastes
+}
diff --git a/crates/ui-egui/src/native_menu_tests.rs b/crates/ui-egui/src/native_menu_tests.rs
new file mode 100644
--- /dev/null
+++ b/crates/ui-egui/src/native_menu_tests.rs
@@ -0,0 +1,196 @@
+//! The native menu bar's tree: built from the catalog like the in-window menu, with its shortcuts,
+//! enabled and checked state, Open recent and the plugins, and its items run as the in-window
+//! menu's do, in the window in front.
+
+use std::cell::RefCell;
+
+use craft_menu::{ItemRole, LayoutOptions, MenuBar, MenuRole, Node};
+use egui::{RawInput, ViewportId};
+use figcraft_engine::Session;
+use figcraft_engine::plugins::{InstalledPlugin, Manifest};
+use serde_json::json;
+
+use crate::native_menu::{CLEAR_RECENT, MAC_LABELS, RECENT, front, menu_bar, menu_bar_in_front, run, run_in_front, send_keys, take_pastes};
+use crate::{FigApp, NoServices};
+
+fn app() -> FigApp {
+    FigApp::new(Session::new(), Box::new(NoServices))
+}
+
+fn item<'a>(bar: &'a MenuBar, id: &str) -> &'a craft_menu::Item {
+    bar.find(id).unwrap_or_else(|| panic!("no item {id}"))
+}
+
+#[test]
+fn the_bar_has_the_in_window_menus_in_order() {
+    let bar = menu_bar(&app(), true);
+    let titles: Vec<&str> = bar.menus.iter().map(|m| m.title.as_str()).collect();
+    assert_eq!(titles, ["File", "Edit", "View", "Object", "Text", "Arrange", "Vector", "Plugins", "Preferences", "Libraries", "Help"]);
+    // Submenus keep their place: Edit ▸ Copy as.
+    let edit = &bar.menus[1];
+    assert!(edit.children.iter().any(|n| matches!(n, Node::Submenu { label, .. } if label == "Copy as")), "{:?}", edit.children);
+}
+
+#[test]
+fn items_carry_their_shortcuts_and_state() {
+    let mut a = app();
+    let bar = menu_bar(&a, true);
+    let save = item(&bar, "file.save");
+    assert_eq!(save.label, "Save");
+    assert_eq!(save.shortcut.as_deref(), Some("Cmd+S"));
+    assert!(save.enabled);
+    assert_eq!(item(&bar, "edit.redo").shortcut.as_deref(), Some("Shift+Cmd+Z"));
+    // A View toggle shows its check, and follows it.
+    assert_eq!(item(&bar, "view.rulers").checked, Some(false));
+    a.run("view.rulers", json!({}));
+    assert_eq!(item(&menu_bar(&a, true), "view.rulers").checked, Some(true));
+    // New window only where the app can open windows.
+    assert!(item(&bar, "window.new").enabled);
+    assert!(!item(&menu_bar(&a, false), "window.new").enabled);
+}
+
+#[test]
+fn open_recent_lists_the_files_and_clears_them() {
+    let mut a = app();
+    let dir = std::env::temp_dir().join(format!("figcraft-native-menu-{}", std::process::id()));
+    std::fs::create_dir_all(&dir).unwrap();
+    let here = dir.join("here.figcraft");
+    std::fs::write(&here, b"x").unwrap();
+    let gone = dir.join("gone.figcraft");
+    a.session.prefs.recent_files = vec![here.to_string_lossy().into_owned(), gone.to_string_lossy().into_owned()];
+    let bar = menu_bar(&a, true);
+    let shown = item(&bar, &format!("{RECENT}{}", here.display()));
+    assert_eq!(shown.label, "here.figcraft");
+    assert!(shown.enabled);
+    assert!(!item(&bar, &format!("{RECENT}{}", gone.display())).enabled, "a file that is gone is greyed");
+    assert!(item(&bar, CLEAR_RECENT).enabled);
+    run(&mut a, CLEAR_RECENT);
+    assert!(a.session.prefs.recent_files.is_empty());
+    assert!(!item(&menu_bar(&a, true), CLEAR_RECENT).enabled);
+    let _ = std::fs::remove_dir_all(&dir);
+}
+
+#[test]
+fn chosen_items_run_their_commands() {
+    let mut a = app();
+    assert!(!a.session.ds().unwrap().view.rulers);
+    run(&mut a, "view.rulers");
+    assert!(a.session.ds().unwrap().view.rulers);
+    // Unknown ids and greyed parameters do nothing.
+    run(&mut a, "no.such.command");
+    run(&mut a, "plugin:missing");
+}
+
+#[test]
+fn the_mac_layout_moves_about_and_settings_to_the_app_menu() {
+    let bar = menu_bar(&app(), true);
+    let layout = craft_menu::mac_layout(&bar, &LayoutOptions::new("FigCraft"));
+    let first = &layout.bar.menus[0];
+    assert_eq!(first.role, MenuRole::App);
+    let roles: Vec<Option<ItemRole>> = first
+        .children
+        .iter()
+        .filter_map(|n| match n {
+            Node::Item(i) => Some(i.role),
+            _ => None,
+        })
+        .collect();
+    assert!(roles.contains(&Some(ItemRole::About)) && roles.contains(&Some(ItemRole::Settings)), "{:?}", first.children);
+    assert!(layout.bar.find("help.about").is_some_and(|i| i.role == Some(ItemRole::About)));
+    // A Window menu is added; Help stays last.
+    let titles: Vec<&str> = layout.bar.menus.iter().map(|m| m.title.as_str()).collect();
+    assert!(titles.contains(&"Window"), "{titles:?}");
+    assert_eq!(titles.last(), Some(&"Help"));
+}
+
+#[test]
+fn every_label_the_mac_layout_writes_is_translated() {
+    let asked = RefCell::new(Vec::new());
+    let label = |english: &'static str| {
+        asked.borrow_mut().push(english);
+        english.to_string()
+    };
+    craft_menu::mac_layout(&menu_bar(&app(), true), &LayoutOptions { label: &label, ..LayoutOptions::new("FigCraft") });
+    let asked = asked.into_inner();
+    assert!(asked.contains(&"About {app}") && asked.contains(&"Quit {app}"), "{asked:?}");
+    for english in &asked {
+        assert!(MAC_LABELS.contains(english), "{english:?} is missing from MAC_LABELS");
+    }
+    // Every full catalog (not a regional overlay) has them: About doesn't fall back to English.
+    for lang in crate::i18n::LANGS.iter().filter(|l| l.parent == 0) {
+        let missing: Vec<_> = MAC_LABELS.iter().filter(|k| lang.ui.binary_search_by(|(key, _)| key.cmp(k)).is_err()).collect();
+        assert!(missing.is_empty(), "{}: untranslated menu bar labels {missing:?}", lang.code);
+    }
+    let de = crate::i18n::LANGS.iter().find(|l| l.code == "de").unwrap();
+    let about = de.ui.iter().find(|(k, _)| *k == "About {app}").map(|(_, t)| t);
+    assert!(matches!(about, Some(crate::i18n::Text::Plain("Über {app}"))));
+}
+
+fn plugin(id: &str, name: &str, commands: &[&str], widget: bool) -> InstalledPlugin {
+    let commands: Vec<_> = commands.iter().map(|c| json!({"id": c, "label": format!("Run {c}")})).collect();
+    let manifest: Manifest =
+        serde_json::from_value(json!({"id": id, "name": name, "version": "1.0.0", "main": "main.js", "commands": commands, "widget": widget}))
+            .unwrap();
+    InstalledPlugin { manifest, path: None, source: None }
+}
+
+#[test]
+fn the_plugins_menu_lists_commands_and_widgets_as_in_the_window() {
+    let mut a = app();
+    a.session.prefs.plugins = vec![plugin("one", "One", &["go"], false), plugin("two", "Two", &["a", "b"], false), plugin("pad", "Pad", &[], true)];
+    let bar = menu_bar(&a, true);
+    let plugins = bar.menus.iter().find(|m| m.title == "Plugins").unwrap();
+    assert!(matches!(&plugins.children[0], Node::Item(i) if i.id == "plugin:one:go" && i.label == "One"));
+    assert!(matches!(&plugins.children[1], Node::Submenu { label, children, .. } if label == "Two" && children.len() == 2));
+    assert!(
+        matches!(&plugins.children[2], Node::Submenu { label, children, .. } if label == "Widgets" && matches!(&children[0], Node::Item(i) if i.id == "widget:pad"))
+    );
+    assert!(matches!(plugins.children[3], Node::Separator));
+}
+
+/// Files A and B, A in a second window in front, B in the main window.
+fn two_windows() -> (egui::Context, FigApp, u64) {
+    let mut s = Session::empty();
+    for n in ["A", "B"] {
+        s.execute("file.new", &json!({"name": n})).unwrap();
+    }
+    let ctx = egui::Context::default();
+    ctx.set_embed_viewports(false);
+    let mut a = FigApp::new(s, Box::new(NoServices));
+    let w = crate::control::handle(&mut a, &ctx, "window.new", &json!({"index": 0})).unwrap()["window"].as_u64().unwrap();
+    a.windows.focused = w;
+    (ctx, a, w)
+}
+
+fn rulers(a: &FigApp, name: &str) -> bool {
+    a.session.docs.iter().find(|d| d.doc.name == name).unwrap().view.rulers
+}
+
+#[test]
+fn with_two_windows_the_menu_acts_on_the_one_in_front() {
+    let (_ctx, mut a, w) = two_windows();
+    assert_eq!(front(&a), crate::windows::viewport(w));
+    run_in_front(&mut a, "view.rulers");
+    assert!(rulers(&a, "A") && !rulers(&a, "B"), "in the front window's file");
+    assert_eq!(a.session.ds().unwrap().doc.name, "B", "the main window keeps its own file");
+    assert_eq!(item(&menu_bar_in_front(&mut a, true), "view.rulers").checked, Some(true));
+    assert_eq!(item(&menu_bar(&a, true), "view.rulers").checked, Some(false), "the main window's state");
+    // With the main window in front, the main window's.
+    a.windows.focused = 0;
+    assert_eq!(front(&a), ViewportId::ROOT);
+    run_in_front(&mut a, "view.rulers");
+    assert!(rulers(&a, "B"));
+}
+
+#[test]
+fn key_equivalents_and_pastes_go_to_the_window_in_front() {
+    let (ctx, mut a, w) = two_windows();
+    let key = egui::Event::Key { key: egui::Key::S, physical_key: None, pressed: true, repeat: false, modifiers: egui::Modifiers::COMMAND };
+    send_keys(&mut a, &ctx, vec![key.clone()]);
+    assert!(crate::windows::take_input(&mut a, ViewportId::ROOT).is_empty(), "not the main window's");
+    assert_eq!(crate::windows::take_input(&mut a, crate::windows::viewport(w)), vec![key.clone()]);
+    // The main window's input loses the paste it was asked for, and keeps the rest.
+    let mut raw = RawInput { events: vec![key.clone(), egui::Event::Paste("text".into())], ..Default::default() };
+    assert_eq!(take_pastes(&mut raw.events), vec![egui::Event::Paste("text".into())]);
+    assert_eq!(raw.events, vec![key]);
+}
diff --git a/crates/ui-egui/src/plugins_ui.rs b/crates/ui-egui/src/plugins_ui.rs
--- a/crates/ui-egui/src/plugins_ui.rs
+++ b/crates/ui-egui/src/plugins_ui.rs
@@ -131,37 +131,68 @@ pub fn import_dialog(app: &mut FigApp) {
     });
 }
 
+/// One entry at the top of the Plugins menu.
+pub enum MenuEntry<'a> {
+    /// A plugin with one command: an item, with an ellipsis when it asks for parameters first.
+    Command { plugin: &'a str, command: &'a str, label: String },
+    /// A plugin with several commands: a submenu of them, as (command id, label).
+    Submenu { plugin: &'a str, name: &'a str, commands: Vec<(&'a str, &'a str)> },
+}
+
+/// The top of the Plugins menu, for the in-window menu and the native menu bar: the installed
+/// plugins' commands, then their widgets as (plugin id, name). Empty without plugins.
+pub fn menu_entries(plugins: &[InstalledPlugin]) -> (Vec<MenuEntry<'_>>, Vec<(&str, &str)>) {
+    let mut entries = Vec::new();
+    for p in plugins {
+        let m = &p.manifest;
+        match m.commands.as_slice() {
+            // A widget may have no commands: it's in Widgets.
+            [] => {}
+            [only] => {
+                let label = if only.params.is_empty() { m.name.clone() } else { format!("{}…", m.name) };
+                entries.push(MenuEntry::Command { plugin: &m.id, command: &only.id, label });
+            }
+            commands => entries.push(MenuEntry::Submenu {
+                plugin: &m.id,
+                name: &m.name,
+                commands: commands.iter().map(|c| (c.id.as_str(), c.label.as_str())).collect(),
+            }),
+        }
+    }
+    let widgets = plugins.iter().filter(|p| p.manifest.widget).map(|p| (p.manifest.id.as_str(), p.manifest.name.as_str())).collect();
+    (entries, widgets)
+}
+
+/// Insert plugin `id`'s widget (Plugins ▸ Widgets).
+pub fn insert_widget(app: &mut FigApp, id: &str) {
+    app.run("widget.insert", json!({"plugin": id}));
+}
+
 /// The installed plugins at the top of the Plugins menu: a plugin with one command is an item, one
 /// with several a submenu. True when one was clicked.
 pub fn menu(app: &mut FigApp, ui: &mut Ui, t: &Tokens) -> bool {
     if app.session.prefs.plugins.is_empty() {
         return false;
     }
+    let (entries, widgets) = menu_entries(&app.session.prefs.plugins);
     let mut chosen: Option<(String, String)> = None;
-    for p in &app.session.prefs.plugins {
-        let m = &p.manifest;
-        match m.commands.as_slice() {
-            // A widget may have no commands: it's in Widgets.
-            [] => {}
-            [only] => {
-                // An ellipsis: it asks for parameters first.
-                let label = if only.params.is_empty() { m.name.clone() } else { format!("{}…", m.name) };
-                if crate::menus::item(ui, &label, "", true, false, t) {
-                    chosen = Some((m.id.clone(), only.id.clone()));
+    for e in &entries {
+        match e {
+            MenuEntry::Command { plugin, command, label } => {
+                if crate::menus::item(ui, label, "", true, false, t) {
+                    chosen = Some((plugin.to_string(), command.to_string()));
                 }
             }
-            commands => {
-                let picked = crate::menus::submenu(ui, &format!("Plugins/plugin:{}", m.id), &m.name, t, |ui| {
-                    commands.iter().find(|c| crate::menus::item(ui, &c.label, "", true, false, t)).map(|c| c.id.clone())
+            MenuEntry::Submenu { plugin, name, commands } => {
+                let picked = crate::menus::submenu(ui, &format!("Plugins/plugin:{plugin}"), name, t, |ui| {
+                    commands.iter().find(|(_, label)| crate::menus::item(ui, label, "", true, false, t)).map(|(id, _)| id.to_string())
                 });
                 if let Some(c) = picked {
-                    chosen = Some((m.id.clone(), c));
+                    chosen = Some((plugin.to_string(), c));
                 }
             }
         }
     }
-    let widgets: Vec<(&str, &str)> =
-        app.session.prefs.plugins.iter().filter(|p| p.manifest.widget).map(|p| (p.manifest.id.as_str(), p.manifest.name.as_str())).collect();
     let widget = (!widgets.is_empty())
         .then(|| {
             crate::menus::submenu(ui, "Plugins/widgets", tr("Widgets"), t, |ui| {
@@ -171,7 +202,7 @@ pub fn menu(app: &mut FigApp, ui: &mut Ui, t: &Tokens) -> bool {
         .flatten();
     crate::menus::separator(ui);
     if let Some(plugin) = widget {
-        app.run("widget.insert", json!({"plugin": plugin}));
+        insert_widget(app, &plugin);
         return true;
     }
     let Some((id, command)) = chosen else { return false };
