Replace the sidebar
The sidebar is the panel to the right of the desktop in a Kasm session. It shows what the run is doing and has an inbox for sending messages to it. Kasm Session Runtime ships one, named default. This guide builds your own and installs it beside that one.
A sidebar is a bundle: a directory with sidebar.js and sidebar.css. You install it into the image under a name of your own, and each session picks one with KASM_RUNTIME_SIDEBAR=<name>. An image can carry several, and installing one overwrites nothing. A sidebar needs no vision model.
A bundle depends on two contracts: how the viewer page hosts it (this guide) and how it talks to the runtime (the sidebar protocol). The bundled sidebar’s internal structure is not part of either contract, and it has no slot or plugin API.
Before you start
Section titled “Before you start”- A stock
kasmweb/*image built with the SDK’sstock_image_modslayer. It carries thekasm-sidebar-installinstaller. See Enable the runtime on a workspace, part B. - The workspace’s
mcpport map. The sidebar reaches the runtime through it. - A copy of the
09-custom-sidebarexample from the SDK: a working sidebar in under two hundred lines of plain JavaScript, with no build step. Start from it.
1. Mount the bundle in the page
Section titled “1. Mount the bundle in the page”sidebar.js is an ES module that the viewer page loads with <script type="module">. It mounts itself when it runs:
- Append one element to
document.body. Do not replace or move the viewer’s#noVNC_container. - Link the stylesheet from the module. The installer puts the CSS next to the script under the same hashed name, so derive its URL from
import.meta.urlby swapping.jsfor.css. - Scope every CSS selector under your root element, so nothing leaks into the viewer.
- Take width by making
bodya flex row, so#noVNC_containershrinks beside the sidebar. After any change of width, dispatch aresizeevent onwindow; KasmVNC re-fits the desktop on that event and no other. - Keep everything in the bundle. The viewer runs under
Cross-Origin-Embedder-Policy: require-corp, so web fonts, icon CDNs and scripts from another origin will not load. Other files in the bundle directory are copied beside the script; refer to them relative to the module URL.
2. Keep keyboard focus with the desktop
Section titled “2. Keep keyboard focus with the desktop”Typing in the sidebar must not reach the session, and the session must get focus back when the person is done.
- Give every control that is not a text input
tabindex="-1", and cancelmousedownon it withpreventDefault. - Stop
keydown,keypressandkeyupfrom propagating out of your root element. - A text input takes focus while the person types.
Escapein any input, and submitting a form, blur it and return focus to the viewer’s canvas. - Never call
focus()on the sidebar except when the person clicks into an input.
3. Talk to the runtime
Section titled “3. Talk to the runtime”The viewer page is /desktop/<kasm_id>/vnc/vnc.html, and the runtime is its sibling at /desktop/<kasm_id>/mcp/. Derive that base from the page’s own location.
- Read
GET <base>events?after=<seq>&run=<run_id>as a stream withfetchand a stream reader. The runtime may not be up when the page loads, and may restart for another run while it is open: probe, reconnect, and reset when a differentrun_idarrives. - Screenshots are at
GET <base>artifacts/<file>. - Writes to the inbox (
POST <base>messages,<base>attachments,<base>actions) carry the token in theX-Kasm-Runtime-Session-Tokenheader. The event stream cannot carry a header, so it takes the token as?token=. - Name the run on every write, and generate each write’s idempotency key before the first attempt, reusing it on retry.
- Keep nothing you need after a reload in the page. The inbox and the trace live in the runtime.
The event types, request shapes and inbox rules are in the sidebar protocol reference. The 09-custom-sidebar example does all of the above; keep those parts when you change it.
4. Install it into the image
Section titled “4. Install it into the image”On top of an image built with stock_image_mods:
FROM kasmweb/chrome:stock-modsUSER rootCOPY my-bundle /tmp/my-bundleRUN kasm-sidebar-install --name acme /usr/share/kasmvnc/www /tmp/my-bundle && rm -rf /tmp/my-bundleUSER 1000With --name acme, kasm-sidebar-install copies the stock KasmVNC web root to /usr/share/kasmvnc-sidebars/acme/www and puts your files in its assets/ as sidebar-<hash>.js and sidebar-<hash>.css. The stock web root and every other sidebar are left alone. Running it again replaces the earlier build of that name.
Names match [a-z0-9][a-z0-9_-]*. KASM_SIDEBAR_ROOT changes the root directory; the default is /usr/share/kasmvnc-sidebars.
Kasm’s proxy caches everything under /desktop/ except vnc.html for four hours. A new build gets a new hashed name, so users never see a stale script.
5. Select it per session
Section titled “5. Select it per session”Set the variable in the workspace’s environment, or in the session request:
{ "environment": { "KASM_RUNTIME_SIDEBAR": "acme" }}KASM_RUNTIME_SIDEBAR |
Viewer served |
|---|---|
| unset | The stock viewer, no sidebar |
| an installed name | That sidebar |
| malformed, or not installed | The stock viewer; the reason and the installed names are logged |
A sidebar is chosen per session and cannot change while the session runs.
6. Let people talk to a run (optional)
Section titled “6. Let people talk to a run (optional)”The compose box and forms need the runtime’s inbox. Set a messaging token on the run, and give the sidebar the same value on the viewer URL:
KASM_RUNTIME_INBOX_TOKEN=change-me KASM_RUNTIME_OBSERVE=1 kasm-session-runtime run scenario.yaml# open the viewer as .../vnc/vnc.html?kasm_runtime_token=change-meKASM_RUNTIME_OBSERVE=1 makes a run serve the event stream and artifacts for the sidebar; under mcp they are served whenever HTTP is on. Without an inbox token the inbox routes are absent and the sidebar can show only the timeline. See People in the loop for messages, ask_human and stop.
Check it works
Section titled “Check it works”Start a session of the workspace with KASM_RUNTIME_SIDEBAR=acme set. Your panel appears to the right of the desktop, the desktop re-fits beside it, and typing in the panel does not reach the session.
To confirm which bundle the viewer loaded, open your browser’s developer tools on the session page and look in the Network tab for assets/sidebar-<hash>.js. The hash changes with each build of the bundle. With KASM_RUNTIME_SIDEBAR unset, no such file loads.
The session’s log has a line sidebar-select: sidebar 'acme' (KASM_VNC_PATH=/usr/share/kasmvnc-sidebars/acme) when the selector found the bundle.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Cause | Fix |
|---|---|---|
Stock viewer, and sidebar-select: no sidebar 'acme' under /usr/share/kasmvnc-sidebars (installed: ...) in the log |
The name is not installed in this image | Check the name in the Dockerfile matches the variable, and that the session uses the rebuilt image |
Stock viewer, and ... is not a valid name ([a-z0-9][a-z0-9_-]*) -- stock viewer |
Uppercase letters or other characters in the name | Use lowercase letters, digits, - and _, starting with a letter or digit |
Build fails with has no sidebar.js/sidebar.css |
The bundle directory is missing a required file | Put both files at the top of the directory |
Build fails with does not look like a KasmVNC web root (no vnc.html) |
The second argument is not the stock web root | Pass /usr/share/kasmvnc/www |
| A font, icon or script does not load | It comes from another origin, which the viewer’s embedder policy blocks | Copy it into the bundle |
| Keys typed in the sidebar also appear in the app | Key events propagate out of the sidebar | Stop propagation on your root element, as in step 2 |
| The desktop does not shrink beside the panel | No resize event after the width changed |
Dispatch resize on window |
| The timeline stays empty | The runtime is not serving events, or the mcp port map is missing |
Check the port map; under run, set KASM_RUNTIME_OBSERVE=1 |