Sidebar panels
context.ui.setPanel (requires sidebar, sandboxed)
Section titled “context.ui.setPanel (requires sidebar, sandboxed)”A sandboxed plugin can have up to three panels of its own in the sidebar. Each gets a button on the
activity bar, and its panel fills the sidebar under a header Baram draws: the panel’s title and
Plugin · with your plugin’s name. You send the panel’s content as strings — the same HTML and CSS
a code block renderer returns — and Baram sanitizes them and
draws them in a shadow root of their own.
A trusted plugin mounts its own DOM in a panel instead, with ctx.ui.addSidebarPanel — see
Context: UI and Shadow-DOM isolation.
setPanel(id: string, content: RenderedContent): void;onPanelAction(id: string, handler: (action: string) => Promise<void> | void): Disposable;
// RenderedContent = { html: string; css?: string }Declaring panels
Section titled “Declaring panels”Declare every panel in the manifest, with the sidebar capability:
{ "trust": "sandboxed", "capabilities": ["sidebar", "files:readonly"], "contributions": { "panels": [{ "id": "recent", "title": "Recent notes", "icon": "🕘" }] }}- At most 3 panels. An
idis letters, digits,_and-, at most 64 characters, each id once. Atitleis text that is not empty; Baram sanitizes it and shortens it to 64 characters, and shows the panel’sidif nothing is left. Anicon, if you give one, is a single character — an emoji or a letter — of at most 16 UTF-16 units. Without an icon, or with one that draws nothing (for example a space, a line break, a zero-width joiner, a variation selector on its own, a Hangul filler), the button shows a generic plugin icon. panelswithout thesidebarcapability is a manifest error, and so ispanelsin a trusted manifest.- The buttons appear before your plugin’s code runs, so they show while it starts. A button’s label
is the panel’s title followed by
Plugin ·and your plugin’s name, so a panel cannot pass for one of Baram’s own. - The user approves every capability your manifest lists in one answer, when installing the plugin
and again when an update adds one, so there is no install without
sidebar. Baram withholds it only when the approval it recorded for your plugin does not cover it. Then no panel button appears, and Baram refuses eachsetPanel— it does not throw, and your plugin gets no error.
Sending content
Section titled “Sending content”const escape = (text: string) => text.replace(/[&<>"]/g, (c) => `&#${c.charCodeAt(0)};`);
export function activate(ctx: SandboxContext) { const draw = async () => { const names = (await ctx.files.listDir("")).filter((n) => n.endsWith(".md")); ctx.ui.setPanel("recent", { html: `<ul>${names .map((n) => `<li><a data-note="${escape(n)}">${escape(n)}</a></li>`) .join("")}</ul>`, css: "ul { margin: 0; padding: 0 12px; }", }); }; void draw(); ctx.events.on("vault:changed", () => void draw());}setPanelthrows at once for an id you did not declare, and for content over a limit — more than 1 MiB of HTML or 64 KiB of CSS in UTF-8, or a lone surrogate (half an emoji) — with the sentences a renderer’s result meets.- Baram keeps the last content of each panel. A panel that is not on screen is not drawn; when the user opens it, Baram draws the last content it received.
- Call
setPanelas often as you like: the latest content wins, and Baram draws a panel that is on screen at most once every 250 milliseconds. A call made while the previous one is still on its way replaces the content waiting to go, so the last content always arrives. - Until your first
setPanel, the panel says it has nothing to show yet.
What may come back
Section titled “What may come back”Baram sanitizes a panel’s content as it does a code block renderer’s result — see What may come back for what is removed or refused, and the limits. Two things differ:
- A panel scrolls inside the sidebar, vertically, with no height limit of its own. It does not
scroll sideways — what is wider than the sidebar is cut off — so put wide content in an element of
your own with
overflow-x: auto. - Refused CSS, or content over the node limit, shows
Render failed —and the reason under the panel’s header, and the last drawing stays, faded.
Clicks
Section titled “Clicks”Clicks are judged as in a code block — see Clicks — with these differences:
data-note="notes/a.md"is relative to the root of the vault or folder that is active when the user clicks: a panel belongs to no note. With no vault or folder active, or if the path is not a note there, nothing opens and Baram says so.- A
data-noteand alistDir("")without acontextdo not always read the same vault. The click uses the vault or folder the app shows as active;listDiruses the one Baram’s backend holds as active. The two can differ — for example while a note opened as a single file is active (a click then opens nothing, whilelistDir("")can still list the vault or folder that was active before it), and for a moment during a switch between vaults. To choose the vault yourself, pass acontexttolistDir("", { context }): redraw onvault:changed(needsfilesorfiles:readonly) or onfile:open(needsevents), which both carry thecontextthey refer to. data-action="name"calls the handlers you registered for that panel withonPanelAction(id, handler); each gets the name. Until every one has settled,ctx.promptsmay open, for the 30 seconds a command runs on — counted from the click, and again from each prompt closing.onPanelActionthrows for an id you did not declare.- A right click does none of these.
Showing a panel, or drawing it, grants nothing: only a click on a data-action element lets your
plugin open a prompt.
The Baram repository has no example plugin with a panel; examples/plugins/backlinks-block draws
the same kind of content in a code block.
Version floor
Section titled “Version floor”ctx.ui.setPanel, ctx.ui.onPanelAction and contributions.panels are new. Set engines.baram to
the first release whose notes list them — see
Version floor.

