Reference
Changelog
For anyone upgrading or deciding whether to adopt: what each release changed and what is merged but not yet released, rendered from CHANGELOG.md so it cannot drift. The format follows Keep a Changelog and the project follows semver.
The Unreleased section below lists what is merged and not yet published to npm. Everything under a dated heading ships in that release.
Unreleased
0.7.1 · 2026-09-12
Fixed
deck-annotatenever keeps a collapsed frame as the picture rectangle. The letterboxed rectangle the markers are placed against was measured from aResizeObserveron.frameplus the image'sloadevent, and a single missed or early notification froze it : a measurement taken while the frame was still collapsed (or none at all) stayed published forever, and every badge sat next to what it names. It now re-measures on a bounded animation frame loop until the geometry holds still, observes the host, the figure and the image as well as the frame, re-arms itself when the element is moved in the DOM (firstUpdatedruns once, so a re-attached figure had no observers left), and settles again once web fonts land.
deck-annotatemeasures synchronously before its settle loop, so the first paint already places the badges against the picture; the animation frame loop only re-measures when the geometry moves.- The CLI walker gives a deck time on a busy machine. A slide change may take 15 s and a deck 60 s to load before
render,checkorexportgives up (shared constants inbin/lib/browser.mjs), instead of the 5 s and 30 s that failed under load with no defect in the deck. package.jsondeclares itsbinpath in the form npm keeps and the lockfile carries the@emnapientriesnpm cirequires, so a fresh install on a runner no longer fails before the tests.- Bundled examples regenerated on the current runtime, so the drift guard in the browser suite passes on a clean checkout.
Changed
- CI runs the browser suite with one Playwright worker and the smoke test reads the deployed bundle from a file instead of a pipe, removing two sources of false negatives.
- The site serves Markdown and text with an explicit UTF-8 charset, so the published LLM reference reads correctly in a browser.
- The site tells the product's story for two authors. The landing opens on the manifesto argument for a human author and a coding agent, shows the example decks with thumbnails rendered by
rikiki render, and its layout library, command rail, module shelf and closing block carry their own weight again; the docs gain a "Check and deliver" CLI page, a sidebar in reading order with build-time counts, a changelog rendered from this file, and a component catalogue guarded attribute by attribute.
0.7.0 · 2026-09-12
Added
deck-versusgains afooterslot in slide mode. Renders full width under both sides, at reading size, separated by--deck-versus-footer-gap(defaults to--rik-space-3); a slotteddeck-calloutkeeps its own size. Hidden outsideslide, liketitleandlead.deck-annotatebadges accept a named anchor, not just pixels.offsetandoffsetstakeabove,below,leftorrightnext tox,yCSS pixels, mixable in one list (offsets="above|0,-40|right"). A keyword displaces the badge by its own rendered diameter plus one--deck-annotate-anchor-gap(defaults to--rik-space-2) in that direction, and turns the leader on for that mark whether or notleaderis set · the author states the intent, the component measures the badge to place it.parseOffsetlives insrc/shared/annotation-marks.ts; an unreadable token still falls back to0,0.deck-sourceatom for crediting evidence blocks. Place it under anydeck-csv,deck-table,deck-bar,deck-kpi-grid,deck-annotate, or plain prose ·<deck-source href="…">Word, p. 12</deck-source>renders the credit line, linked whenhrefis set. It is a core atom, not an opt-in module.deck-figurenow renders one internally for its ownsource/source-hrefattributes, so both paths share one implementation; its--deck-figure-source-colortoken keeps working, forwarded to the new--deck-source-color.deck-annotateis now a figure too.caption,sourceandsource-hrefrender a real<figure>/<figcaption>around the screenshot, legend and marks, crediting it with the samedeck-sourceatomdeck-figureuses ·--deck-annotate-gapand--deck-annotate-caption-colordefault todeck-figure's own--deck-figure-gapand--deck-figure-caption-color, so both authoring paths read as one line under either kind of image. Marker placement is unaffected:_measure()still reads the.framebox, not the figure the caption grows.rikiki render --baseline <dir>says what moved since an earlier render. Each fresh PNG is compared to the same-named capture in<dir>, in the browser that just took the pictures (both images on a canvas,getImageData, a pixel counted when its worst channel moves by more than 32 of 255) · no image library, no new dependency. Slides are ranked bychangedRatio, each with the bounding box of what moved; a file only one side has isaddedormissing, two captures of different sizes areresized.--threshold(percent of pixels, default0.5) is what keeps anti-aliasing noise out of the report ·--threshold 0lists every pixel change.--jsonwrites the report to stdout, and it is always written todiff.jsonbesidemanifest.json. Exit 1 on a changed, missing or resized slide; exit 2 on a baseline directory that is not there.rikiki check --stepsmeasures every revealed state of a slide, not just its opening one. Each slide is walked from its opening state through every stateadvanceStep(ArrowRight) reaches, running the same diagnostics on each; every diagnostic carries astate(0for the opening one) in the JSON and· state Non the human line, and a diagnostic identical on code, slide and path/message across several states of one slide is reported once, at the lowest state it held.renderandchecknow share one walker (goToSlide/advanceStep, moved intobin/lib/browser.mjs) instead of each driving the deck on its own.rikiki checkreports content that paints outside its box or on top of a sibling.CONTENT_ESCAPES_BOX(error) fires when a painted box leaves its nearest painted ancestor by more than 4px on any side and nothing clips it;CONTENT_OVERLAPS_SIBLING(error) fires when two unrelated painted boxes intersect by more than 8px on both axes.CONTENT_CLIPPEDonly sees an ancestor whose overflow is hidden, and most layouts never set that: a box simply too small for its content just paints past its own edges instead, silently, and the last sentence of a card can land on the callout below it with nothing in the report to say so.rikiki checkreports two graph nodes painted on top of each other.GRAPH_NODE_OVERLAPS_NODE(error) fires when twodeck-nodeof the samedeck-graphintersect by more than 4px on both axes, naming both ids and the overlap in pixels.GRAPH_NODE_OUT_OF_BOUNDSalready measured a node against its canvas; nobody was measuring the nodes against each other, and a node hidden behind another is a node nobody reads.deck-figure· a screenshot, diagram or chart with its own caption and source. OPT-IN, native<figure>/<figcaption>under the hood, so the image, its explanation and its credit stay one semantic unit instead of adeck-imagewith prose bolted beside it.altis required unless the image is declareddecorative; skip either and the element flags itself withdata-missing-altrather than silently shipping an unlabelled image.captionandsource(with an optionalsource-href) print as one baseline-aligned line,sourcein mono type so it reads as a credit and not as more caption.deck-graphedges now stop at the node's painted edge, not a fixed percentage gap. A wide or boxed node could swallow the arrowhead entirely, because the old geometry aimed at a point short of the node regardless of its actual size. Edges are now measured against each node's realgetBoundingClientRect()and meet its boundary. Three attributes come with it:route="ortho"ondeck-edgefor a right-angle path around nodes a straight line would cross,label-offset="x,y"to nudge an edge label off a crowded midpoint, andwidthondeck-nodeto force a label to wrap before it collides with its neighbour.- New attributes across five opt-in components.
deck-annotategainsleaderwithoffset/offsets(x,yCSS pixels,|-separated per mark) to draw a line from the exact point being called out to a badge displaced away from it, for screenshots where the badge itself would cover the detail.deck-personagainscompact(a smaller portrait and type for a supporting persona) andinline(name, role and context on one wrapping row instead of the opening third of a slide).deck-versusgainsslideto use the same before/after vocabulary as a complete slide, with its owntitle/leadslots and aneyebrow, instead of composingdeck-splitand twodeck-card.deck-calloutgainson-darkso a callout dropped into a cover, section or takeaway inherits readable inverse text and surface instead of the light-theme defaults going invisible.deck-stepgainsnote-position="below"to keep a step's supporting note under its label instead of squeezed into the gutter between steps. rikiki checkgains two graph-specific codes.GRAPH_NODE_OUT_OF_BOUNDS(error) fires when a node's painted box sits outside itsdeck-graphcanvas;GRAPH_EDGE_CROSSES_NODE(warning) fires when a straight edge's path enters another node's content box on its way between the two it connects. Both are read off the rendered layout, the same geometry the arrowhead fix above relies on, not inferred from the authoredatcoordinates.rikiki initwrites a deck you can edit, not only one you can ship. The default is now a source deck plus the runtime it loads, copied intorikiki/beside it. It needs nothing but Node · the previous behaviour, a single self-contained file, moved behind--standaloneand still needs the optionalrolldownpeer.--forceis what overwrites an existing deck; without it the command stops rather than replacing someone's work. mermaid and Shiki, ~12 MB together, are copied only when the deck asks for them.rikiki assemblebuilds one deck from ordered partials. The multi-file assembler was documented for a year asbuild/vite-deck.mjs, a pathfilesnever published: the instruction could not be followed from an install. It is now a command of the CLI, with-for stdout, a title-derived default output, alangoption, and a note on stderr when a configured href will not inline at bundle time. Itsthemeandbundledefault to therikiki/…spellinginitwrites, which is the onebundlerewrites.rikiki render· one picture per slide, plus a manifest. An agent cannot see a deck. This writes a PNG per slide, a dependency-free gallery, and a versionedmanifest.jsontying each picture to the slide index, id and title it came from. Slides are picked by number or id, the canvas size is explicit, and--stepscaptures each revealed state instead of only the opening one, which on a stepped slide is usually the emptiest. File names derived from a slide id are always safe; the manifest keeps the id verbatim.rikiki check· what is wrong, where, and what to try. Ten stable codes, a severity, the slide, an element path that reaches into the Shadow DOM, the measurement behind the finding and a suggestion.--jsonwrites a versioned report to stdout and nothing else, even when the deck is broken. Exit 0 clean, 1 defects, 2 could not look. The report names what was *not* checked, because silence about a check that never ran reads as approval. It will not call empty space a defect, and it does not claim to audit accessibility.- One browser layer behind export, render and check. Lazy Playwright, a local server on a free port, the narrowest served root that still holds the deck, error and missing-resource collection, a settle that waits on animations rather than on a clock, and both resources closed even when the command fails.
- A working guide ships with the package.
docs/llms/rikiki-workflow.mdis the short path from a brief to a file someone can present: an editorial contract to fill in before writing, a plan whose lines each carry a message and its evidence, nine compositions by intent, the render-and-check loop, the order to try fixes in, and the three delivery shapes. Every HTML block in it is assembled into a deck and measured byrikiki checkin the test suite, so a renamed component breaks the docs before a reader does. checkreports an attribute the element does not read.deck-metrictakes its label from its content, solabel="Budget consumed"was dropped in silence and the slide rendered a number with nothing beside it. The newUNKNOWN_ATTRIBUTEdiagnostic compares what is written against what the element observes *and* what its own stylesheet selects on, because an attribute can act through CSS alone.checkcompares the announced duration with what there is to say. A cover that promises twenty minutes over notes carrying two gets a warning. Speech runs at 100 to 130 words a minute on technical material, and those words live in<deck-notes>, so the slide count was never the right proxy. Reported as an estimate from the notes, never as a verdict.checkreports content no slot takes. Adeck-cardwritten inside adeck-feature-cardsthat sits in another layout is dropped: the slide renders blank and nothing said why.CONTENT_NOT_RENDEREDnames the element, the slot it asked for and the slots the parent offers. Components that read their own text rather than slot it, likedeck-codeanddeck-mermaid, are left alone.- Text inside
deck-codeis measured for size. Slotted content is measured in the light DOM, but this element rebuilds the author's own text into its shadow tree, where the size check never looked.
Changed
- Documented the "single record, field by field" recipe. §22 of the LLM reference shows a
deck-tablewithhighlight-rowsandrevealas the composition for one entity's fields, instead of a newdeck-recordcomponent. deck-kpi-grid/deck-kpi· the figures are one family, and the marked one is a mass. The grid now owns three rows (value, label, note) and every figure adopts them withgrid-template-rows: subgrid, so all the values share one baseline and one size, all the labels sit on one line, and a column without a note costs no height anywhere else. The value keeps the statement size it has always had · a fluid scale that grew with the viewport pushed a row of three three-line metrics off the bottom of a slide that used to fit, and it is reachable when a deck has room with--deck-kpi-value-size: clamp(2.25rem, 6cqw, 7rem). Atoneofaccent,ok,warnordangerputs the figure on the inverse surface with inverse digits, and says the tone in the colour of the label under it rather than recolouring the figure · a coloured number on paper is a different colour, not more emphasis;defaultandmutedpaint nothing at all.ruleddraws a divider that can actually be seen: its 1px--rik-border-defaulthairline was invisible at projection distance, so it is now a--deck-kpi-grid-rule-width(default 2px) in ink. It had also never rendered at all, for the unrelated build reason recorded under Fixed above, so this is a visible change for a deck that already carried the attribute: it gains the separators it asked for, and the--deck-kpi-grid-gapof padding that keeps a figure off its own rule. No attribute changed.deck-persona· the portrait block is the one mass. The initials were faint grey type parked left of the name, attached to nothing. They now sit in a square of the inverse surface in inverse ink at statement scale, or the photo fills the same square, so the person has a place on the slide; the name lines up with the block's top edge, and the context is the quiet line, gapped away from the identity rather than stacked flush against it.compactandinlineshrink the block and the name together instead of only the block, andon-darkflips the block to paper with ink initials. No attribute changed.- The authoring skill covers the whole job. It carried the wiring; it now carries the seven steps from brief to delivery, the editorial contract, seven graphic composition decisions with the failure each one prevents, a table mapping what a slide has to say to the element that says it, and the presentation mode: what the speaker window shows, what belongs in the notes rather than on the slide, and when a reveal is right.
- The guide states why assertion-evidence, rather than asserting it. The measured comprehension and recall results, and the two consequences that follow: a bullet list read aloud costs the room, and cutting is a design act.
- The engine's rules moved out of the component. A domain layer (
src/domain/) holds the slide/step position, the chapter outline, the 2D coordinates, the loop, the deep-link grammar and the zoom/pan arithmetic; an application layer (src/application/) holds the deep-link use case, the keyboard map and themouse-navselection; one adapter (src/infrastructure/) owns the URL. 165 of these run without a browser, so "what does ArrowUp do in a 2D deck at the top of a chapter?" and "can the reader drag the slide off screen at 4x?" are unit tests now.deck-rootnow asks it where to go and applies the answer. The layering and its five gates are recorded indocs/design/adr-001-deck-navigation-domain.md, and the dependency direction is enforced byscripts/architecture.test.mjsrather than by convention. No public API change. - Print promises are now backed by tests.
e2e/print.spec.tsreads the produced PDF back with poppler: page count, page geometry, text on every page, no chrome, and a rasterised check that the backgrounds printed. rolldownis no longer a production dependency. It serves the CLI only and weighed ~55 MB of native bindings on every install · it is now an optional peer loaded on first use, with an actionable message when it is missing.engines.nodeis declared (^20.19.0 || >=22.12.0).- Release tags run the same gates as
main. A tag pipeline previously ranpublish-npmalone · no typecheck, no lint, no browser suite, nodist/drift guard. It now waits on all three check jobs and refuses a tag that disagrees withpackage.json. - Published sizes corrected. The headline figure was
~14 KB gzipfor a runtime that costs 38 KB gzip once Lit and marked are counted. Every occurrence now states the measured initial load. - The site build no longer downloads anything. The post-build step that fetched
markedandmermaidfrom jsdelivr on every deploy, overwriting the pinned vendored artifacts, is gone · its reason to exist disappeared when the bundle started vendoring its dependencies locally.
Fixed
rikiki checkmeasured only the slide that was on screen.deck-rootlays out the active slide and hides the rest, and the default mode inspected the document once · every other slide reported empty rects, soCONTENT_ESCAPES_BOX,CONTENT_OVERLAPS_SIBLING,GRAPH_NODE_OVERLAPS_NODE,GRAPH_EDGE_CROSSES_NODEandGRAPH_NODE_OUT_OF_BOUNDS(the last one since it shipped, the others since this branch added them) fired only when the defect happened to sit on slide 1. Default mode now walks the deck with the samegoToSlidewalker--stepsuses and measures each slide in its opening state;statesInspectedstill reads the slide count, and no diagnostic carries astateunless--stepswas asked for. The inspection is scoped to the slide being measured, and what does not depend on which slide is showing (unknown tags, stray attributes, unslotted content, the notes word count) is asked once for the whole document instead of once per state ·--stepsused to re-walk every element of the deck on every state. A deck the walk cannot navigate no longer kills the command: it stops at that slide with a newNAVIGATION_STALLEDerror and the report still carries every slide measured before it, so--jsonalways has a report to hand back. A graph node's own label no longer reads as a painted box either: it sits two levels underdeck-graph, and the exclusion only looked at the nearestdeck-*ancestor, soGRAPH_NODE_OVERLAPS_NODEcame with aCONTENT_OVERLAPS_SIBLINGsaying the same thing. This is a coverage trade, not only a deduplication: nothing inside adeck-graphor adeck-annotateis compared against its neighbours any more, slotted content included, so a graph's insides are covered by the graph codes alone.GRAPH_EDGE_CROSSES_NODEno longer misses a visible crossing. The check re-derived a centre-to-centre segment of its own, tested it as a mathematical line against the node box shrunk by 2px, and ignoredroute="ortho"entirely · an edge running a pixel outside a node's corner, which a 4px stroke paints straight over, was reported as clean.deck-graphnow publishes the polyline it actually paints on eachdeck-edgeasdata-path(graph-relative CSS pixels, bends included), and the check tests that polyline, widened by half the stroke, against every node it does not connect. A runtime older than the attribute still falls back to the straight centre-to-centre test.- The CSS-in-JS minifier silently deleted the descendant combinator in front of a pseudo-class or pseudo-element.
minify-templates.mjstightened the whitespace around EVERY:, so:host([banded]) ::slotted(*)shipped as:host([banded])::slotted(*)· valid CSS that matches nothing. Three shipped rules were dead indist/and nowhere else::host([banded]) ::slotted(*)indeck-point(a banded bento cell aligning its children to the top of the band), and both:host([direction='row']) ::slotted(*)and the:not([no-connectors])) ::slotted(* + *)::beforeconnector indeck-step-list(a horizontal step list sharing the width and drawing the arrow between steps). The minifier now tracks whether it is inside a declaration block, so a declaration colon still collapses and a selector colon keeps the space in front of it;scripts/minify-templates.test.mjscovers the three shapes and the four real selectors. A follow-up fixed the same loss under CSS nesting::host { & ::slotted(*) { … } }tightened to&::slotted(*), because a colon inside a declarations block was always read as a declaration colon even when its own run opened a nested rule. deck-stepreads as a list, not as three stacked cards. The label was in the mono face for no reason, the number sat in a 16px accent disc that read as a speck from the back of a room, and each row was a white card with a shadow · the card kit, three times over. The number now carries the sequence at reading size in accent, the label is in the sans face, the note follows it instead of being pushed to the far edge, and a hairline separates the rows.- The tour deck's cover line was invisible. It was written
slot="sub"on a component that offers only a default slot, so the sentence never rendered. Found by the diagnostic above, on the repository's own showcase deck. - Three recipes in the authoring guide used attributes their components ignore, and one example deck promised an arrow head that is not drawn. Both found by the diagnostic above, on its first run.
- Text size is measured on the author's own text. A component's chrome is sized by the theme; telling an author to fix a span they never wrote fired on three shipped decks and helped nobody.
- A diagram is capped against the box it sits in, not the slide.
deck-mermaidcapped its SVG at 60% of the slide height, which ignores the padding of its own host: beside a two-line title the drawing outgrew the room left for it and the box clipped. Found by checking a recipe in the new guide. - The cover speaks the deck's language. Its four meta labels were French whatever the document declared, so an English deck opened on "PRÉSENTÉ PAR" while every other word the engine writes was in English. They now follow the document's
lang, and the per-label attributes still win over both. - The served root no longer admits a sibling with a similar name. The path check was a string prefix, so serving
/srv/deckalso admitted/srv/deck-secrets. Traversals, escaped traversals and prefix collisions are now covered by tests. - The starter deck no longer plants a phantom element. Its own prose said
<deck-*>, which the HTML parser turned into an element node.rikiki checkfound it in the deckrikiki initwrites, which is how it was noticed. - An assemble config is read whatever dialect the host project uses.
.jsis CommonJS or ESM depending on the nearest package.json, andnpm init -ywrites"type": "commonjs". A config that did not match crashed with a parse trace; both dialects now load, and a mismatch names the three ways out. --no-fontsdrops the whole font rule, not just its source. It left twelve@font-faceblocks declaringsrc: none, which is invalid CSS the browser discards anyway.- A missing optional peer prints its remedy, not a stack trace. The advice to run
npm i -D rolldownwas buried under six lines of package internals. - The published documents no longer send the reader to files they do not ship. The README opened on
cp starter.html my-deck.html, and the shipped skills pointed atbundle.mjs,npm run deckandexamples/rikiki-tour/· none of which exist afternpm install. A test now walks every published document and fails on any citation of an unpublished path. - The built site no longer carries the whole package.
site/public/rikikiwas a symlink torikiki/, so a build copied the TypeScript sources, the fixtures and 366 MB of node_modules intodist/: 400 MB published. A staging script now copies seven named entries, and a post-build check fails on node_modules, sources, development directories, build manifests or a site over 60 MB. The built site weighs 14.3 MB. - Opt-in components, outside the default bundle.
src/extras/holds components a deck loads on purpose, each its own module. The core stays at 34 registered elements and a deck that never uses them pays nothing.rikiki bundlefolds a loaded one into the single file like any other script, so a standalone deck keeps it and stays offline.deck-bar· a proportion, drawn. One value against a total (160 / 538leaves the rest of the track empty, which is the point), or a stack of categories on one track with its legend. The printed percentages of a stack always add to exactly 100; the rounding drift lands on the largest slice.deck-quote· someone else's words, attributed. Distinct fromdeck-punch, which is the speaker's own line. The attribution attribute isauthor-role, neverrole, which belongs to ARIA.deck-annotate· a screenshot the speaker can point at. Numbered markers positioned in percent, so they hold under zoom-to-fit, in the overview thumbnail and in the PDF. They reveal one per step through the engine's own step mechanism, with no plugin. The percentages are relative to the PAINTED picture, not to the element box: an image is letterboxed inside its box, and anchoring to the box put markers on the empty margin beside the screenshot.deck-agenda· the running order and where the talk is. It reads the deck's own chapter structure through the navigation domain, so adding adeck-sectiongrows a line and there is nothing to keep in sync.
- Nine more opt-in components, all outside the default bundle.
deck-icon(24 drawn glyphs, nothing vendored, pruned by the bundler to what the deck writes),deck-checklist/deck-check,deck-kpi-grid/deck-kpi,deck-pull,deck-persona,deck-versus,deck-flow/deck-flow-step,deck-timeline/deck-milestone,deck-table. deck-graph,deck-node,deck-edge· nodes and edges, the primitive behind every boxes-and-arrows slide. Unlike mermaid it inherits the theme, reveals with the engine's own steps, prints, and weighs 2.5 KB gzip instead of 1 MB. It runs no layout solver on purpose: an automatic layout moves every node when you add one, which breaks "source = output".at="x,y"in percent is the layout language, plusrowandcolumnfor the common cases.- One visual signature for all of them (
src/extras/signature.ts): an accent rule marks what matters, a mono uppercase micro-label carries the metadata, and structure comes from hairlines and space rather than filled boxes. The first pass rendered a row of identical tinted tiles, which gives a projected slide no hierarchy at all. - Icon curation in
rikiki bundle· the set is one JSON string literal, so pruning it to the glyphs a deck writes is an exact swap the CLI verifies, reported like the component curation already is. - Four knobs on components that already existed, chosen over four new elements that would have duplicated a vocabulary the project already has.
deck-splittakespivotandwinnerfor a directed comparison;deck-step-listtakesdirection="row"with connectors for a chain across the width;deck-csvtakeshighlight-rows,highlight-colsandreveal;deck-stattakescompactfor a row of figures. All opt-in, all absent by default. - A deck-wide density default, as a theme token (
--rik-slide-spread) rather than a deck attribute · density is a per-slide judgement, so the per-slidespreadstill wins andspread="theme"defers explicitly. - A guard against a backtick inside a
csstemplate literal (scripts/css-template.test.mjs). It closes the literal, and the parse error it produces points nowhere near the comment that caused it. Three debugging rounds went into that trap before the test did. - A slide budget guard (
e2e/slide-budget.spec.ts). The engine never lets content overflow, it CLIPS it, so an over-filled slide silently loses its last lines. Every shipped deck is walked slide by slide and a clipping box that loses more than a few pixels fails the build. Under-filled slides are measured and reported, never failed · that one is a judgement, not a defect. - Vertical distribution on content layouts. A three-line slide left 62% of the canvas empty, with no way to use it short of switching to a bento grid.
deck-feature,deck-splitanddeck-takeawaynow takespread(between/around/evenly/center/end/start) to share the leftover height between the blocks, andfillto give that height to the blocks themselves · paired with<deck-fit>the text grows into it. Both are opt-in and absent means unchanged. An unknownspreadvalue falls back to the documented default rather than dropping the layout. - Bento grid slides.
<deck-bento>lays out<deck-cell>children on a multi-row, multi-column canvas · cells take aspan(2x1), atone, and per-axiscol/rowoverrides. Each cell is a size container, socqw/cqhtype scales against the cell rather than the slide.<deck-csv>renders inline CSV as a styled table and<deck-fit>shrinks slotted text to its box. - Trust model, stated and tested.
SECURITY.mdand the shipped LLM reference now spell out the line: deck content is the author's code and renders as written; text *derived* from it is escaped. Frozen bye2e/security.spec.ts. - Embedded decks stop touching the host page. A
<deck-root>that is not a direct child of<body>no longer rewrites the URL hash, no longer captures the arrow keys until it is focused (it gainstabindex="0"), and lets the wheel scroll the page it sits in. The themes' reset, page background and helper classes (.accent,.lead,.display,table.dense…) are scoped to the deck subtree, so importing a theme no longer restyles the document around it. Pinned bye2e/embed.spec.tsagainst a host page that styles itself *before* importing the theme. - Several decks per document are supported, and now say so. Each keeps its own canvas, slide index and navigation; focus decides which one the keyboard drives. Verified by
e2e/multi-deck.spec.ts. - Accessibility baseline, tested. An Axe pass over the reference decks (
e2e/a11y.spec.ts) plus targeted keyboard assertions. The keyboard hint chips were<kbd>elements with click handlers, unreachable by keyboard; the overview grid was<div>cells, so it could not be opened, walked or used at all without a mouse. Both are real buttons now, with accessible names, a visible focus ring and arrow-key movement. A polite live region announces each slide change. - Three browser engines. Firefox and WebKit join Chromium in Playwright and pass the full base contract. They skip only what is genuinely Chromium-only: PDF printing and the presenter's multi-screen APIs.
- Print and PDF export. (fixed twice: the print rules were declared before the on-screen ones they override, at equal ID specificity, so the keyboard chips and the nav arrows printed in the corners anyway; and the export now carries a bookmark outline and a tag tree, so a viewer can navigate it page by page.) A deck now carries a real print stylesheet: one slide per page at the deck's own canvas size (A4 cropped a 16:9 slide), backgrounds kept, navigation chrome dropped, click-stages printed once fully revealed.
rikiki export deck.html --output deck.pdfdrives a headless Chromium, waits for fonts and mermaid diagrams, and reports any asset it could not load. Playwright is an optional peer dependency. rikiki bundle --with-mermaid/--with-shiki. The flags existed oninitonly, so a bundled mermaid deck fetched./vendor/mermaid.min.jsat runtime and rendered an empty diagram offline, with exit code 0. The command now folds the runtime in, or fails and names it.- Size, component-count and packaging contracts. Every published figure is measured from the artifacts it describes (
scripts/size.test.mjs,scripts/component.test.mjs) and the install cost is asserted (scripts/packaging.test.mjs). A page that drifts fails the build. - Going back into a slide lost its steps when a morph deferred the move. Crossing a
data-morphpair backwards landed on step 0 instead of the slide's last step. The engine decided "previous slide" and "its last step" in two statements; the morph plugin defers the first into a View Transition, so the second ran against the old slide and the deferred move then reset the step. The navigation model now returns one complete position, so the two cannot come apart. Reproduced and pinned bye2e/navigation.spec.ts. - A 2D deck wrote a deep link it could not read back. With
nav="2d"and steps, the engine wrote the linear form (#3.1for slide 3, step 1) and parsed it as "chapter 3, slide 1". Both sides now use the same grammar. - XSS through a mermaid error message. mermaid folds the offending source into
UnknownDiagramError, and that text reached aninnerHTMLsink unescaped in both the slide and the overview thumbnail. mermaid now also runs at itsstrictsecurity level, and the presenter escapes the theme URL and the inlined stylesheet it writes into its popup. - Four contrast failures Axe found. The cover's meta labels used a decoration-grade alpha (2.43:1), the eyebrow badge put white on mango (2.98:1), the faint text token sat at 4.30:1, and siliceum's link colour at 3.56:1. All four now clear AA, without inventing a new brand hue.
- Status tones failing WCAG.
deck-punchanddeck-statrendered text in surface-grade tones ·oksat at 2.15:1 on the rikiki theme. Both now route to text-grade token companions that clear the large-text threshold on both themes. - CSV data loss. A quoted empty field dropped the whole row, a lone
\rnever terminated a row, a multi-character delimiter was ignored, and a row shorter than the header rendered an invalid table. deck-cellalignment axes.alignis horizontal andjustifyvertical (the cell is a column flex box) · documented, pinned by a test, and corrected in the showcase deck, which had them swapped.
0.6.0 · 2026-06-19
Added
- Slide zoom. Ctrl/⌘ + wheel, trackpad pinch and
+/-/0magnify the active slide around the cursor and pan it (drag or wheel), fixed-canvas only, on by default. Scales fonts and layout together (no reflow). Opt out withno-zoomon<deck-root>. - Per-slide fluid escape. A single slide can carry its own
fluidattribute to escape the fixed canvas and use the real viewport (no zoom-to-fit scale, no letterbox), while the rest of the deck stays on the canvas · for one slide that embeds a live interactive demo. The canvas is restored on navigation away. - Plugin hook API.
<deck-root>exposes a publicuse(plugin)method and aDeckPlugin/DeckContextcontract (steps,applyStep,navigate,setuphooks) so plugins extend the deck through a stable surface instead of patching engine internals.setDeckCodeHighlighter()is the matching hook for<deck-code>syntax highlighting. All three (DeckPlugin,DeckContext,setDeckCodeHighlighter) are re-exported fromdist/index.js. - Presenter multi-screen placement. With the Window Management API the deck goes fullscreen on the external screen (the projector) and the speaker window opens on the speaker's current screen · released when the presenter closes, with a fallback to leaving the deck in place otherwise.
- Preview (inert) mode.
previewon<deck-root>renders and letterboxes a deck but wires no input, autoplay or presenter handlers · used internally so the presenter's Current preview can act as a control surface without spawning a nested presenter. Ano-counterattribute hides the slide counter outright.
Changed
- Presenter Current preview is a live control surface. Keyboard, click and wheel events on the Current preview are forwarded to the projected deck, with an opt-in advance-on-click. The preview renders the inert
previewdeck. - Slide counter is hidden on cover, overview and blank screens (in addition to the new
no-counteropt-out). - Default slide typography scaled up for projection. Body, lead and heading sizes step up the type scale (body ≈32px on the 1080 canvas) in both shipped themes so text reads from the back of a room; the hierarchy stays monotonic.
- Presenter Current/Next previews are constrained to 16:9, so the laptop thumbnail matches the projected slide's geometry regardless of window shape.
- Presenter mode auto-hides the projected window's key-hint chips and nav arrows while the speaker window is open (restored on close).
- Click-stages and Shiki plugins migrated to the hook API. They no longer monkey-patch
deck-root/deck-codeprototypes.installClickStages()andinstallShiki()keep working as back-compat shims that attach the plugin to every<deck-root>already on the page · a deck created dynamically after the call must now register the plugin itself viadeckRoot.use().
Documented
no-hintandno-arrowsattributes on<deck-root>are now in the reference attribute table (they already existed).
Fixed
- Wheel navigation no longer swallows browser zoom. Ctrl/⌘ + wheel and trackpad pinch (which fire
ctrlKeywheel events) are left for the browser instead of beingpreventDefaulted for slide navigation. - Shiki-highlighted code keeps its colors. The vendored Shiki emits token colors as inline styles, so stripping them left every token monochrome · the palette is now preserved while the
class="line"wrappers survive for step dimming. - Presenter places the speaker window on the right screen on the first press and closes its
BroadcastChannelon teardown, so reopening no longer doubles forwarded input. - Shiki reference docs no longer mention a non-existent
cdnoption · the highlighter loads from the vendored offline bundle, not a CDN.
0.5.0 · 2026-06-12
This release reworks the rendering model. Every deck now renders into a fixed logical canvas scaled uniformly to fit the viewport, with an opt-in fluid mode for decks that should reflow like a web page, and embedded decks no longer disturb the page that hosts them.
Added
- Opt-in fluid mode.
<deck-root fluid>makes a deck fill its box and reflow like a web page · no logical canvas, no zoom-to-fit scale, no letterbox. It can be toggled at runtime. The default stays zoom-to-fit. - Playwright render net. A browser-level test suite (smoke, navigation, scaling, embedding) loads every fixture deck and asserts the engine upgrades, slides render, navigation works and the host page is left intact.
Changed
- Uniform zoom-to-fit is now the default rendering mode. Every deck renders into a fixed logical canvas (1920×1080 by default, set via
width/height) scaled uniformly to fit, so a slide's layout is identical at any window size and letterboxed when the screen aspect differs. Decks previously stretched fluidly to the viewport; author fluid layouts now opt in withfluid. - Embedded decks leave the host page alone. The framework's global baseline (scroll lock, rem sizing) is now scoped to full-page decks, so a
<deck-root>placed inside a larger document no longer hijacks the page's scroll or typography. - Zoom-to-fit measures the deck's own box. Scaling is driven by a
ResizeObserveron the host instead of the window, so an embedded deck scales to its container and re-fits on container resize.
Removed
- The
fixedattribute. Zoom-to-fit is now the only canvas mode, sofixedno longer has any effect and has been removed. Decks that still carry it render identically.
Fixed
- Letterbox bands now match slides whose background has a zero blue channel (black, red, yellow). The opacity check parsed the blue channel as the alpha, so those slides wrongly fell back to the page surface for their bands.
- The deck no longer leaves marks on the host page. Removing the last deck from the DOM restores the page's scroll and rem baseline; changing
width/heightafter first render re-fits the canvas; a deck moved or re-attached in the DOM keeps rescaling on resize.
0.4.0 · 2026-06-12
Added
- Release tooling:
npm run bump <version>rewrites every version surface at once (package.json/lock, CHANGELOG, site changelog, demo deck, doc stamps), backed by a shared manifest (scripts/version-surfaces.mjs) and a Vitest suite (npm test) that fails when any surface drifts. Abump-versionskill orchestrates the release. - The docs now declare which release they document via a
rikiki v<version>stamp inREADME.md,llms.txt, and the LLM reference.
Changed
- The npm package now ships the LLM docs:
llms.txtanddocs/llmsare included in the published tarball, so the reference is available afternpm install rikiki-deck.
0.3.1 · 2026-06-09
Fixed
- Presenter mode showed the wrong, un-styled slides in single-file bundled decks. The presenter mirrors each slide in an iframe that reloads the rikiki bundle via
new URL('./index.js', import.meta.url). In a bundled deck that resolves to adata:URI; loaded as<script src="data:…">the module's ownimport.meta.urlis that data: URL, where a top-levelnew URL(relative, import.meta.url)throws and aborts custom-element registration, so the iframes rendered raw, un-upgraded markup. The presenter now inlines the bundle as a module when the URL is adata:URI; file-served decks keep loading it via<script src>unchanged.
0.3.0 · 2026-06-08
Added
- Mouse navigation: click / Shift-click, scroll wheel (yields to scrollable content, navigates at the edge), bottom-right chevrons, and mouse back/forward buttons. Configurable via the
mouse-navattribute (none, or a subset ofclick wheel arrows aux). - Click-stages plugin reveals (
installClickStages()):data-click,data-click="N",data-click-hide,data-click-auto,data-click-stagger,data-click-children, per-elementdata-anim*, anddata-morphMagic Move (View Transitions, with a WAAPI FLIP fallback on Firefox). - Overview grid (
O): viewport-faithful thumbnails, type-to-filter search, and click-to-jump. Mermaid diagrams render in their thumbnails. - Clickable bottom-left key-hint chips (
← → O P ?): each chip now triggers its action (back / advance / overview / presenter / help), not just hints the key.
Fixed
- Overview thumbnails · mermaid/SVG rendered unstyled. Cloned slide SVGs get their ids namespaced per-thumbnail to avoid collisions, but the
<style>selectors weren't rewritten, so mermaid's id-scoped rules (#mmd-N …) stopped matching and the diagram fell back to black.namespaceIdsnow rewrites#idselectors inside<style>blocks too. A thumbnail build that fails retries on the next scroll-into-view, capped so a deterministic failure can't re-warn forever. - Deep links clamp to the nearest valid position instead of resetting to the first slide: an out-of-range slide/chapter index lands on the last one, and a step past a slide's range settles on its last step (keeps a bookmarked
#4.3usable while iterating). - Wheel navigation yields to scrollable descendants, including inline
<svg>(the check was limited toHTMLElement), and navigates once the element reaches its scroll edge instead of trapping the wheel inside it. - click-stages edge cases:
data-click-stagger="0"flips children simultaneously (was falling back to 80 ms); stagger children honordata-click-hideanddata-anim-delay;data-anim-delayis no longer applied twice on auto/stagger entries; delayed reveals no longer flicker during adata-morphview transition; morph visibility is judged from computed style so CSS-class visibility counts; a synchronousstartViewTransitionthrow no longer strands the animation/morph state. - click-stages step counting is unified on the entry walk, so the step count can no longer disagree with the reveals (no unreachable stage / dead step), and an explicit
data-click="N"floors the running cursor so a later bare element lands after it in document order. Morph visibility prefers the inline target opacity over the mid-transition computed value, so a fading element isn't briefly mistaken for the visible one.
Changed
- 2D navigation is now opt-in via
nav="2d"on<deck-root>(it was auto-enabled whenever a deck had 2+ sections). Without it, arrows stay linear, so adding a<deck-section>no longer silently remaps←/→to chapter jumps.<v-clicks>wrapper elements remain unsupported · rikiki drives reveals with thedata-clickattribute. - The CSS-in-JS template minifier is extracted to a shared
minify-templates.mjsimported by bothbuild.mjsandbuild-standalone.mjs.
0.2.0 · 2026-06-02
Added
click-stagesplugin: per-element click-through reveals on any element, in the spirit of Slidev'sv-click. Attributesdata-click,data-click="N",data-click-hide, anddata-anim="fade|slide-up|slide-left|scale". Opt-in viainstallClickStages(), reverses cleanly, honorsprefers-reduced-motion.- Multi-file deck assembly:
build/vite-deck.mjsstitches ordered.htmland.mdpartials listed in adeck.config.{js,json}into one deck. A single.mdfile can hold many slides, split on lines that are exactly---(reveal.js convention). ?liveopt-in: append?liveto a deck URL and the core lazy-imports the livereload poller. The poller now watches the document's own stylesheets (same-origin only) instead of a hard-coded path.- LLM-facing reference (
docs/llms/rikiki-reference.md) and arikiki-deckauthoring skill, surfaced from the documentation site.
Fixed
- Presenter window showed slides unstyled. The rikiki bundle URL is now derived from
import.meta.urlinstead of a fragile theme-path guess, the mirrored slide is markedactive, and the popup grid clips its panels and goes single-column on small screens. deck-rootno longer throws aSecurityErrorwhen navigating inside asrcdoc/ sandboxed iframe; thehistory.replaceStatecall is guarded.
Changed
npm run buildregeneratesdist/standalone.jsas well (viabuild-standalone.mjs), so the Lit-inlined bundle no longer drifts out of sync with the source.
0.1.1 · 2026-05-29
Fixed
<deck-code>heroandnestedvariants now honor the documented--deck-code-padding-x/--deck-code-padding-ycustom properties. They were hard-coded, so deck authors could not tune the padding as the component contract promised. Defaults are unchanged.
0.1.0 · 2026-05-21
Added
- Initial public release.
- 20
<deck-*>Web Components (cover, section, hero, hero-detail, split, hook, md, code, callout, card, mermaid, badge, metric, tier-list, step-list, kicker, stack, grid, punch, root). - Two ship-ready themes:
rikiki(default, tropical-jungle palette on dark) andsiliceum(warm paper with a yellow accent). - 2D keyboard navigation:
←/→between sections,↑/↓within a section,Spacefor linear progress, with linear fallback at deck edges. - Path-style overview mode (
O), one row per chapter, sub-slides flowing right. - Live-reload helper (
?live) ~70 lines, no WebSocket. rikiki/dist/is versioned so deck authors run zero build.
Next
- Contributing · how a change gets from a branch to one of these entries.
- Install · pick up the released version.