Spec

What it takes to read a Deckyard deck

Publishing a format is a promise, and a promise without an edge is not one. This page draws the edge: what you have to build, what you may then say you support, and what happens at every point where your reader meets something it does not know.

Conformance has two levels

The point of the split is that neither level grows with the number of slide types. A reader that learns nine type contracts knows nine things and is stale the day a tenth is published. A reader that learns six structure contracts can render a type that did not exist when it shipped.

Level 1

Structure

The envelope, the six structure contracts, and the behaviour on a type you do not recognise.

Reads Deckyard decks

Every deck renders and nothing is dropped. Some slides render generically rather than the way they were authored, which is a degradation you have declared rather than a failure the reader discovers.

Level 2

Core profile

Level 1, plus the field contracts of the nine tier-1 types, plus fallback.

Renders the Deckyard core profile

Every deck renders the way it was authored, up to the degradation each type declares for itself. This is the realistic target: it is a weekend of work, not a catalogue.

The claim you may publish

Tick what you actually render. The sentence underneath is the one you are entitled to put in your own documentation, and the numbers behind it are counted off the published registry rather than estimated.

Structures you render
The nine tier-1 types, field by field

26 of 33 render as authored

7 degrade through a declared fallback or the unknown-type contract

What you may say

The six item contracts

Every slide type declares one structure, and that declaration is the interop currency: it says what the content carries, whether the count means anything, and what a reader that knows nothing else about the type is entitled to do with it.

The content carries
No repeated-item array.
The count means
Nothing to count.
A reader that knows only this
Renders the named scalar slots, in declaration order.
Types declaring it
14
"content": {
  "title": "A deck is a file you own",
  "subtitle": "Portable by construction"
}
What that looks like

collection and fixed-collection differ only in whether the count is meaning. That is exactly why they are two structures and not one: a list of six may be reflowed into two columns, and a four-quadrant matrix may not be reduced to three. dataset is the one structure whose payload cannot be checked, and the contract says so rather than leaving a reader to invent a degradation.

The nine that carry a promise

Nine names are normative. The other types Deckyard ships are published and documented but version with the app, and a type from a fork carries its declarant’s promise rather than ours. A tier is a property of the name, not of the definition: a fork that overrides `title-slide` inherits the tier-1 promise, because that is what choosing the name means.

  • title-slide
  • chapter-title-slide
  • content-slide
  • list-slide
  • quote-slide
  • image-slide
  • image-text-slide
  • table-slide
  • end-slide

The choice is a criterion, not a taste: this is the minimal set that expresses an ordinary presentation without loss - title, section break, prose, enumeration, quotation, image, image-with-text, table, closing. Everything outside it adds expressiveness that has an acceptable degradation inside it, which is also why no chart type is in it: a profile that demands a charting runtime is no longer an entry threshold.

Every other type degrades into one of them

This is the rule that makes the nine worth anything: every tier-2 type declares a fallback to a tier-1 type. A funnel falls back to a list, a gallery to images, a chart to a table. So a reader that knows only the nine renders every Deckyard deck without dropping content.

  • content-slide 7 degrade to it

    • video-slide
    • embed-slide
    • countdown-slide
    • likert-slider-slide
    • feedback-slide
    • follow-invite-slide
    • custom-html-slide
  • list-slide 11 degrade to it

    • kpi-metrics-slide
    • team-cards-slide
    • icon-card-grid-slide
    • poll-slide
    • likert-slide
    • text-blocks-slide
    • process-slide
    • timeline-slide
    • funnel-slide
    • pyramid-slide
    • cycle-slide
  • image-slide 2 degrade to it

    • logo-wall-slide
    • gallery-slide
  • table-slide 3 degrade to it

    • chart-slide
    • comparison-slide
    • matrix-slide
  • end-slide 1 degrade to it

    • payoff-slide

The fallback names a tier-1 contract, not a one-for-one slide swap. A gallery falling back to image-slide means "this content is images, render it the way you render images", and a reader is free to emit more than one slide for it.

The evolution rule

Within a name, only additions. A change of meaning is a change of name.

Normative, and it applies to every published name: a slide type, a content key, an envelope key, an enum value. Deckyard migrates its own storage forward, but a reader we do not own does not run our migration chain, so for anything published a migration is not a fix. The two blocks below make the same change to the same type, and only one of them leaves every deck ever written still valid.

Permitted

"fields": [
  { "key": "title",    "required": true  },
  { "key": "subtitle", "required": false },
  { "key": "eyebrow",  "required": false }
]
A new optional key. Every existing deck stays valid.

Forbidden

"fields": [
  { "key": "title",    "required": true  },
  { "key": "subtitle", "required": false },
  { "key": "eyebrow",  "required": true  }
]
A new required key. Every existing deck is retroactively invalid.
  1. A published name MUST keep its meaning for as long as it exists. If the meaning has to change, the name changes, and the old one is deprecated rather than removed silently.
  2. Optional keys MAY be added at any time. A new required key MUST NOT be added to a published type: that turns every existing deck retroactively invalid, and it is a rename wearing a compatible-looking hat. Widening a value space is additive; narrowing it is not.
  3. A reader MUST ignore keys it does not know, at every level - envelope, slide, content, item - and MUST NOT reject a deck for carrying them.
  4. A reader MUST compare type ids as strings, after stripping an optional @version suffix: one type has one id, and there is no alias table to learn. The two older spellings of a core id - the bare title-slide and the qualified core/title-slide - are pre-convergence residue, not part of the format; a reader owes them nothing and MAY treat them as unknown types, which the unknown-type contract renders without loss. The @version suffix is a compatibility hint about a definition, not a different type, so eu.deckyard.slide.title@2 MUST NOT be treated as unknown.

A type your reader has never heard of

This is what "nothing is dropped" means at the hardest point: a type you have no declaration for at all, from a fork you have never seen. It is a specified rendering, not an error path, and it is the last resort - a type you have the declaration for but have not implemented takes its `structure` or its declared `fallback` instead.

{
  "type": "nl.ciiic.slide.roadmap",
  "content": {
    "title": "Where this is going",
    "intro": "Three horizons, no dates.",
    "phases": [
      { "label": "Now",  "detail": "One pilot" },
      { "label": "Next", "detail": "A second reader" }
    ]
  },
  "notes": "Do not promise dates."
}
A slide from a fork you do not know

Unknown type nl.ciiic.slide.roadmap

Where this is going

Three horizons, no dates.

NowOne pilot

NextA second reader

What a conforming reader shows

Every string in author order, every array element as a repeated item, the type named on the slide, and the presenter note honoured because it is an envelope key and does not depend on the type.

  1. MUST render the slide. Dropping it silently changes the slide count, the numbering and the argument the deck is making. A reader MUST NOT reject the deck either: one unknown type is not a malformed deck.

  2. MUST render every string-valued entry of content as text, in the order the keys appear in content - a producer writes them in the declared field order, so that order is the author's. A reader whose parser does not preserve member order MUST pick a stable order rather than an arbitrary one. The empty string means unset and MAY be skipped.

  3. MUST render each element of an array-valued entry as a repeated item, in array order, applying rule 2 within each element. This is the collection contract: it may be reflowed, it may not be reordered or truncated.

  4. MUST show the type reference, as written in type, on or beside the slide. A viewer has to be able to tell a generic rendering from an authored one; silently pretty output is how "we support Deckyard" becomes untrue without anyone noticing.

  5. MUST honour the global slide keys it already knows - notes, duration, visibility, and the accessibility and background keys. Those are envelope-level and their meaning does not depend on the type.

  6. SHOULD render it in the deck’s theme, so an unknown type reads as a plain slide rather than as breakage.

  7. MUST NOT invent content. No synthesized headings, no filled-in blanks, no reordering. Rendering less faithfully than the author wrote is a degradation; rendering something the author did not write is a bug.

A value that is neither a scalar nor an array or object of scalars - a nested payload a reader cannot interpret - MAY be omitted. Rule 7 outranks completeness.

The exhaustive tables - every envelope field, every manifest field, every slide type with its facets - are in the documentation, which is the half the site search indexes. Full reference