Build panels
Extras
Give a panel a row in the Extras popover: a state line, a progress ring, a countdown Deska keeps running, a round control, and a compact page of its own.
What the row is
Extras is the popover on the canvas that holds Focus and Music. A panel that declares contributes.extras gets a row there, drawn by Deska like the built-in ones: the package’s icon, its title, one state line and one round control. The panel fills them; Deska draws them, so the row looks native and stays readable whatever the panel sends.
The row is a glance and a remote control. It shows what the panel is doing while its panel sits off screen or in another project, and it plays, pauses, starts or stops without the person going there. The closed Extras button never shows a package’s state, and the person can hide any package’s row with Hide from Extras on its card in Installed.
Declaring it
"contributes": {
"panel": { "entry": "index.html", "scope": "global" },
"extras": { "title": "Net Radio", "view": "extras.html" }
}titlenames the row, up to 40 characters, and cannot read as Deska’s own.viewis optional: a page that opens inside the popover when the row is opened. Without it, the popover shows the panel’s entry page.- Declaring Extras needs no permission. The panel’s page tells people it shows in Extras before they install.
What the row shows
Fill the row with deska.extras.set. Fields you leave out keep their value, null clears one, and anything else refuses the whole call with invalid_args.
| Field | Value | Shows |
|---|---|---|
state | string up to 60 characters, or null | The state line under the title, as untrusted text. |
progress | 0 to 1, or null | A ring around the icon. |
until | milliseconds since 1970, or null | The time left, counted by Deska. |
control | play, pause, start, stop or null | The round button, with that icon. |
What you set stays after every frame of the package closes, so the row keeps saying the last thing the panel knew. Clear what is no longer true.
A countdown that runs without the panel
Send the target time, not a string of minutes. Deska draws the time left from until and keeps counting with no frame of the package alive, so the panel never has to stay open or tick every second to keep the row right.
deska.extras.set({
state: 'Public beta',
until: Date.parse('2026-10-09T17:00:00'),
progress: elapsed / total,
control: null,
})Ship Countdown sends the soonest countdown this way, with the elapsed run up as the ring, and Noise Machine uses until for its sleep timer.
The control
A press on the round button sends extras.control with the control’s name to every open frame of the package. When none is open, Deska opens the panel and delivers the press once it is ready. Your handler decides what the press means, then sets the next control.
let sent = ''
function syncExtras(station, playing) {
const fields = {
state: playing ? station.title.slice(0, 60) : 'Stopped',
control: playing ? 'pause' : 'play',
}
const key = JSON.stringify(fields)
if (key === sent) return // nothing changed, no call
sent = key
deska.extras.set(fields).catch((err) => console.warn('extras.set:', err.code))
}
deska.on('extras.control', ({ control }) => {
if (control === 'play') play()
if (control === 'pause') stop()
})- Send only what changed. A page that ticks every second should call
extras.setonly when the row would change; calls count against the frame’s 20 a second. - Every frame hears the press. When the action must happen once (sound from one speaker, one write), agree through storage which frame acts, as Noise Machine does with a short lease.
- On another computer’s desk, where community panels do not run, a press waits for the package’s next frame on this computer.
The Extras page
Opening the row shows view (or the entry page) inside the popover, sized to it. It is a page of the same package: same SDK, same storage, same permissions. It knows where it is from init.place and deska.place:
const init = await deska.ready()
document.body.dataset.place = init.place // 'panel' or 'extras'- Keep it compact: the popover is narrow. Hide search fields and descriptions, as Net Radio does with one page and a
data-placeattribute. - The popover’s frame closes with the popover. Anything that must keep going (sound, a timer) belongs to the panel on the desk or to
until. - Two pages of one package cannot message each other. They share storage: read, change and write in one step, and re-read on each tick (Ship Countdown).
Examples with a row
- Ship Countdown: a view page,
until, the ring,stopandstart. - Net Radio: play and pause from the row, a compact layout for
place: 'extras'. - Noise Machine: a sleep timer, and one speaker across many frames.
- Service Status: a one line summary in
state. - SDK Playground: every field by hand, and the control answered from every frame.