Build panels
Testing and debugging
Three ways to run a panel while you build it: a folder loaded in Deska, a dev server on localhost, and the preview tool from the examples repository. Plus the checks, the browser tools, and the failures people hit most.
Load a folder in Deska
Open the Panel Library (Cmd/CtrlShiftE), go to Community, then Build a panel, and choose Load a folder. Pick the folder that holds panel.json. This is the runtime your panel ships to, so every panel should pass here before it is published.
- Deska checks the folder with the same rules deska.dev uses, and lists every problem on the folder’s page under What to fix.
- It serves the folder with the same policy as an installed package and reloads the panel whenever a file changes, a moment after you save.
- The panel holds exactly the permissions its panel.json asks for, as it will once people allow them. The folder’s page shows what they will be asked.
- It keeps its own storage and files, apart from any installed version of the same panel, and
init.panel.devistrue. - Load the folder panel.json is in. A folder of 5,000 files or more, like a project root with its
node_modules, is refused and never watched. - A loaded folder is never pinned, synced to your account or published as it is.
A dev server on localhost
Running Vite or another dev server with hot reload? In Build a panel, under A page on localhost, enter its address, like http://localhost:5173. Only localhost and 127.0.0.1, over http or https, are accepted.
The frame is still sandboxed: no localStorage, the same focus rules, links through deska.openUrl. Load /__deska/sdk.js from your own dev server: copy the preview tool’s tools/preview/sdk.js into your public folder: it is a copy of the SDK Deska serves.
The preview tool
The examples repository has a preview host that runs a panel in your own browser, next to your editor. It needs Node 20 or later.
git clone https://github.com/0xPasho/deska-panels-examples.git
cd deska-panels-examples
npm install
npm run preview -- path/to/my-panelOpen the address it prints (http://127.0.0.1:5170/ by default). The preview:
- serves each panel on an origin of its own with the same Content-Security-Policy and sandbox as Deska, and the SDK at
/__deska/sdk.js; - plays Deska’s side of the bridge: theme, visibility, storage, files, notify, links, clipboard and the Extras row, with Deska’s limits, so a call Deska would refuse is refused here too;
- lets you switch the theme (dark or light), the place (desk or Extras), visibility and idle, reload the panel and clear its storage;
- logs every call and its answer in a bridge log beside the panel.
Checks from the command line
npm run preview -- panels/team-clocks # one panel, several, or none for all
npm run preview -- panels/net-radio --port 5300
npm run validate # Deska's package checks on every folder in panels/
npm run check # the checks, then a headless load in dark and light
npm run check -- team-clocks # one panel from panels/validateruns Deska’s package checks on every folder inpanels/, without a browser, prints each folder’s problems and fails when any folder is refused.checkruns the checks, then loads each panel headless in the preview, in dark and light (and in Extras when it has a row). It fails on a console error, a policy violation, a page that never callsdeska.ready(), or a refused call, and writes screenshots todocs/screenshots/preview/.- Both look in
panels/. To check your own panel with them, put its folder there in your clone;previewtakes any path.
Console and browser tools
In the preview, open your browser’s developer tools. The panel is a normal frame there: pick it in the console’s context menu to run code in it, watch its requests in the network tab, and read policy violations, which name the directive that refused something (connect-src for a host you did not declare, script-src for a remote script or eval).
- Log refusals by code:
console.warn(err.code)in everycatchturns a silent failure into a line you can read. - Keep SDK Playground loaded next to your panel to try a call by hand and see exactly what Deska answers.
- Test the states people will meet: offline, a slow host, an empty first run, storage full, the panel hidden and shown again, reloaded mid task, both themes, and the Extras page if you have one.
Common failures
| What you see | Why | Fix |
|---|---|---|
deska is not defined | Your script ran before the SDK. | Load /__deska/sdk.js as a classic script first, your code as a module or after it. |
ready() never resolves | The page is open outside Deska and the preview, or the SDK tag points elsewhere. | Run it in Deska or the preview; the path is exactly /__deska/sdk.js. |
Failed to fetch | The host is not in network, the address is http, or the API does not allow any origin. | Declare the exact host, use https, and pick an API that answers Access-Control-Allow-Origin: *. |
| An image, font or video does not show | Its host is not declared, or it is a remote stylesheet. | Declare the host; ship fonts as woff2 in the package. |
SecurityError on localStorage | The sandbox has an opaque origin. | deska.storage |
| A library fails with a policy error about eval | Code built from text at run time is refused. | Use the library’s precompiled build. |
| The panel says it was disconnected | A link, a form or a location change navigated the frame. | deska.openUrl for links, preventDefault() on submit, then Reload. |
not_focused from openUrl or clipboard.write | Called from a timer or after a slow await. | Call it in the click handler, first. |
A download link (<a download>) does nothing | The sandbox refuses downloads. | Save the file with deska.files.save from the click. |
| The first click does nothing | On the canvas, the first click focuses the panel. | Expected. The next click reaches the page. |
invalid_args from setTitle | null, an empty string or more than 60 characters. | Shorten it, or set init.panel.name to clear it. |
permission_denied from extras.set | panel.json has no contributes.extras. | Declare it, then reload the folder. |
| State is gone after moving the panel | Moving, docking and updating reload the page. | Keep state in deska.storage and rebuild from it. |
| Sound will not start after a reload | Audio needs a press in the panel first. | Show a play button when play() is refused. |
| It works on localhost and fails as a folder | Localhost pages skip the policy. | Fix what the folder’s policy errors name. |
| The folder is refused as too big | It holds 5,000 files or more, like node_modules. | Load the folder with panel.json, with only what the panel ships. |
Next
When the folder passes in Deska and looks right in both themes, read Publishing.