Panel UI
Extension panels are custom browser UIs rendered inside Mantis. They are the main way users interact with an extension, while long-lived behavior belongs in the extension host main file.
How Mantis loads a panel
When a user opens an extension panel:
- Mantis activates
onPanel:<panelId>for the extension host if the manifest declares it. - Mantis creates an isolated iframe panel environment.
- Mantis makes
window.mantisavailable to the panel. - Mantis loads any declared CSS files.
- Mantis loads any declared extra scripts.
- Mantis runs the panel
entryscript.
The panel owns its DOM. Mantis does not require React, Vue, or any particular frontend framework. The entry script can be plain JavaScript or bundled output from your framework of choice.
Isolation
Mantis loads panel HTML in an iframe with a strict sandbox: scripts run, but the document does not get allow-same-origin, so the panel cannot treat itself as the same site as Mantis. Use window.mantis.workspaceState and window.mantis.globalState for persisted settings (see SDK API); that data is stored via message passing in the parent app, not by sharing browser storage with the main window.
Root element
Every panel starts with:
<div id="root">
<div>Loading extension...</div>
</div>Your entry script should render into #root:
const root = document.getElementById('root');
root.innerHTML = [
'<main>',
'<h1>My Extension</h1>',
'<button id="load">Load Data</button>',
'<pre id="output">Ready.</pre>',
'</main>',
].join('');Styling
Declare styles in the manifest:
{
"contributes": {
"panels": [
{
"id": "main",
"title": "Main",
"entry": "dist/panel.js",
"styles": ["dist/styles.css"]
}
]
}
}CSS is scoped to the panel, so it does not leak into Mantis. Mantis also injects base styles for full-height layout:
html,
body,
#root {
height: 100%;
margin: 0;
}Extra scripts
Use scripts for helpers that should load before the entry file:
{
"id": "main",
"title": "Main",
"entry": "dist/panel.js",
"scripts": ["dist/helpers.js"]
}Scripts run in the order listed, then the entry script runs.
Example: selection-aware panel
(function () {
const root = document.getElementById('root');
root.innerHTML = [
'<main style="padding: 12px">',
'<h2>Selection</h2>',
'<button id="read">Read Selection</button>',
'<pre id="output">Ready.</pre>',
'</main>',
].join('');
const output = document.getElementById('output');
document.getElementById('read').onclick = async () => {
try {
const active = await window.mantis.maps.getActive();
const selection = await window.mantis.selection.get(active && active.mapId);
output.textContent = JSON.stringify(selection, null, 2);
} catch (error) {
output.textContent = error && error.message ? error.message : String(error);
}
};
})();This requires:
{
"permissions": ["maps:read", "selection:read"]
}Example: live event panel
(async function () {
const root = document.getElementById('root');
root.innerHTML = [
'<main style="padding: 12px">',
'<h2>Live Selection</h2>',
'<pre id="output">Waiting...</pre>',
'</main>',
].join('');
const output = document.getElementById('output');
const active = await window.mantis.maps.getActive();
const subscription = await window.mantis.events.subscribe(
'selection.changed',
{ mapId: active && active.mapId },
(payload) => {
output.textContent = JSON.stringify(payload, null, 2);
},
);
window.addEventListener('beforeunload', () => {
subscription.dispose();
});
})();This requires:
{
"permissions": ["maps:read", "selection:read"]
}Isolation
Panel code is isolated from the parent Mantis app. All access to Mantis data and actions must go through window.mantis.
Each panel instance also gets a private message channel for SDK traffic. This keeps multiple panels from sharing one generic message path.
Panel Vs Host State
Use panel-local state for UI details such as selected tabs, expanded rows, temporary form fields, and rendered results.
Use the extension host for commands, background subscriptions, and state that should survive panel close. Host code can persist data through context.workspaceState or context.globalState.
Panel-registered commands are still disposed when the panel closes. Register durable commands from activate(context) in the extension host.