Slide decks, described as data β and the Reactor plugin that puts them in any Reactor shell.
spec (.ts, YAML-shaped)
β validation
template branding, palette, type scale, layout overrides
β
slide library reusable semantic slide types
β
@revealjs/react β Reveal.js
A spec says type: metrics; how a metrics slide looks is the template's
business. That is what lets one deck be an application deck, a conference talk
and a dark projection deck by changing one word.
pip install datalayer_decks
datalayer decks serve # http://127.0.0.1:8797, opens the browser
datalayer decks serve my-deck.yaml # load YAML and open this deck directly
datalayer decks listInstalling the package adds a decks group to the Datalayer CLI β serve,
list, show, delete β through the reactor's datalayer.cli entry-point
group; datalayer-decks is the same group on its own. serve runs the decks
API (/decks, JSON specs on disk) and the interface built from
app/ from one origin. The interface is a Reactor shell: the decks
plugin in the sidebar and main slots, the plugins manager, the command
palette, and β at the header's right edge β the appearance menu of
@datalayer/primer-addons, the color mode and theme chooser the Datalayer
header wears. Set DATALAYER_DECKS_DIR (or --decks-dir) to choose where
decks are stored (~/.datalayer/decks otherwise).
A YAML file passed to serve is loaded into a temporary store and opened at
its deck URL, so trying a file does not alter the user's normal deck catalog.
Pass --decks-dir as well when the imported copy should persist. A diagram is
ordinary spec content, either as a full slide or inside a column:
- type: mermaid
title: From specification to screen
diagram: |
flowchart LR
YAML[Deck YAML] --> Validate
Validate --> Reveal[Reveal.js]Installed beside any other Reactor host, the same wheel publishes itself as an
extension: the routes join that host, and the Decks plugin β built by
extension/ as a Module Federation container into
share/datalayer/reactor/extensions/decks β is loaded into that host's shell
through federation, no import
needed. See python-packaged extensions.
import { registerDecks, DeckView, DecksHostProvider } from '@datalayer/decks';
import { DecksPlugin } from '@datalayer/decks/plugin';@datalayer/decksβ the engine: types,validateDeck, the slide library, the templates (datalayer,datalayer-brand,datalayer-ink),DeckRenderer,DeckView,DeckPrintView,DeckAccess, and two registries the host fills β the deck catalog (registerDecks) and the components a spec may name (registerDeckComponents).@datalayer/decks/pluginβDecksPlugin: a list in thesidebarslot, the open deck inmain, a "Decks" entry in the shell's view selector, and commands with keystrokes.
| Command | Keystroke | Does |
|---|---|---|
decks.list |
Mod+Shift+D | close the open deck, show the list |
decks.new |
Mod+Alt+N | the "new deck" dialog |
decks.open |
β | open a deck by id ({ id, slide? }) |
decks.rename |
F2 | rename the open deck (or { id }): title, address, family |
decks.delete |
β | delete the open deck (or { id }), after asking |
decks.goToSlide |
β | move the open deck to a slide ({ slide }) |
decks.nextSlide / decks.previousSlide |
Alt+β / Alt+β | move through the open deck |
decks.present |
Mod+Shift+F | the open deck, fullscreen |
decks.print |
Mod+Shift+P | the print view, for Save as PDF |
What the engine needs from the application around it β the theme, a footer
logo, whether the reader is signed in β arrives through DecksHostProvider,
with defaults that make a bare mount work.
Every slide may carry notes. Above a deck, Notes shows them to the
presenter (never in the PDF). When the host gives a voice
(DecksHost.useNarrator, a DeckNarrator), two more buttons follow
Download PDF:
- Play reads the notes aloud, slide after slide, as captions at the bottom of the slide; a slide without notes stays four seconds.
- Video makes the same into a video file, in the browser: each slide drawn
at 1920Γ1080 (fragments shown, fonts, pictures and Mermaid diagrams waited
for), each sentence voiced by the narrator's
synthesize(sentence)β the whole audio of one sentence, in a formatAudioContext.decodeAudioDatareads β and the deck recorded with the sentence being said as a caption, withMediaRecorder: MP4 where the browser records it, WebM otherwise. It records in real time, with its progress, Sound on/off and Cancel above the deck, and keeps going in a background tab. You see and hear it as it records β the deck on screen follows the slide being recorded, with its caption, as Play does, and goes back where you were at the end; Sound off silences the speakers only, the video keeps its sound. Then it offers the video and a WebVTT captions file with the same sentences and timings, named after the deck.
A host that keeps the video passes DeckView a videoPublisher
(DeckVideoPublisher: a label, publish(made, spec), and what to say once
published) β or gives it once, for every deck under it, as
DecksHost.videoPublisher (how a host keeps the video of a deck the plugin
draws; DeckView's own prop wins). Video then reads as the label, and the
video β with its captions and poster, the first slide as a 1920Γ1080 PNG β
is handed to publish as soon as it is made, with spec, the deck as it was
when Video was pressed, so the host can tell later whether the video is
still the deck's; a refusal shows its reason with Upload again.
The landing does this twice. On a deck's page in its space, the video is
saved with the deck (Spacer, PUT /api/spacer/v1/decks/{id}/video, in the
private bucket): the next time the page opens it plays above the slides with
Download, without being made again, and Video reads Make again β
the page says so when the deck was edited after it was made. Publishing that
deck to the Library offers Publish as a video, which copies the saved
video into the Library's public one. On a published deck's Library page, its
owner's Make the video uploads straight to the Library.
A narrator without synthesize gives no Video button. A narrator that
cannot speak for this reader (unavailable, with needsSignIn when signing
in is the way out) has both buttons say why rather than play or record.
Everything an agent may do with decks is declared by this plugin, as one
AgentTools bundle
(src/plugin/agentTools.json): the data β decks_list_decks,
decks_get_deck, decks_create_deck, decks_update_deck, the per-slide
edits, decks_delete_deck β and the screen β decks_open, decks_go_to_slide,
decks_next_slide, decks_previous_slide, decks_present, decks_print,
decks_list. Each is a command the plugin registers (DECKS_DATA_COMMANDS,
DECKS_COMMANDS), run on the page against the catalog and the store β saved
to this server when the plugin was given one β and answering with what the
model needs next. No agentspec
names a deck tool; example-decks declares tools: [] and gets them all from
the plugin it is mounted beside.
The Python half serves the same file from GET /plugins/agent-tools, and
registers the data half of the suite under the same ids
(datalayer_decks/commands.py, through provide_slash_commands) β so on a
host with no browser the commands the bundle names are commands the reactor
actually has, reachable from reactor commands list and reactor commands run decks.getDeck '{"id": "talks/hello"}' as much as from an agent. The seven
that move a deck on screen stay with the browser plugin: a store has no
screen.
In the datalayer decks interface itself, --reactor-plugins ai-agents
puts the Decks agent (example-decks) beside the decks: a floating chat on a
temporary inference key, with the bundle above as its tools, read from the
reactor rather than declared β see plugins/ai-agents.
example-decks and worker-pitcher (the landing's deck editor agent) name
the same frontend toolset, decks, in agentspecs.
In a Loop (@datalayer/agent-runtimes), @datalayer/loop-plugin-decks
mounts this plugin beside the chat β the list in the sidebar, a Deck
editor view, a menu on the composer β and the chat hands the agent the
bundle's commands as tools, so an agent writes and drives a deck with no
server behind it. The DecksAgent example shows it.
make install # pip install -e ".[test]"
make build-lib # tsc β lib/
make build-app # the interface β share/datalayer/reactor/apps/decks
make build-extension # the container β share/datalayer/reactor/extensions/decks
make wheel # both, then the wheel (hatch_build.py builds what is missing)
make test
make dev-app # Rsbuild on :5190, against `datalayer decks serve` on :8797
make example # serve examples/welcome/welcome.yaml directlyThe container build needs @module-federation/rsbuild-plugin (a
devDependency): npm install once in this package.
The deck definitions of a product stay with that product: the Datalayer landing keeps its specs, its registry entries and its design-system illustrations, and mounts this package around them.