Specificatie
Het deckformaat
De draagbare, geversioneerde vorm waarin een presentatie wordt geserialiseerd, zodat een tweede implementatie hem kan lezen, renderen en round-trippen zonder de server of de opslag van Deckyard.
Een deck is data, geen rendering. Het formaat is met opzet eenvoudig: slides zijn een platte reeks van { type, content }, en niets erin hangt af van de machine waar het vandaan komt.
De envelop
Vijf velden op het hoogste niveau. Al het andere zit in slides.
{
"format": "deckyard.deck",
"version": 1,
"title": "My deck",
"theme": "default",
"slides": [
{ "type": "eu.deckyard.slide.title", "content": { "title": "Hello", "background": "lime" } },
{ "type": "eu.deckyard.slide.quote", "content": { "quote": "A deck is data.", "attribution": "Deckyard" } }
]
} De envelop is inschikkelijk. Onbekende sleutels op het hoogste niveau worden door een importeur genegeerd en nooit geweigerd, zodat een nieuwere schrijver een veld kan toevoegen dat een oudere lezer simpelweg overslaat.
Alle vijf velden staan uitgeschreven in de documentatie: hun type, en wat een lezer ermee hoort te doen. Volledige referentie
Slides, en de ene spelling van een type-id
Een slide is een type plus een content-object waarvan dat type de vorm bepaalt. Een afwezig of leeg veld betekent "niet ingevuld": een importeur vult de standaardwaarden van het type in en maakt een verplicht veld nooit leeg. Draagbare slides hebben geen id, want ids horen bij opslag en worden bij import opnieuw gegenereerd.
slides[].type is de canonieke id van het type, en een type heeft er precies één: reverse-DNS voor een declarant met een domein (eu.deckyard.slide.title), namespace/naam voor een declarant zonder. De id noemt zelf al de definitie waartegen de slide geschreven is en mag een versie vastpinnen (@2), dus er is geen apart manifest om ernaast te leggen. Twee oudere spellingen - de kale registrysleutel title-slide en de gekwalificeerde core/title-slide - zijn restanten van vóór de convergentie, geen onderdeel van het formaat: Deckyard accepteert en normaliseert ze nog bij import, maar wat het exporteert is canoniek, en een tweede implementatie is ze niets verschuldigd.
{ "type": "eu.deckyard.slide.content", "content": { "title": "Why", "body": "..." } } Contentschema's
De contentvorm van elk slidetype wordt beschreven door een JSON Schema dat wordt gegenereerd uit dezelfde veldregistry die de validatie draait en het editorformulier bouwt. Eén declaratie, vier afnemers, en geen manier waarop een schema een vorm kan beschrijven die de software niet accepteert.
Ze zijn geversioneerd via hun $id, en die draagt de versie van de contentvorm (4), niet de envelopversie (1). https://deckyard.eu/schema/v4/deck.schema.json is het hele deck, https://deckyard.eu/schema/v4/slide-types/<type>.schema.json is één type, en https://deckyard.eu/schema/v4/index.json geeft je de lijst. Alle drie zijn ze op te halen: formeel hoeft een $id dat niet te zijn, maar voor een formaat dat je aan anderen aanbiedt als standaard hoort het toch.
Extra eigenschappen zijn toegestaan. De schema's documenteren de bekende vorm van een slide; ze wijzen de geschiedenis niet af. Onder een gepubliceerd versiepad wordt ook nooit iets teruggetrokken, want een type dat hier met pensioen ging, wordt in het deck van iemand anders nog genoemd.
Verwijzingen naar beelden
Afbeeldingen worden per string aangeduid. Een lokale upload is een serverpad en is alleen draagbaar zolang die server bereikbaar is. Een externe https://-URL is al draagbaar en wordt door elke transformatie met rust gelaten.
Het pakket: een deck met zijn pixels
Waar de JSON-export nog naar plaatjes op een server wijst, draagt het pakket zijn eigen pixels mee. Het rendert en round-trript op een machine die de installatie waar het vandaan komt nooit gezien heeft, en het kan precies opsommen welke bestanden erin zitten.
De indeling is gemodelleerd op OCF, de verpakking die EPUB gebruikt, en om dezelfde reden: een ZIP waarvan de eerste ingang een ongecomprimeerd mediatype is, valt te herkennen aan een magic number voordat er iets is uitgepakt. Vier ingangen, in deze volgorde.
mimetype
manifest.json
deck.json
assets/<sha256>.<ext> -
mimetype - Eerste ingang, ongecomprimeerd opgeslagen, met precies
application/vnd.deckyard.deckerin. -
manifest.json - Metadata van het pakket en de volledige inventaris van de beelden. De oorspronkelijke bestandsnamen blijven erin staan, zodat hash-churn nooit in de leesbare structuur lekt, en meerdere bronnen bij één hash betekent dat dezelfde bytes vanaf meerdere plekken werden gebruikt.
-
deck.json - De draagbare envelop, met elke verwijzing naar een beeld herschreven naar een plek in het archief.
-
assets/<sha256>.<ext> - De bytes van de beelden, geadresseerd op de SHA-256 van hun eigen inhoud. Identieke bytes worden één keer opgeslagen.
-
Zelfdragend
Elk lokaal bestand zit erin. Het pakket rendert offline, zonder server in beeld.
-
Op inhoud geadresseerd en verifieerbaar
De bytes van elk bestand hashen naar hun eigen verwijzing. De lezer hasht bij binnenkomst alles opnieuw en weigert een mismatch, dus een beschadigd of gemanipuleerd archief faalt hoorbaar.
-
Gededupliceerd
Identieke bytes worden één keer opgeslagen, hoeveel slides er ook naar wijzen.
-
Opsombaar
Het manifest noemt elk bestand dat een deck nodig heeft. Je kunt "wat zit hier eigenlijk in" beantwoorden zonder uit te pakken.
Elk veld in het manifest, en wat de import ermee doet, staat uitgeschreven in de documentatie. Volledige referentie
Wat gegarandeerd is, en wat lossy
Voor slides met inhoud is export naar import naar export een vast punt: na één normalisatieslag is de draagbare projectie stabiel, en identieke bytes hashen naar identieke adressen. Een test in de core-repo bewijst dat bij elke run tegen een meegeleverd voorbeelddeck. Twee randen zijn bewust wel lossy, en allebei zijn ze gespecificeerd zodat ze degraderen in plaats van crashen - en dat is precies wat het formaat veilig maakt om tegenaan te bouwen.
- Onbekend slidetype
- Importeert als plaatshouder die zegt welk type niet gevonden werd, of dat type bewust is uitgefaseerd en wat ervoor in de plaats komt, en die de oorspronkelijke inhoud als tekst meeneemt.
- Ontbrekend lokaal beeld
- Behoudt zijn oorspronkelijke verwijzing en importeert als losse verwijzing. Onschadelijk, en zichtbaar in plaats van stil. Beelden die aan een thema hangen neemt het pakket ook niet mee, net zomin als externe URL's: het eerste is een bekend gat, het tweede is een keuze, want een
https://-URL is al draagbaar.
Een deck is niet inert
Het grootste deel van het formaat is data die een lezer kan renderen zonder iets uit te voeren. Twee slidetypes niet: custom-html-slide draagt HTML die een auteur geschreven heeft, en embed-slide draagt een URL die in een frame komt te staan. Een deck is dus actieve inhoud, en een bestand van iemand anders veilig noemen omdat het valideert is de verkeerde conclusie.
Wat daaruit volgt is een plicht voor wie het formaat leest, geen eigenschap ervan: saneer auteurs-HTML voordat die een document bereikt, en isoleer ingesloten URLs in een frame dat niet bij de pagina eromheen kan. De pakketlaag verandert daar niets aan. Adressering op hash bewijst dat de bytes de ingepakte bytes zijn; over de vraag of ze veilig zijn om uit te voeren zegt het niets.
Versionering
version is de envelopversie. Die beweegt alleen bij een breaking wijziging aan de vorm van de envelop, en hij heeft nog niet bewogen. De inhoud van slides is apart geversioneerd, gekoppeld aan een schemaversie met een eigen migratieloop. Het model is nbformat uit Jupyter: een lezer valideert tegen de versie die hij kent, en het inschikkelijke contract laat hem sleutels uit een nieuwere versie verdragen.