Skip to content

Commit a661358

Browse files
committed
Implement fix for menus
1 parent 18473b3 commit a661358

5 files changed

Lines changed: 33 additions & 1 deletion

File tree

Backend/src/core/ui.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@
55
66
use serde::{Deserialize, Serialize};
77

8+
/// Menu surface target for rendering.
9+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10+
#[serde(rename_all = "kebab-case")]
11+
pub enum MenuSurface {
12+
/// Menu appears in the main menubar.
13+
Menubar,
14+
/// Menu appears in the settings modal.
15+
Settings,
16+
}
17+
818
/// Menu descriptor contributed by a plugin.
919
#[derive(Debug, Clone, Serialize, Deserialize)]
1020
pub struct Menu {
@@ -15,6 +25,8 @@ pub struct Menu {
1525
/// Optional display ordering hint.
1626
#[serde(default, skip_serializing_if = "Option::is_none")]
1727
pub order: Option<u32>,
28+
/// Required render target surface ('menubar' or 'settings').
29+
pub surface: MenuSurface,
1830
/// Renderable UI elements under the menu.
1931
pub elements: Vec<UiElement>,
2032
}

Backend/src/tauri_commands/plugins.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright © 2025-2026 OpenVCS Contributors
22
// SPDX-License-Identifier: GPL-3.0-or-later
33
use crate::core::settings::{SettingKv, SettingValue};
4-
use crate::core::ui::{Menu, UiElement};
4+
use crate::core::ui::{Menu, MenuSurface, UiElement};
55
use crate::plugin_bundles::{InstalledPluginIndex, PluginBundleStore};
66
use crate::plugin_runtime::instance::PluginRuntimeInstance;
77
use crate::plugin_runtime::settings_store;
@@ -63,6 +63,8 @@ pub struct PluginMenuPayload {
6363
pub id: String,
6464
/// User-visible label.
6565
pub label: String,
66+
/// Render target surface.
67+
pub surface: MenuSurface,
6668
/// Renderable menu elements.
6769
pub elements: Vec<Value>,
6870
}
@@ -420,6 +422,7 @@ fn menu_to_payload(plugin_id: &str, menu: Menu) -> PluginMenuPayload {
420422
plugin_id: plugin_id.to_string(),
421423
id: menu.id,
422424
label: menu.label,
425+
surface: menu.surface,
423426
elements,
424427
}
425428
}

Frontend/src/scripts/features/settings.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ interface PluginMenuPayload {
2121
plugin_id: string;
2222
id: string;
2323
label: string;
24+
surface: 'menubar' | 'settings';
2425
elements: Array<{
2526
type: 'text' | 'button' | string;
2627
id?: string;
@@ -300,6 +301,10 @@ async function renderPluginMenus(modal: HTMLElement): Promise<void> {
300301
};
301302

302303
for (const menu of menus) {
304+
// Only render settings-surface menus in the settings modal.
305+
const surface = String(menu.surface || 'menubar').toLowerCase();
306+
if (surface !== 'settings') continue;
307+
303308
const section = pluginSectionId(menu.plugin_id, menu.id);
304309
const navLi = document.createElement('li');
305310
navLi.dataset.pluginMenu = 'true';

Frontend/src/scripts/ui/menubar.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ interface PluginMenuPayload {
1111
plugin_id: string;
1212
id: string;
1313
label: string;
14+
surface: 'menubar' | 'settings';
1415
elements: Array<{
1516
type: 'text' | 'button' | string;
1617
id?: string;
@@ -57,6 +58,10 @@ export async function refreshPluginMenubarMenus(): Promise<void> {
5758
}
5859

5960
for (const menu of Array.isArray(menus) ? menus : []) {
61+
// Only render menubar-surface menus in the menubar.
62+
const surface = String(menu.surface || 'menubar').toLowerCase();
63+
if (surface !== 'menubar') continue;
64+
6065
const menuId = String(menu?.id || '').trim();
6166
const list = getMenuList(menuId);
6267
if (!menuId || !list) continue;

docs/plugin architecture.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ match built-in top-level menus such as `repository` are projected into the main
4848
menubar. For VCS backend plugins, these items therefore appear only after the
4949
repository-scoped runtime has started.
5050

51+
Menu surfaces can be explicitly targeted using the `surface` option:
52+
53+
- `getOrCreateMenu('repository', 'Repository', { surface: 'menubar' })` - renders in the top menubar
54+
- `getOrCreateMenu('my-settings', 'My Settings', { surface: 'settings' })` - renders in the Settings modal
55+
56+
The `surface` option is required. Plugin authors must explicitly specify where their menus should appear.
57+
5158
Core plugin-to-host notifications:
5259

5360
- `host.log`

0 commit comments

Comments
 (0)