Menu and palette entries
BetaThis is a reference for developers declaring menu and palette entries by hand. Most readers don't need it — see Build one with the Builder instead.
UI contributions let trusted local plugin manifests place host-rendered entries in Wrengle surfaces without rendering plugin code in host chrome. Wrengle validates the manifest on Settings -> Plugins -> Reload or app reload. It then exposes accepted actions, views, and UI contributions through one atomic snapshot.
The current manifest schema uses surface-specific TOML tables. Runtime snapshots
normalize those accepted rows into UiContribution entries with surface,
group, target, and disabled fields. Do not rely on runtime registration or
dynamic unregistration from plugin code in this phase.
Surfaces
| Surface | Manifest table | Notes |
|---|---|---|
| Command palette | [[palette]] | References a reusable [[commands]] entry or declares label plus target directly. |
| Slash menu | [[slash_menu]] | Adds editor slash-menu rows with plugin attribution. Custom block schemas are not supported. |
| Context menu | [[context_menus]] | Supports surface = "fileTree" and surface = "editorSelection". |
| Keybinding | [[keybindings]] | References [[commands]]; default_chords must pass host shortcut validation and conflict checks. |
| Status bar | [[status_bar]] | Static plugin-attributed button; no live polling or background status computation. |
| Settings page | [[settings_pages]] | Appears under Settings -> Plugins and renders a same-plugin sandboxed view. |
| Docked panel | [[panels]] | Renders a same-plugin sandboxed view in rightDock or utilityDock. |
Reusable [[commands]] are not visible by themselves. A commands-only plugin
is invalid. Expose a command through [[palette]] or [[keybindings]].
Targets
Targets must resolve at reload. Same-plugin action and view references cannot cross into another plugin.
| Target type | Example | Behavior |
|---|---|---|
hostCommand | { type = "hostCommand", command = "newNote", args = {} } | Runs one explicit host command from the allowlist below. |
runPluginAction | { type = "runPluginAction", action = "plugin.example.action", request = "selection" } | Opens the normal plugin action confirmation and grant flow. |
openPluginView | { type = "openPluginView", view = "plugin.example.view" } | Opens a declared plugin view as a tab. |
sendSelectionToPluginView | { type = "sendSelectionToPluginView", view = "plugin.example.view" } | Sends selected editor text to a declared view. |
openPluginSettingsPage | { type = "openPluginSettingsPage", settingsPage = "plugin.example.settings" } | Opens a declared plugin settings page. |
openPluginPanel | { type = "openPluginPanel", panel = "plugin.example.panel" } | Opens a declared plugin panel. |
Action request modes are empty, selection, and surfaceContext.
selection requires selected editor text when the target runs.
surfaceContext is for limited host context such as a file-tree row.
Host command allowlist
Wrengle does not expose a generic runCommand(name, args) entry point. UI
contributions can call only these host commands:
| Command | Args |
|---|---|
openSearch | Optional { query = "text" }. Context-menu placeholders expand from limited surface context when available: file-tree rows expose {path}, {name}, and {kind}; editor-selection rows expose {selectedText}, {notePath}, and {blockId}. |
newNote | No args. |
toggleTerminal | No args. |
focusEditor | No args. |
openSettings | Optional { page = "plugins" } for built-in pages: general, plugins, integrations, appearance, or privacy. Plugin settings pages use openPluginSettingsPage. |
showNotification | Required { message = "...", level = "info" | "warning" | "error" }. Notifications are visibly attributed to the plugin. |
Host-command args are schema-checked on reload and again after placeholder expansion. Absolute filesystem paths are rejected or redacted. File-tree context uses vault-relative paths.
Manifest examples
Palette and keybinding entries commonly share a reusable command:
[[commands]]
id = "plugin.quick-notes.new-note"
label = "New quick note"
description = "Create a note from anywhere."
target = { type = "hostCommand", command = "newNote", args = {} }
[[palette]]
id = "plugin.quick-notes.new-note.palette"
command = "plugin.quick-notes.new-note"
group = "Plugins"
[[keybindings]]
id = "plugin.quick-notes.new-note.keybinding"
command = "plugin.quick-notes.new-note"
default_chords = ["Ctrl+Shift+KeyN"]
when = "workspaceOpen"Context menus receive limited surface context:
[[context_menus]]
id = "plugin.quick-notes.search-file"
surface = "fileTree"
label = "Search related"
description = "Search for notes related to this file."
target = { type = "hostCommand", command = "openSearch", args = { query = "{path}" } }
[[context_menus]]
id = "plugin.quick-notes.summarize-selection"
surface = "editorSelection"
label = "Summarize selection"
target = { type = "runPluginAction", action = "plugin.quick-notes.summarize", request = "selection" }Settings pages and panels use declared plugin views as their body:
[[views]]
id = "plugin.quick-notes.settings-view"
label = "Quick Notes Settings"
entry = "ui/settings.html"
[[views]]
id = "plugin.quick-notes.panel-view"
label = "Quick Notes"
entry = "ui/panel.html"
[[settings_pages]]
id = "plugin.quick-notes.settings"
label = "Quick Notes"
view = "plugin.quick-notes.settings-view"
[[panels]]
id = "plugin.quick-notes.panel"
label = "Quick Notes"
view = "plugin.quick-notes.panel-view"
placement = "rightDock"
[[status_bar]]
id = "plugin.quick-notes.status"
label = "Quick Notes"
target = { type = "openPluginPanel", panel = "plugin.quick-notes.panel" }Reload, policy, and audit
Accepted contributions become visible only after plugin reload or app reload. Wrengle swaps the contribution snapshot atomically, so actions, views, diagnostics, and UI rows come from the same generation. Invalid rows are skipped with reload diagnostics.
Policy can disable all UI from one plugin installation, one surface, one host command, or one contribution. Settings currently exposes contribution-level disable and re-enable controls. Broader policy scopes are kept as an explicit host API for maintainer-reviewed tooling. Disabled contributions are kept out of host UI surfaces. Attempts to invoke stale, missing, disabled, or invalid targets are denied and audited with path, secret, and selected-text redaction. Confirmed plugin actions are also audited independently of proposal refresh. If an external action may have completed but Wrengle cannot confirm its result, the action is not offered for retry. Wrengle records unknown when the originating vault-open session is still current. If the vault changed, it keeps the terminal warning but never writes the old session's audit record into the new vault. Plugin action confirmations also belong to the exact vault-open session that created them. Switching vaults or reopening the same vault closes pending confirmation and consent UI. Already-in-flight actions remain non-retryable until settlement, even across a renderer reload. A switch waits for the physical worker and exact-vault ledger boundary. Stale selected text, note paths, proposals, and results are kept out of the newly opened vault.
Current limits
- No marketplace, remote install, publishing flow, or public API stability guarantee exists for UI contributions.
- Runtime dynamic contribution APIs are not supported.
- Plugins cannot inject UI components, arbitrary HTML, or CSS into host chrome. Custom UI belongs in sandboxed plugin views.
- UI contributions cannot add editor block schemas, terminal shell access, direct filesystem access, direct settings writes, or silent note/block writes.
- Plugin keybindings do not set native menu accelerators. They fail closed when inputs, modals, settings, terminals, editors, or plugin iframes have focus.