llms.txt · git:20260927.c36c2e1 · 2026-09-27 · sha256 33e5feb8a6627022
llms.txt git:20260927.c36c2e1A
Immutable. This exact content never changes and is served at /api/v1/blob/33e5feb8a6627022.
# decklet
> Agent-generated, brand-true, real-time editable presentations and assets — one portable HTML file, no office suite.
decklet is a slide engine for agents. The deck is a JSON model; the renderer is the editor; the output is ONE self-contained .html (no dependencies, no network) a human can drag, retype, present and print. Plain Node >= 22 CLI. Playwright is optional and only used by verify/pdf/import-html.
Loop: `node bin/new.mjs --out model.json --slides 8 --density reading` (a starter model + MANIFEST.md, both validate/verify clean) -> content + format + style -> model.json -> `node bin/validate.mjs model.json` -> `node bin/create.mjs --model model.json [--style style.json] --out deck.html --format slides|carousel|carousel-4x5|document-letter|document-a4` -> `node bin/verify.mjs deck.html [--refs shots/]` -> hand-off. Revising a deck a human edited: `node bin/edits.mjs deck.html` (read the in-file edit log) -> `node bin/create.mjs --model model.json --out deck.html --from deck.html` (keeps deck/slide/row ids, replays the human's edits — human wins, conflicts reported) -> verify. The migrate is the LAST step, never the first.
Formats: slides (960x540 or 1600x900) supported; carousel (1080x1080, 1080x1350) and document (Letter 816x1056, A4 794x1123) experimental (sizing/editing/PDF work; text does not flow across pages).
Model: {w,h,format,styles:{roles,margin,pad},slots,layouts:{name:{slot:{x,y,w,role}}},master:[rows with id],slides:[{layout,template,fill,textOnly,kind,bg,hide,els:[rows]}]}. A row is {x,y,w,role,text} plus optional box/tile/bar/line/donut/svg/img/icon/chart/html/anim/nowrap/p/bg/bd/radius/align/color/weight/tt. A logo row is {logo,name,x,y,h,col,gap,plate,aspect,monogram}: the name lines up at x+col+gap whatever the logo's shape. deck.assets = {id: data URI}; any img/logo value may be '#id', so an image is embedded once. after:'<rowId>' puts a row's x at that row's rendered right edge + gap (10). img fits contain by default. icon takes any Lucide name. chart mark is bar|hbar|line; a bar/hbar datum may carry a logo. Twenty list templates take media keys m1..mn (a logo, img or icon per item). Plan one non-text visual per slide: validate warns on a text-only free-row slide, and on a deck.entities name with no logo beside it. anim is one of rise|fade|pop|wipe: entrance motion on slide ENTRY, staggered 120ms in model order, off under prefers-reduced-motion and absent from print/PDF/contact sheet/verify (they draw the settled frame); any other value is a validate error. Eight roles (Title,Supertitle,H1,H2,Body,Caption,Label,Stat) are the only source of font/size/lh/ls; rows may not override them (no H3, no Subtitle; Title = display headline for cover/closing slides, H1 = content-slide title). styles.margin is the content inset chrome sits on; master chrome is deck-wide and never varies per layout; a slide override row is partial (only the changed props).
PDF: the ⤓ button writes a true slide-sized PDF inside the file (foreignObject -> canvas -> JPEG -> PDF, zero dependencies; verified in Chromium, Safari unconfirmed -> falls back to print). ⌘P is the paper path: named Letter/A4 pages, one slide per page.
## Files
- SKILL.md: the agent authoring skill — a START HERE routing block, then inputs, process, model + style contracts, layouts, templates, density, graphics, verification thresholds, anti-patterns (~7,000 words, the authoring path only)
- docs/building.md: the recommended build loop — a coverage manifest written before the model, then a mandatory second pass over the verify PNGs (tick the manifest, then shape / facts / chrome); the shape rules, the number-tracing rule and the polish rules a parity study found
- docs/editor.md: what the deck file does once a human opens it — the HUD manifest and every control, the contact sheet, presenting, both PDF routes, persistence (File System Access, Safari, the amber dot), versions, revising an edited deck, motion, the bug door
- docs/verify.md: what layout parity measures — the five collision shapes in full, and every validate/verify threshold with its pass criterion
- docs/charts.md: the chart row's drawing rules (bars from zero, one explicit max, direct value labels, no legend, annotations)
- docs/connectors.md: the shapes a deck may draw — a connector is a stroke with a head; orthogonal runs, S-curve channels, control points, termination, weight, fan-out, entry
- docs/figures.md: diagramSlide()/diagramRows() spec API and the nine figure templates
- docs/logo.md: the logo row (fixed column, contain fit, plate, aligned name, monogram) and what validate checks
- docs/assets.md: decklet-assets (bin/assets.mjs): logo <name|domain>, shot <url>, monogram <name>; manifest.json with aspect and plate
- docs/examples.md, docs/import-html.md: four worked briefs end to end, and finished HTML pages into a model
- README.md: what/for whom/guarantees/feature matrix/roadmap
- deck.html: the engine with its own explainer deck (12 slides, incl. the motion vocabulary and three inlined GIF clips of the editor) — live at https://grunion-ai.github.io/decklet/deck.html
- docs/demo.gif, docs/demo-poster.png: README demo recording, a build product of deck.html via docs/record-demo.mjs (Playwright + ffmpeg)
- docs/record-clips.mjs: films the three editor clips on the explainer's "filmed" slide from deck.html and writes them into examples/explainer/model.json as data: URIs (Playwright + ffmpeg)
- docs/editor.md: the editor reference — the HUD manifest (a machine-checked list the gate compares with the template), phones, the contact sheet, both PDF routes, PNG export, presenting, persistence, the flagged-word panel and the bug door
- template.html: the engine with an empty model; create.mjs fills the /*DECK*/ /*TOKENS*/ /*KEY*/ /*ENGINE*/ /*LOG*/ /*SPELL*/ markers (the /*EDITS*/ block is the in-file edit core, shared with lib/edits.mjs; the /*BUG*/ block is the bug-report builder, shared with lib/bug.mjs)
- bin/new.mjs: writes a starter model.json (format, canvas, margin, master footer, 3-12 slides up the shape ladder, placeholder copy) plus MANIFEST.md, and copies a style kit with --style; the output is validate --strict and verify --strict clean untouched
- bin/validate.mjs: pure-Node model contract validator (exit 1 on errors; --strict for warnings); includes the gap gate — declared boxes owe neighbours styles.gap (4px), estimates warn with ~
- bin/create.mjs: model (+style) -> deck.html with format presets
- bin/verify.mjs: self-containment + contract + layout parity (always) + AE vs refs (optional); writes results.json
- bin/pdf.mjs: deck.html -> vector PDF via Chromium print engine + px @page (one slide per page, text stays text, hrefs are /Link annotations, HUD hidden by the deck's @media print); gates pages/ratio/HUD/links, exit 1 on failure. The ONLY way an agent makes a PDF — never screenshots stitched by ImageMagick
- bin/edits.mjs: prints the human edit log a deck file carries (read before revising)
- bin/assets.mjs: decklet-assets CLI; lib/assets.mjs holds its pure half (slug, SVG bbox fit, plate detection, PNG decode, ranking, manifest)
- lib/logo.mjs: logo row geometry (logoGeom, plateOf, monogramOf), shared by the runtime, validate, charts and templates
- bin/export.mjs: deck.html --png -> one PNG per slide at native W×H (--scale 2 for retina; --out dir; 01-<slide name or slide-N>.png), HUD hidden, selection cleared, animations off, spell marks off, page counter kept; gates HUD/count/dimensions, exit 1 on failure. The way an agent makes per-card carousel images — never screenshot stitching
- lib/bug.mjs: node side of the bug-report builder (bugReport/bugFacts/scrub lifted from template.html's /*BUG*/ block; engineOf reads a deck's ENGINE literal)
- bin/bug.mjs: decklet bug — prints a prefilled mailto to decklet@grunion.ai ([ISSUE] subject with --category and --desc; engine version, format, size, slide count, node + OS, a scrubbed --log snippet); never the deck's words
- lib/spell.mjs: the build's spellchecker — flags(deck, correct) and flagMap(deck, correct) -> {word: [up to 5 suggestions]} written into /*SPELL*/; loadChecker('en') loads the optional nspell + dictionary-en peer (null when absent, and the deck then carries {}); deck.spell.ignore silences a word, case-blind, and the editor's panel appends to it
- lib/edits.mjs: node side of the in-file edit core (stampIds/diffDecks/applyLog lifted from template.html's /*EDITS*/ block; blockOf/putBlock for DECK/LOG)
- bin/import-html.mjs: HTML pages at a fixed viewport -> model.json (master/slots/roles lifted, Title detected on non-content layouts; _lines intent for parity)
- examples/explainer, examples/quarterly-update, examples/launch-carousel, examples/one-pager: brief.md -> model.json (+ style.json)
- test/gate.test.mjs: node --test gate (engine contract, validator, create, import, live proofs); test/edits.test.mjs (ids, log, migrate, create --from); test/editor.test.mjs (live: nibs, PDF arrows, autosave, position, migrate on load, ⌘S write-back, ⌘B); test/autosave.test.mjs (continuous write-back, the storage tier chain, two windows converging); test/mobile.test.mjs (Pixel 7 / iPhone 14 emulation: touch edits persist, an app switch commits); test/embed.test.mjs (same-origin, cross-site and srcdoc iframes)
- CHANGELOG.md, LICENSE (MIT)