← SEF hub

Format version 1.1.0 Guidance revision 2026-07-05 Page synced 2026-07-14

This page renders the canonical SEF specification. The SuperCards desktop repository is the source of truth; this copy states the format version it reflects.

SuperCards Exchange Format — Specification v1.1

Format version: 1.1.0 Status: Final draft — open decisions ratified 2026-07-05 (extension, markdown default, one-semantics versioning); pending JP’s final read-through. Payload-kinds amendment (deck sharing, §2.2) added 2026-07-07 per the ratified Phase C+S plan — container-level, no format-version bump Date: 2026-07-04 (updated 2026-07-05; payload-kinds revision 2026-07-07; §6 pdf-reference fragility note 2026-07-14) Predecessor: specs/v0.5.3/LLM-integration/import-schema-spec-v2.md (format 1.0.0 — approved 2026-03-24) Governing decisions: SEF_llm_integration_roadmap.md Decision Record (all six ratified); user journeys in connector_user_journey_map.md


1. Overview

The SuperCards Exchange Format (SEF) is a semantic JSON format with three use cases:

  1. AI-assisted creation — LLMs generate card content from source material and deliver it to SuperCards via the Claude connector (MCP submit_deck), a downloaded file, or an import folder.
  2. Deck sharing — decks travel as single self-contained .supercards archive files (new in 1.1).
  3. Import — the desktop app converts SEF into native card files with auto-generated layouts.

The format intentionally omits spatial layout (coordinates, dimensions, zIndex). Content is described semantically — what each block is, not where it goes — and the layout engine positions it at import. v1.1 adds bounded creative control: rich text via markdown (now the default text format), code blocks, and advisory layout hints.

1.1 Design Principles

Carried from v1.0, with one addition:

1.2 Relationship to the Native Format

Exchange FormatNative FormatTranslation
deck.* fieldsdeck.jsonDirect mapping + ID generation
card.front[] / card.back[]front.elements[] / back.elements[]Layout engine generates positions
card.size (new)CardStyle.sizePer-card override of deck default
Text block roleElementStyles (fontSize, bold, …)Role-to-style table (§9)
Text block text (plain)element.contentDirect mapping
Text block text (markdown) (new)element.richContent (ProseMirror JSON) + element.content (plain projection)Markdown parser (§4.5)
Text block align (new)Paragraph attrs.textAlign in richContent and styles.textAlignUniform by construction — see §5.1
Code block (new)Text element whose richContent is a single codeBlock node§3.4.3
Block group (new)(no native field)Consumed by the layout engine only
Image srcelement.content (bare filename)Asset resolution + copy to deck assets/
Array orderelement.zIndex / card_order.jsonIndex → zIndex / card order

The native card format version does not change (formatVersion: "1.0.0" in card/deck JSON). richContent has existed on CardElement since v0.4.3; v1.1 is the first time the import pipeline populates it (today layout_engine.rs always emits rich_content: None).

1.3 What’s New in 1.1 (summary)

AdditionWhere
.supercards single-file archive container§2.2
Markdown as the default text format (plain opt-out)§3.4.1, §4
One-semantics versioning — all 1.x files read under 1.1 rules§11
code content block type§3.4.3
align layout hint on text blocks§5.1
group layout hint on content blocks§5.2
size per-card override§3.3, §5.3
math block type reserved (not supported)§3.4.4
Degradation matrix for older importers§11
Authoring guidance for models§14

Every v1.0 file remains valid and importable. It is interpreted under 1.1 semantics rather than version-gated rules — see §11 for why this is safe and what it deliberately gives up (ratified 2026-07-05).


2. File & Container Conventions

2.1 Manifest Files

Manifests use the compound extension .supercards.json (unchanged). Exactly one manifest per import folder or archive; zero or multiple is a fatal error.

2.2 The .supercards Archive (new — roadmap Decision #5)

A .supercards file is a ZIP archive of a deck payload — an import folder (the original 1.1 form) or, since the deck-sharing revision (2026-07-07), a native deck folder. For the SEF-manifest kind the mental model is unchanged: anything valid in an import folder is valid inside the archive, and validation/import semantics are identical after extraction.

Spanish Verbs.supercards          (ZIP archive)
├── spanish_verbs.supercards.json   # exactly one manifest, at archive root
└── assets/                          # optional; referenced files
    ├── ser_conjugation.png
    └── estar_conjugation.png

Payload kinds (added 2026-07-07 — Phase C+S plan, Decisions #2/#3): the archive root determines — by parsing, never by filename sniffing — which of exactly two payload kinds the container carries:

Payload kindRoot markerProduced byImport path
SEF manifestexactly one *.supercards.jsonLLM workflows (connector, Skill, universal prompts)validate → resolve → layout → drip-feed (§8)
Native deckdeck.json parsing as a native deck file, no manifest presentDeck sharing: the desktop app’s per-deck export — the native deck folder zipped (deck.json + card_order.json + cards/ + assets/, preview PNGs included)native structural validation → identity re-stamp → drip-feed

Archive limits and hardening (validator MUST enforce):

RuleLimit / behavior
Total archive size500 MB hard cap; warning above 100 MB
Total decompressed size1 GB hard cap (zip-bomb guard)
Per-entry decompressed size200 MB hard cap
Entry count2,000 hard cap
Entry pathsReject absolute paths, .. traversal, and backslash-separated paths (zip-slip)
Symlink entriesRejected
Nested archivesNot extracted (treated as opaque files; ignored unless referenced)
Encrypted entriesRejected with a clear error

2.3 Import Folder Structure

Unchanged from v1.0: a user- or connector-assembled folder containing exactly one *.supercards.json plus any referenced PDFs/images. Multi-PDF imports remain supported via the pdf:filename:page:N:index:N scheme.

2.4 Canonical Locations

ArtifactURL
SEF hub (start here)supercards.io/sef
Human-readable spec (this document)supercards.io/sef/spec
JSON Schema (machine-readable)supercards.io/sef/schema.json
Model-optimized page (llms.txt-style)supercards.io/sef/llms.txt
Downloadable examplessupercards.io/sef/examples/

Prompts and connector tool descriptions reference these instead of inlining the spec. The connector packages also bundle copies so they work offline.

In-repo source of truth: this spec, SEF-schema-v1.1.json, and the example manifests live in the desktop repo’s top-level sef/ directory (sef/README.md documents how every derived copy propagates). The schema is draft 2020-12, authoring-strict (canonical enums, no unknown fields; the Rust validator stays the lenient authority). Verified against the §15 examples, both fixture manifests in sef/examples/, and a negative-case suite (2026-07-05).


3. Schema Definition

3.1 Root Object

{
  "supercards_format": "1.1.0",
  "deck": { ... },
  "cards": [ ... ]
}
FieldTypeRequiredDescription
supercards_formatstringYesWrite "1.1.0". Any 1.x string is accepted and read under 1.1 semantics (§11)
deckobjectYesDeck metadata
cardsarrayYesMinimum 1 card; maximum 200 (warning above 100)

3.2 Deck Object

Unchanged from v1.0:

FieldTypeRequiredDefaultDescription
titlestringYesMax 150 chars (truncate + warn)
summarystringNo""Description
colorstringNo"#F4F4F4"Hex; applied to all cards unless overridden
card_sizeenumNo"medium""small" | "medium" | "large" — deck default
sourcestringNo""Provenance (e.g., “Generated by Claude from workout.pdf”)
tagsstring[]No[]Reserved

3.3 Card Object

{
  "title": "Grasshoppers",
  "tags": ["core"],
  "color": "#F4F4F4",
  "size": "large",
  "front": [ ... ],
  "back": [ ... ]
}
FieldTypeRequiredDefaultDescription
titlestringNo""Shown in card controls, not on canvas
tagsstring[]No[]Card-level tags
colorstringNodeck colorPer-card color override
size (new)enumNodeck card_sizePer-card canvas size override — see §5.3
frontContentBlock[]YesMin 1 block
backContentBlock[]No[]Optional

3.4 Content Blocks

Every block has a type. v1.1 types: text, image, code. Unknown types are skipped with a warning (this is how v1.0 importers survive v1.1 files, and how v1.1 importers will survive v1.2’s math).

3.4.1 Text Block

{
  "type": "text",
  "text": "**Grasshoppers** build conditioning:\n1. Hands and feet on the floor\n2. Bring right foot to left hand\n3. Alternate at a brisk pace",
  "role": "body",
  "align": "left",
  "group": null
}

(The block above needs no format field — markdown is the default, so the bold span and numbered list just render.)

FieldTypeRequiredDefaultDescription
typestringYes"text"
textstringYesContent; \n preserved
roleenumNo"body"Semantic role (§3.5)
format (new)enumNo"markdown""markdown" (default) | "plain" (§4). Aliases: md/commonmark/gfm → markdown; plaintext/text/none → plain. Unknown → markdown + warning
align (new)enumNo"left""left" | "center" | "right" (§5.1). Unknown → left + warning
group (new)stringNononeAdvisory clustering label (§5.2)

3.4.2 Image Block

{
  "type": "image",
  "src": "grasshopper_start.png",
  "alt": "Start position",
  "size": "medium",
  "group": "steps"
}

Unchanged from v1.0 (src reference formats in §6; size hint semantics unchanged) except the new optional group field (§5.2).

3.4.3 Code Block (new)

{
  "type": "code",
  "code": "const isBrainDump = deck.id === \"brain_dump_deck\";",
  "language": "typescript",
  "group": null
}
FieldTypeRequiredDefaultDescription
typestringYes"code"
codestringYesSource text, newlines preserved, no fencing
languagestringNonone (plain)See language table below
groupstringNonone§5.2

Why a dedicated type when markdown has fences: a code block tells the layout engine the entire element is code (monospace metrics, code-dominant face profiles — the “code snippet on one side” use case), and it’s the natural shape for LLMs when a face is exclusively code. Fenced blocks inside markdown text remain fully supported for mixed prose-and-code content; both routes produce the same native codeBlock node.

Languages (the desktop editor’s curated lowlight common set): bash, c, cpp, go, javascript, json, markdown, python, rust, sql, swift, typescript, yaml, or omitted for plain text.

Alias normalization (same list applies to markdown fence info strings): jsjavascript, tstypescript, pypython, c++cpp, sh/shell/zshbash, golanggo, jsxjavascript, tsxtypescript. Unknown language → plain text + warning (source is always kept).

Native mapping: a text element whose richContent is a ProseMirror doc containing a single codeBlock node (attrs.language); content carries the plain source. role and align do not apply to code blocks and are ignored if present.

3.4.4 Reserved: math (not supported in 1.1)

Block math is deferred to format 1.2 (roadmap Decision #3): its renderability cannot be verified by the pure-Rust pipeline, which would break the “validated ⇒ renders” guarantee (§1.1). A {"type": "math", ...} block in a 1.1 file is skipped with a warning like any unknown type. Authoring guidance: express math as plain-text notation or a code block for now; do not invent LaTeX text blocks expecting them to render.

3.5 Semantic Roles

Unchanged from v1.0 — heading, subheading, body, caption, label, with alias normalization ("title"/"header"/"h1"heading, "subtitle"/"h2"/"h3"subheading, "paragraph"/"p"/"content"body, "note"/"footnote"caption, "tag"/"badge"/"annotation"label; unknown → body).

Interplay with markdown: one block = one role. The role sets the block’s typographic baseline (size, weight, color per §9); markdown marks compose on top of it (a body block with **bold** words renders body-size with bold spans). Markdown heading syntax is not the way to make headings — use a separate block with role: "heading" (§4.2).


4. Markdown Profile

Applies to every text block that does not declare format: "plain"markdown is the default (ratified 2026-07-05; see §12 for the rationale). The profile is a deliberately small CommonMark subset plus two extensions, chosen to match exactly what the Card Editor and iOS renderer support — nothing the profile accepts can fail to render. Standard CommonMark backslash escapes are honored (\*literal asterisks\*), and format: "plain" remains the full opt-out for text that must render exactly as typed.

4.1 Supported Syntax

SyntaxExampleNative mapping
Bold**text**bold mark
Italic*text*italic mark
Bold+italic***text***both marks
Strikethrough (GFM)~~text~~strike mark
Inline code`text`code mark
Highlight (SuperCards extension)==text==highlight mark (default yellow)
Paragraph breakblank linenew paragraph node
Hard breaksingle \nhardBreak (§4.3)
Bullet list- itembulletList > listItem
Ordered list1. itemorderedList > listItem
Nested list (≤ 2 levels)indented - itemnested list nodes; deeper levels flattened to level 2 + warning
Fenced code block```rustcodeBlock node (language table §3.4.3)

==highlight== is not CommonMark; it is a SuperCards extension (Obsidian-style) requiring a custom inline pass in the parser. If a parser cannot support it, the literal == characters must be preserved (never dropped silently).

Highlight matching rules (errata, added 2026-07-05 — implemented from the start but previously unstated): pairs match non-greedily within a single text run; the inner span must be non-empty and must not begin or end with whitespace (so comparison prose like if a == b == c stays literal); == sequences inside code spans and code blocks are never candidates; unpaired or ill-formed == stays literal. Reference implementation: sef_markdown.rs.

Indented code blocks (errata, added 2026-07-05): 4-space-indented code blocks are core CommonMark and parse as language-less codeBlock nodes, identical to an unlabeled fence. The profile table lists only fences because fences are the recommended authoring form (they can carry a language); indented code is accepted rather than rejected per the §4.2 never-error rule.

4.2 Unsupported Syntax — Normalization Rules

The parser never errors on markdown content; it normalizes and warns:

SyntaxBehavior
ATX headings (# Heading)Leading # marks stripped; text kept at the block’s role styling; warning MD_HEADING_IGNORED (“use a separate block with role: heading”)
Links [text](url)Link text kept as plain text; URL dropped; warning MD_LINK_FLATTENED
Inline images ![alt](src)Skipped; warning MD_IMAGE_IGNORED (“use an image content block”)
Blockquotes (> text)> stripped; treated as paragraph; warning
TablesKept as literal text; warning
Horizontal rules (---)Skipped silently
Raw HTMLKept as literal text; warning
UnderlineNot expressible in markdown; use bold/italic/highlight

4.3 Newline Semantics

Within markdown blocks, a single \n is a hard line break (GFM “breaks” behavior), and a blank line starts a new paragraph. Rationale: LLMs use single newlines meaningfully in card content, and v1.0 plain-text blocks already preserved them — silent newline-swallowing (strict CommonMark soft breaks) would make markdown lose fidelity relative to plain text.

These semantics are also what make markdown-by-default safe: sigil-free text parses to exactly the paragraphs and line breaks v1.0 produced, so plain prose is a fixed point of the parser.

4.4 Composition with Roles and Hints

4.5 Mapping to Native richContent

For each markdown text block, the import pipeline:

  1. Parses the profile into a ProseMirror document (paragraphs, marks, lists, code blocks) — pure Rust.
  2. Serializes it into element.richContent (stringified JSON), matching the editor’s TipTap schema (same node/mark names the iOS ProseMirrorParser already reads).
  3. Writes element.content as the plain-text projection of that document. This projection MUST be byte-identical with tipTapToPlainText (src/types/tiptap.ts) via its Rust mirror text_utils.rs — the same parity contract the save pipeline enforces.
  4. Maps role to ElementStyles as the baseline (§9).

format: "plain" blocks produce content only (richContent absent) — byte-for-byte the v1.0 text-element output.

Plain-reduction rule (errata, wording corrected 2026-07-05): implementations SHOULD reduce a markdown block — emit content only, richContent absent — when both conditions hold: (1) the parsed document is structurally plain (only unmarked paragraphs and hard breaks — no marks, no lists, no code blocks), and (2) the plain-text projection equals the input with blank-line runs collapsed to single newlines. Reduced blocks emit the input text verbatim as content (blank lines survive — byte-identical to v1.0 output). Condition (2) is load-bearing: a block that parses to plain paragraphs only because normalization stripped something (a # heading marker, an honored backslash escape, a dropped horizontal rule) fails the guard and takes the rich path — otherwise the verbatim copy would resurrect syntax the parser stripped. Non-reduced blocks emit content = the projection, matching the save-pipeline convention for elements that carry richContent. This is an output optimization, not a validation rule.

Conformance note — richContent consumers (errata, added 2026-07-05): richContent is JSON; key order is not significant and legitimately varies by producer (TipTap’s JS serializer emits type first; this pipeline’s serde_json emits keys alphabetically, e.g. {"content":[…],"type":"doc"}). Consumers MUST detect and parse the document structurally — parse the string, then check the root type == "doc" — never by byte-prefix matching against one particular serialization. (Motivating defect: a desktop startsWith('{"type":"doc"') check rejected every pipeline-produced document while iOS’s structural parser accepted the same files; fixed in desktop commit 1a5f95e1.)


5. Layout Hints

All hints are advisory: the layout engine honors them when they fit and degrades gracefully when they don’t. Every hint maps to something the engine demonstrably supports — the vocabulary stays small by design (roadmap risk: hint sprawl).

5.1 align (text blocks)

"left" (default) | "center" | "right".

Native mapping — two writes that agree by construction:

This satisfies the repo’s all-or-nothing alignment-mirror principle (see CLAUDE.md “Per-Paragraph Alignment”): a block-level hint is uniform across the element’s paragraphs by definition, so the element-level mirror can never misrepresent mixed alignment. For format: "plain" blocks — and markdown blocks reduced to plain output per §4.5 — styles.textAlign alone carries it, as in v1.0.

5.2 group (any content block)

A string label (≤ 32 chars) marking blocks that should lay out together.

"front": [
  { "type": "text", "text": "Grasshoppers", "role": "heading" },
  { "type": "image", "src": "step1.png", "alt": "Start", "size": "small", "group": "steps" },
  { "type": "image", "src": "step2.png", "alt": "Cross",  "size": "small", "group": "steps" },
  { "type": "image", "src": "step3.png", "alt": "Return", "size": "small", "group": "steps" }
]

5.3 size (card object)

Per-card canvas override of the deck’s card_size. Maps to native CardStyle.size; the layout engine uses the corresponding canvas dimensions (§10). Mixed-size decks are valid. Use sparingly — e.g., large for a card carrying a code block that needs the width — and let the deck default carry everything else.

5.4 size (image blocks)

Unchanged from v1.0: "small" (~25–30% width) | "medium" (~40–50%, default) | "large" (~60–75%) | "full-width" (~90–100%).


6. Image Source References

Unchanged from v1.0 — four formats identified by prefix:

  1. File reference — bare filename resolved against the import folder / archive (assets/ preferred, root accepted). PNG, JPEG, WebP, GIF.
  2. PDF referencepdf:filename.pdf:page:N:index:M (1-based page, 0-based index; index-0 fallback with warning).
  3. Data URIdata:image/png;base64,... (use sparingly).
  4. URLhttps://... downloaded at import (10 MB/image, 30 s timeout, 5 redirects, max 50 URL refs, content-type + magic-byte validated; failures → placeholder + warning).

Fragility note (authoring guidance): pdf: indices select an image by enumeration order inside the PDF, which follows the file’s internal structure — nested Form XObjects, decorative strips, and split images all claim indices — not what a reader sees as “the Mth picture on the page.” An index that looks plausible can silently resolve to the wrong image. Treat pdf: references as a convenience for verified extractions only: check every resolved image after import, and when an index can’t be verified, extract the image and bundle it as a plain file reference instead. Attached/bundled image files are the reliable path. This note adds no new semantics; resolution behavior is unchanged.

Archive note: inside a .supercards archive, file references resolve against the archive’s extracted contents exactly as against a folder.


7. Validation Rules

7.1 Fatal Errors

All v1.0 fatals carry forward (invalid JSON, missing/unsupported supercards_format major version, missing deck/title/cards, empty card front, zero or multiple manifests), plus:

ConditionError
Archive exceeds size/entry caps (§2.2)“This deck file is too large to import.”
Archive entry fails path/symlink/encryption rules (§2.2)“This deck file contains invalid entries and cannot be imported.”
Archive is not a readable ZIP”This file is not a valid .supercards deck.”
Code block missing code field”Card {i} has an invalid code block.”

7.2 Warnings (import continues)

All v1.0 warnings carry forward (missing image file, failed PDF extraction, unknown role → body, unknown block type → skip, empty text → skip, unknown fields ignored, card count/title truncation, unknown image size → medium), plus:

ConditionBehavior
Unknown format valueTreat as markdown (the default); warn
Unknown align valueTreat as left; warn
Unknown language (code block or fence)Plain text; warn; source kept
Unknown card size valueDeck default; warn
group longer than 32 charsIgnore the hint; warn (GROUP_INVALID)
group not a stringHint dropped at parse time, silently (the lenient deserializer coerces wrong-typed optional fields to their defaults)
Unhonorable groupSequential layout; warn
Markdown normalization events (§4.2)Per-rule warning codes
List nesting > 2 levelsFlatten to 2; warn
math block (reserved)Skip; warn (“math arrives in a future version”)

Machine-readable diagnostic codes extend the existing ImportDiagnostic enum; the connector’s validate_deck returns them verbatim so models can self-correct.


8. Processing Pipeline

One new step; the rest carries forward:

 0. UNPACK       If input is a .supercards archive: enforce §2.2 hardening,
                 extract to a staging directory, then treat as an import folder
 1. SCAN         Find exactly one *.supercards.json
 2. PARSE        Parse + validate (§7); collect diagnostics
 3. RESOLVE      Resolve image references (file / pdf / data / url)
 4. STAGE        Write resolved assets to temp staging (outside iCloud container)
 5. LAYOUT       Classify face profiles; apply hints (§5); position elements;
                 parse markdown/code into richContent (§4.5); map roles (§9)
 6. GENERATE     Card JSONs, card_order.json, deck.json in staging
 7. PREVIEW      Placeholder preview PNGs (Rust-side)
 8. MIGRATE      Drip-feed staged files → library (WriteTracker, ~50ms pacing)
 9. INDEX        Update SharedLibraryIndex
10. NOTIFY       deck-updated / refresh-library events
11. OPEN         Card Editor on first card (existing post-import behavior)

Steps 0–7 are pure computation plus staging I/O; nothing touches the Library until MIGRATE. This is the single-write-path guarantee the connector relies on.


9. Role-to-Style Mapping

Unchanged baseline from v1.0; markdown marks compose on top (§4.4):

RolefontSizeboldtextAlign defaultcolor
heading24trueper align (default left)#000000
subheading16trueper align#000000
body16falseper align#000000
caption14falseper align#666666
label12falseper align#666666
(code block)13 (nominal — see note)editor code-block theme

Dark-background inversion (luminance-based, #FFFFFF/#BBBBBB) carries forward unchanged.

Code-block metrics (errata, corrected 2026-07-05): the editor renders code blocks at a fixed 11px × 1.2 line-height plus 38px of vertical chrome (.code-block-wrapper pre padding and the language-picker allowance); element fontSize does not scale code blocks and carries the nominal 13 for stylistic consistency only. The v1.0 wording “engine may shrink to fit” is therefore implemented as box-height clamping to the canvas, not font scaling — the layout engine’s height estimation uses the real 11px metrics.


10. Card Canvas Dimensions

Unchanged; now selectable per card via card.size (§5.3):

SizeWidthHeight
small812375
medium844390
large956440

11. Versioning & Degradation

check_format_version in the shipping validator accepts any major-version-1 file (SUPPORTED_FORMAT_MAJOR = 1) and rejects 2.x.x. Consequences:

A 1.x file in a 1.1 importer — one semantics, no version gating (ratified 2026-07-05): every major-version-1 file is read under 1.1 rules; the version string selects nothing. For 1.0-era files (which predate the format field) text now parses as markdown by default. §4.3’s hard-break semantics keep sigil-free text rendering identically, and the §4.5 plain-reduction rule keeps its native output byte-identical — so in practice only text containing markdown-significant characters changes, gaining the structure it appears to declare. This trade was accepted deliberately: the v1.0 corpus is essentially zero (the shipped beta’s copyable prompt templates — which still stamp "1.0.0" and must keep importing — plus two internal example manifests), which makes behavior-preservation machinery pure cost. Zero migration, one parser path, one documented behavior.

A 1.1 file in a pre-v1.1 app (graceful degradation, no data loss):

FeatureOld-app behavior
Markdown text (format field usually absent under the default)All text treated as plain; raw markdown shown as literal text (asterisks visible)
code blocksSkipped with warning (existing Skip variant via #[serde(other)])
align, group, card sizeIgnored silently (unknown-field rule)
.supercards archiveNot recognized — user must unzip manually and folder-import

The archive is the only hard gap; auto-update (4-hour check cadence) keeps the fleet current, and the Skill/connector can state the minimum app version.

Forward: 1.2 (math) will be additive; 1.1 importers will skip math blocks with a warning by the existing unknown-type rule.


12. Design Decisions (v1.1)

DecisionRationale
Markdown as a format field, not a new block typePreserves v1.0 text-block shape; the field is the opt-out switch, not an opt-in
Markdown is the default format (2026-07-05)Models write markdown by reflex; a forgotten opt-in flag fails silently (plain text with asterisks is valid — no diagnostic possible, so even the connector’s validate-loop can’t catch it). Sigil-free text is a fixed point of the parser (§4.3, §4.5), so the default costs v1.0-era files ~nothing
One semantics for all 1.x files (2026-07-05)No version-gated behavior matrix to document and test forever; the ~zero v1.0 corpus (beta prompts + two fixtures) makes preservation pure cost. Old "1.0.0" version strings remain importable
Markdown profile = editor’s exact capability set”Validated ⇒ renders” (§1.1); nothing accepted can fail downstream
No markdown headingsRoles own block typography; two heading systems would fight. Strip + warn
Single \n = hard breakLLM/card-content fidelity; consistent with v1.0 plain text
==highlight== extensionFlagship flashcard affordance; Obsidian-familiar to LLMs; literal-preserve fallback if unparsed
No underlineNo markdown syntax; bold/italic/highlight suffice
Dedicated code block type and fencesType = layout signal for code-dominant faces; fences = mixed prose. Same native node
Block math deferred to 1.2MathJax renders frontend-side; pipeline can’t verify renderability; JP wary of dependency; additive later
align writes paragraph attrs + element mirrorUniform-by-construction satisfies the all-or-nothing mirror principle (CLAUDE.md)
group consecutive-onlyDeterministic, cheap to honor, impossible to misread
Archive = zipped import folderOne mental model; validation/import reuse; folder form stays valid
.supercards extensionBrand-carry on every shared file; .sc (SuperCollider/Scala) and .cards (existing flashcard ZIP format) rejected for collisions. Re-validated 2026-07-05 against shorter alternatives: .sef rejected (Saxon XSLT .sef/.sef.json compiled stylesheets, active EDI SEF standard, legacy Windows tools), .scef rejected (collision-free but brandless; 3GPP owns the acronym). Length is functionally free — UTIs, AirDrop, share sheet, mail, and Windows all ignore it; .flashcards (10 chars, same category) is shipping precedent
Guidance revision decoupled from format version (2026-07-05)§14 is advisory content, not schema — edits must never require a format bump or re-validate a file; all surfaces derive from one canonical source (§14.1, roadmap Principle 2)
Hard archive caps (§2.2)Zip-bomb/zip-slip hardening before deck sharing exposes the surface to strangers’ files
Native formatVersion unchangedrichContent existed since v0.4.3; no native migration
Native-passthrough share payload (2026-07-07)Receiving devices render native JSON today (iOS ProseMirrorParser/CardContentRenderer); reverse-mapping to SEF is lossy on exactly what a hand-tuned deck uses — block math (no SEF slot until 1.2), per-character styling, free positioning. Export = zip + hygiene pass. The hybrid “stash the original manifest” idea survives as a post-launch rider for LLM re-editability
Identity re-stamp on native import (2026-07-07)Never trust incoming IDs; asset filenames embed card IDs so renames must cascade through refs, order, and thumbnails; the same archive imported twice must yield two independent decks

13. Required Model & Pipeline Changes

13.1 import_models.rs (additive)

(Per CLAUDE.md, implementing these requires a docs/data/YYYY-MM-DD-UPDATE.md note — file-format change to the exchange format.)

13.2 New module: markdown parser (Rust)

Markdown-profile → ProseMirror JSON. Candidate: pulldown-cmark + custom ==highlight== inline pass + TipTap-schema serializer. Pure computation; unit-testable against editor-produced richContent fixtures. Plain-text projection MUST route through text_utils.rs (parity contract, §4.5).

13.3 layout_engine.rs

13.4 import_validator.rs + new archive module

13.5 Desktop app

13.6 Explicitly not required (errata, corrected 2026-07-05)


14. Authoring Guidance for Models

Guidance revision: 2026-07-05 (versioned independently of the format — see §14.1)

This section seeds the web spec’s guidance page, the connector’s get_examples, and the Skill (roadmap A3). It is normative for prompt/tool authors, advisory for models.

Card design:

Layout hints:

Formats and sources:

Self-checks before delivering: valid JSON; every card has non-empty front; every image ref resolvable (file present, page/index plausible, URL direct); roles from the vocabulary; deck source filled in.

14.1 How This Section Evolves

(Ratified 2026-07-05 — governs all future edits to §14 and its derived artifacts.)

Guidance is advisory content, not schema. Editing §14 never bumps supercards_format, never changes the validator, and by construction cannot invalidate an existing file. The litmus test: if a proposed edit would change what validates or how a file renders, it is not a guidance change — it belongs in the schema sections (§3–§11) and rides a format version bump. Pedagogy tweaks, new few-shot examples, recipe patterns, and better image advice are all guidance; they ship anytime.

Versioning. Guidance carries its own date-based revision stamp (Guidance revision: YYYY-MM-DD, above), independent of the format version. The web spec page keeps a guidance changelog. Connector responses (get_spec / get_examples) report both format_version and guidance_rev, so a badly authored deck can be traced to stale guidance.

One canonical source, derived surfaces (roadmap Principle 2). This section’s content lives in one canonical source file in the repo; the web guidance page, llms.txt, the Desktop extension’s bundled copy, and the claude.ai Skill’s content are all generated from it — never forked. Propagation per surface:

SurfaceUpdate path
Web spec page + llms.txtEdit anytime; browsing models and humans see it immediately
Desktop extension (get_spec/get_examples)Runtime refresh: fetch the canonical URL with a cache TTL, bundled copy as offline fallback — guidance edits never require repackaging/re-signing the extension
claude.ai SkillThe weak link (static zip once uploaded). The Skill prefers fetching the canonical llms.txt when it can; otherwise updates ride Skill releases + re-upload
Website prompt templatesEdit anytime

15. Complete Examples

15.1 Exercise Deck — markdown, grouped images, PDF refs

{
  "supercards_format": "1.1.0",
  "deck": {
    "title": "Exercise Library",
    "summary": "Infinite Intensity conditioning movements",
    "card_size": "medium",
    "source": "Generated by Claude from infinite_intensity.pdf"
  },
  "cards": [
    {
      "title": "Grasshoppers",
      "tags": ["core", "conditioning"],
      "front": [
        { "type": "text", "text": "Core Exercises", "role": "caption" },
        { "type": "text", "text": "Grasshoppers", "role": "heading", "align": "center" },
        {
          "type": "image",
          "src": "pdf:infinite_intensity.pdf:page:87:index:0",
          "alt": "Start position",
          "size": "small",
          "group": "steps"
        },
        {
          "type": "image",
          "src": "pdf:infinite_intensity.pdf:page:87:index:1",
          "alt": "Foot to hand",
          "size": "small",
          "group": "steps"
        },
        {
          "type": "image",
          "src": "pdf:infinite_intensity.pdf:page:87:index:2",
          "alt": "Return",
          "size": "small",
          "group": "steps"
        }
      ],
      "back": [
        { "type": "text", "text": "Grasshoppers", "role": "heading" },
        {
          "type": "text",
          "text": "An excellent **conditioning and core** movement.\n\n1. Begin with hands and feet touching the floor\n2. Bring your right foot under the body to your ==left hand==\n3. Return and repeat with the left foot to the right hand\n4. Continue alternating at a brisk pace",
          "role": "body"
        }
      ]
    }
  ]
}

15.2 Codebase Deck — code blocks, per-card size (vibe-coder use case)

{
  "supercards_format": "1.1.0",
  "deck": {
    "title": "SuperCards Codebase Tour",
    "summary": "Key patterns, one per card",
    "card_size": "medium",
    "source": "Generated by Claude from the supercards-app repository"
  },
  "cards": [
    {
      "title": "Brain Dump identification",
      "size": "large",
      "front": [
        {
          "type": "text",
          "text": "How does the app know a deck is the Brain Dump?",
          "role": "heading"
        },
        {
          "type": "code",
          "code": "const isBrainDump = deck.id === \"brain_dump_deck\";",
          "language": "typescript"
        }
      ],
      "back": [
        { "type": "text", "text": "Fixed ID, not a flag", "role": "heading" },
        {
          "type": "text",
          "text": "The Brain Dump is identified by its **fixed ID** (`brain_dump_deck`), never by name or a boolean flag.\n\n- Defined on both sides: `models.rs` and `cards.ts`\n- It cannot be renamed or deleted\n- Every special case checks this one constant",
          "role": "body"
        }
      ]
    }
  ]
}

15.3 Minimal Valid File (unchanged shape from v1.0)

{
  "supercards_format": "1.1.0",
  "deck": { "title": "My Deck" },
  "cards": [{ "front": [{ "type": "text", "text": "Hello, world!" }] }]
}

(Text is markdown by default — **bold** in this block would render bold. Sigil-free text like this parses and outputs exactly as v1.0 plain text did.)

15.4 Shareable Archive Layout

Exercise Library.supercards        (ZIP)
├── exercise_library.supercards.json
├── infinite_intensity.pdf          # pdf: refs resolve self-contained
└── assets/
    └── cover_photo.png

Native-deck payload (deck sharing — same container, second payload kind, §2.2):

Biochem Unit 3.supercards          (ZIP)
├── deck.json                       # native deck metadata (native formatVersion 1.0.0)
├── card_order.json
├── cards/
│   ├── card_1751856000000_a1b2.json
│   └── card_1751856000001_c3d4.json
└── assets/
    ├── card_1751856000000_a1b2_001_9f1e2abc.png
    ├── card_1751856000000_a1b2_front_preview.png              # previews ride along
    └── card_1751856000000_a1b2_math_000_5d6e7f8a.svg          # block math survives losslessly

16. Changes from Format 1.0

  1. .supercards single-file archive container with UTI + hardening rules (§2.2)
  2. Markdown is the default text format, with a closed profile (§4); format: "plain" opts out
  3. code content block type + fence/alias language normalization (§3.4.3)
  4. Layout hints: align, group, per-card size (§5)
  5. Import pipeline populates richContent for markdown/code (previously always absent)
  6. math explicitly reserved for 1.2 with interim authoring guidance (§3.4.4)
  7. Degradation matrix for pre-1.1 importers (§11)
  8. Authoring-guidance section as a normative spec artifact (§14)
  9. New validation rules + diagnostic codes (§7); archive fatals
  10. Canonical web locations defined (§2.4)
  11. One-semantics versioning: all 1.x files read under 1.1 rules — v1.0 behavior-preservation deliberately dropped (§11)
  12. Guidance-evolution policy: date-stamped revisions decoupled from the format version, one canonical source for all surfaces (§14.1)
  13. Second container payload kind — native deck folder for deck sharing, with parse-based detection, identity re-stamp rules, and shared hardening (§2.2, revision 2026-07-07; container-level, no supercards_format bump)

17. Future Improvements (carried + new)