Zipdown, specification version 0.5
Independent Submission (TBD)
Request for Comments: XXXX November 2026
Category: Experimental
Zipdown: Markdown with Embedded Assets (specification version 0.5)
Status of This Memo
Version 0.5 of this specification, written November 11th, 2026. It is not yet stable.
The specification and the file format are numbered separately, and
files carry only the format's number. Every version of this
specification before 2 describes format version 1, so files say
zd-version: 1 until version 2 of the specification is published.
Until then the format may still evolve: a file written to one version
of this specification may not read the same under a later one.
Anyone may implement this specification, in any software, free or paid, without asking and without paying.
This is the site version, jokes included; the version submitted as an Internet-Draft removes the Acknowledgments jokes. Comments are, as tradition demands, requested: write to what this document hopes to become, plural, at treeflow.ai.
This document is itself a Zipdown file. Its diagrams travel inside it
as assets, and so does the worked example from Section 24, so a reader
can extract assets/example.zd and check it byte for byte against the
text. The ASCII diagrams in the text remain the reference; the images
are illustrations.
Abstract
Zipdown is a single-file text format that carries a Markdown document together with the assets it needs. The header is valid frontmatter, the body is ordinary Markdown, and the assets follow as base64 blocks inside an HTML comment, one block per asset, with a visible index and a short two-language note at the end. A plain CommonMark renderer shows the document, one visible marker line, the index, and the note. Every plain Markdown file is a valid Zipdown file with zero assets, and reads as Zipdown Markdown unless it says otherwise.
Table of Contents
- Conventions and Terminology
- Format Overview
- Character Encoding and Line Endings
- Header
- Files With No Assets
- Body
- Zipdown Markdown (zd1)
- Separator
- Boundary Marker
- Delimiters and the Comment
- Payload
- Index
- Epilogue
- Pointer
- Terminator
- Signatures
- Reading a File
- Appearance in Plain Renderers
- Extracting an Asset by Hand
- Security Considerations
- Media Type, Extension, and Site
- Implementations
- Future Work
- Example
- Open Issues
- References
Appendix A. Notes for Viewers and Editors
1. Conventions and Terminology
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.
- Octet: an 8-bit byte. All lengths are in octets.
- Header: the frontmatter block at the top of the file, fences included.
- Header end: the position just after the LF of the header's closing fence line.
- Body: everything from the header end to the separator.
- Separator: the fixed end-of-document block between the body and the opening delimiter line.
- Preamble: the header plus the body.
- Marker: the 65-character string declared by
zd-boundary. - Asset: one file carried in the payload.
- Asset block: one asset's base64 text, in 64-character lines.
- Payload: the asset blocks and the blank lines between them.
- Index: the
zipdown-indexcode block. - Epilogue: the two mandatory note lines.
- Pointer: the line just before the terminator.
- Terminator: the last line of the file, a single
.. - MLO: the value of
zd-markdown-length-octets. - Reader: code that parses and verifies a file and hands its document to something else, such as an agent, a tool, or a viewer.
- Viewer: code that shows a file to a person.
This memo defines the bytes and what they mean. What readers, servers, and editors do with a file, including files that break the rules, is their business, except where Section 20 says otherwise.
2. Format Overview
+-------------------------------------------------------+
| --- | header
| zd-version: 1 | (valid frontmatter)
| zd-markdown-length-octets: 4821 |
| zd-markdown-sha256: <64 hex> |
| zd-boundary: <65-char marker> |
| --- |
+-------------------------------------------------------+ <- header end
| # Title | body
| ... {width=10%} ... | (MLO octets)
| | ends with a blank line
+-------------------------------------------------------+
| *** | separator (visible)
| -----END OF DOCUMENT----- |
| *** |
+-------------------------------------------------------+
| --<marker> | opening delimiter (visible)
| <!-- | comment opens
| <asset 1: base64, 64-char lines> |
| | blank line
| <asset 2: base64, 64-char lines> |
| --<marker>-- | closing delimiter
| --><br/> | comment closes
| ```zipdown-index | index (visible)
| path size lines(1-based,inclusive) base64-sha256 sha256
| assets/dot.png 70 14-15 <64 hex> <64 hex> |
| assets/chart.svg 812 17-30 <64 hex> <64 hex> |
| ``` |
| Psst, this is a Zipdown file! ... \ | epilogue (2 lines)
| (the same, in Chinese) |
| <!-- zd-index 5120 00000000000000000000000000001400 --> pointer (hidden)
| . | terminator (visible)
+-------------------------------------------------------+
3. Character Encoding and Line Endings
- The whole file MUST be UTF-8 [RFC3629].
- Lines end with LF.
- Writers SHOULD NOT emit CR (0x0D) anywhere.
- If a file contains a CR anywhere, MLO, every hash, the index line ranges, and the pointer carry no guarantees. Whether to read such a file anyway is the reader's and the viewer's business, but a viewer MUST NOT show it as verified or signed.
4. Header
4.1. Must Be Valid Frontmatter
The header MUST be valid frontmatter in exactly one of these forms:
- YAML between lines consisting solely of
---. - TOML between lines consisting solely of
+++. - JSON in the style of Hugo: a line consisting solely of
{opens it, and the first later line consisting solely of}closes it. Nested braces MUST be indented. The block is one JSON object.
Readers MUST parse the header as plain data. In particular, a YAML reader MUST NOT honor tags that construct objects or run code; a "safe load" that yields only strings, numbers, booleans, lists, and maps is what is meant.
4.2. Zipdown Keys
| Key | Type | Meaning |
|---|---|---|
zd-version |
integer | REQUIRED. Its presence marks the file as Zipdown. This memo defines version 1. |
zd-markdown-length-octets |
integer | OPTIONAL hint. Length of the body in octets. |
zd-markdown-sha256 |
string | REQUIRED. SHA-256 of the body, 64 lowercase hex digits. |
zd-boundary |
string | REQUIRED. The bare 65-character marker. |
zd-payload-encoding |
string | OPTIONAL. Default base64. In version 1 the only legal value is base64. binary is reserved. |
zd-markdown-flavor |
string | OPTIONAL. The body's Markdown: zd1, or an identifier from the IANA Markdown Variants registry. Default: zd1 (Section 6.2). |
zd-signature |
string | OPTIONAL. An Ed25519 signature over the header, the index, and the epilogue (Section 16). |
zd-date |
string | OPTIONAL. When the file was written, in ISO 8601 with nanoseconds, such as 2026-11-11T13:04:00.123456789-08:00. Conforming writers include the time zone; a date without one is local time. |
(The table above is a GFM table. In a plain CommonMark renderer it shows as rows of text separated by vertical bars, which is still readable.)
4.3. Other Keys
- The
zd-prefix is reserved. Azd-key not defined here is invalid. It is ignored, and the rest of the header is read normally. (Sozd-markdown-flavouris ignored, and the flavor falls back tozd1.) - A header in which any
zd-key appears more than once is invalid. Parsers disagree about which duplicate wins, so two readers could otherwise see different markers or hashes in the same file. - Keys without the
zd-prefix, such astitleortags, are allowed and are not Zipdown's business. - Key order carries no meaning.
- This memo says nothing about files whose
zd-versionit does not define.
4.4. MLO
MLO is the number of octets in the body, from the header end to the first octet of the separator:
offset of separator = header end + MLO
offset of opening delimiter line = header end + MLO + 34
34 is the separator's fixed length in octets (Section 8).
- If MLO is present, readers MUST confirm that the separator starts exactly at header end + MLO and that the opening delimiter line follows it. If either is not there, the file is invalid.
- If MLO is missing, or zero or less, the reader finds the opening delimiter line by scanning, and the body ends 34 octets before it.
- Either way, readers MUST confirm that the marker does not occur in the body.
4.5. The zd- Line Grammar
Libraries for YAML, TOML, and JSON disagree in ways that matter: escaped
key spellings, duplicate keys, and YAML 1.1 reading 0153 as an octal
number. So every zd- entry has exactly one spelling in each header
form, and readers find zd- entries by that spelling, never through a
library.
YAML zd-<name>: <value>
TOML zd-<name> = "<value>" or zd-<name> = <integer>
JSON "zd-<name>": "<value>" or "zd-<name>": <integer>
- Each
zd-entry sits on a line of its own. In JSON the line may be indented with spaces and ends with a comma where JSON needs one. zd-entries sit at the top level only. In TOML they come before the first table header (a line starting with[); in JSON they are members of the outermost object, never of a nested one. Azd-entry anywhere else makes the file invalid.<name>is lowercase ASCII letters, digits, and hyphens.<value>is printable ASCII (0x20 to 0x7E) other than"and\, with no leading or trailing space.<integer>is decimal, with no leading zeros.- To catch disguised spellings, readers rename every key the grammar
matched (for example,
zd-boundarytox-zd-boundary) and parse the header with a library. If the library still finds anyzd-key, the file is invalid. That catches escaped spellings, TOML dotted keys, and JSON\uescapes, including a disguised second copy of a key that is also spelled plainly. - YAML headers MUST NOT use anchors, aliases, tags, merge keys, or a second document.
- The header is at most 64 KiB, has no byte order mark, and its fence
lines are exactly
---,+++, or{and}, with nothing else on them. - Keys without the
zd-prefix are free-form, as before.
5. Files With No Assets
A file with no assets needs no header, no separator, no delimiters,
no comment, no index, no epilogue, no pointer, and no terminator. It
is plain Markdown. It MAY be named .md or .zd.
Either way, its body is zd1 (Section 6.2). If it starts with an
ordinary frontmatter block (one with no zd-version) that holds a
zd-markdown-flavor line, that flavor applies instead. A frontmatter
block is never part of the body, so viewers do not show it as text.
Everything below applies to files with one or more assets.
6. Body
6.1. Rules
- The body MUST NOT contain the marker.
- The body MUST end with an empty line, so its last two octets are LF LF.
zd-markdown-sha256covers exactly the body's octets. The header is never hashed.
6.2. Which Markdown
zd-markdown-flavor names the body's Markdown. A file does not have
to use Zipdown's own Markdown to use the Zipdown container.
zd1, the default, is Zipdown Markdown, version 1: CommonMark 0.31.2 [CommonMark] plus the extensions in Section 7. In practice that is GFM [GFM] without raw HTML, plus Zipdown's own marks. It targets modern Markdown, the kind people type in messaging apps, and does not pin to the GFM spec, which has been at version 0.29 since 2019. Later versions will bezd2,zd3, and so on.- The default applies to every file without a flavor, whatever
its extension. Viewers do not guess: a
.mdfile with no header iszd1, the same as a.zdfile. Plain GFM bodies read the same either way, apart from raw HTML (Section 7.12). - Any other value is an identifier from the IANA Markdown Variants
registry, such as
GFMorCommonMark. The body follows that flavor's rules, and nothing in Section 7 applies. - A renderer that does not know the named flavor renders the body as CommonMark.
7. Zipdown Markdown (zd1)
This section applies only when the flavor is zd1. Renderers decide
how everything here looks: the actual sizes, colors, grays, and indent
widths. A renderer that does not support a feature shows its text as
written. Nothing in this section applies inside code spans, code
blocks, link destinations, or autolinks.
7.1. Inline Marks and the Edge Rule
Size, highlight, and spoilers wrap text in a pair of marks. A mark counts only at an edge:
- Opening mark: the character before it is whitespace, a punctuation character, or the start of a line, and the character after it is not whitespace.
- Closing mark: the character before it is not whitespace, and the character after it is whitespace, a punctuation character, or the end of a line.
- Character means a Unicode code point. Punctuation character means, as in CommonMark, any character in the Unicode general categories P or S, as of Unicode 16.0. The marks themselves count, so marks can sit against each other.
- Exact runs: each mark is an exact run of characters:
+,++,-,--,—,==, or||. A closer pairs only with an opener of the same run. A longer run, such as---or+++, is plain text. - Escape: a backslash before a mark makes it plain text, for
example
\--at the start of a line. - Nesting: marks nest, as in
++**very big and bold**++. Nesting deeper than 16 levels is drawn as level 16. - Pairing: marks pair the way CommonMark pairs emphasis. A
closing mark pairs with the nearest earlier opening mark of the same
kind that is still unpaired. Marks left unpaired, or opened inside a
pair that closes around them, are plain text. So
-a ++b- c++reads as a smalla ++bfollowed by a plainc++.
Ordinary text stays plain, because its dashes and pluses sit inside words or next to spaces:
self-evident 10-20 1+1+1 C++ y-axis this — that a == b
The rule is not perfect. ++i and C++ reads as XL text, because both
ends sit at an edge. A backslash fixes it: \++i and C++.
7.2. Text Size
Five steps. M is normal text and has no marks.
| Step | Inline | Line |
|---|---|---|
| XL | ++text++ |
+## text |
| L | +text+ |
+# text |
| M | (no marks) | (no marker) |
| S | -text- |
-# text |
| XS | --text-- or —text— |
-## text |
- Inline marks follow the edge rule (Section 7.1). An em dash pair
is a second spelling of
--. - Line markers sit at the start of a line and need a space after
them. Like an ATX heading, a line marker sizes exactly its own line,
and it may come directly after a paragraph line with no blank line
between. Plain viewers show
-# textas text, because a list item needs a space after its-or+.
7.3. Text Color and Grays
A color name in parentheses, directly after the closing mark of a styled span, colors that span:
the build is **failing**(red), the docs are *passing*(green)
~~old price~~(g50), `exit 1`(red), ++big news++(blue)
- Styled spans are emphasis (
*or_), strong emphasis (**or__), strikethrough (~~), code spans, and the size marks (Section 7.2). The span keeps its own style, and the color is added on top. - Colors:
red,orange,yellow,green,cyan,blue,magenta, andpurple. - Grays:
g10,g25,g33,g50,g67,g75, andg90, plusslg(super-light),lg(light),dg(dark), andsdg(super-dark). - Names are case-insensitive. Only these names count:
**Note**(see below)stays plain text. - The
)follows the closing edge rule (Section 7.1). So**x**(red)cstays plain. \(red)keeps the parentheses as plain text, as in**x**\(red).- After a highlight or a spoiler,
(red)is ordinary text. Viewers decide how highlights and spoilers look. - Viewers pick the actual shade for every name, in day and night themes alike.
- Colored text always carries another style too, so color is never the only signal.
A plain Markdown viewer shows the styled span followed by the name: the word "failing" in bold, then "(red)", which still says what was meant.
7.4. Highlight
==text== highlights text, like a marker pen. The marks follow the
edge rule (Section 7.1). A file cannot pick the highlight color. Each
renderer picks its own, and a renderer may show no highlight at all.
7.5. Spoilers
||text|| hides text until the reader reveals it, as in Discord and
Telegram. The marks follow the edge rule (Section 7.1). Inside a table
cell, the pipes are escaped: \|\|text\|\|.
7.6. Mentions
A mention is @ followed by a name, and optionally / and a platform
code, as in @qrux or @qrux/gh.
- The
@follows the opening edge rule (Section 7.1), soq@example.comis not a mention. - A name is letters, digits,
_,-, and., not ending in.. A platform code is letters and digits. - A mention only has to make sense to the conversation or document it is in. Zipdown implies no global identity.
- The platform code is optional. The site keeps a list of common codes as a shared convention (Section 21).
- A renderer that cannot resolve a mention shows it as written.
7.7. Emoji Shortcodes
A name between colons, such as :tada:, stands for an emoji. There
is no standard list: Discord, Slack, and GitHub each keep their own.
Zipdown keeps its own list on the site (Section 21). An unknown name
shows as written.
7.8. Indent
> keeps its CommonMark and email meaning: a quote, with depth by
repetition as in RFC 3676 [RFC3676], so >> is a quote inside a
quote. Indent has its own markers.
Block indent. )) followed by a space indents one level, )))
two, and so on:
)) This paragraph is indented one level.
It continues here without a marker.
))) This one is indented two levels.
- It is a container, like
>: lists, code, and quotes can sit inside it, and it nests with>both ways. - It has lazy continuation, like
>: a paragraph needs the marker only on its first line. A blank line ends it. - A single
)means nothing.
Region indent. A line holding only ))> indents everything after
it, across blank lines, until a line holding only <((:
))>
First indented paragraph.
Second indented paragraph.
<((
- Each
)beyond the first in an opener adds one level:))>adds one,)))>adds two. Each(beyond the first in a closer removes one:<((removes one,<(((removes two. - Openers and closers need not pair up one to one, as long as the depth never goes below zero and is zero at the end of the body. A file that breaks either rule is invalid.
- Neither line needs a blank line before or after it.
- Block indents inside a region add to the region's depth.
7.9. Tables
Tables are GFM pipe tables [GFM], with column alignment set by colons
in the second row (:--- left, ---: right, :---: center), plus:
- No cell spans. A cell never merges into another, so an empty
cell written
||is just an empty cell, as in GFM, and^^is just text. - No line breaks in a cell.
<br>is a reserved tag and does nothing (Section 7.12). Cells hold no block content. - Headerless tables: a header row whose cells are all empty is not drawn.
- Captions: a paragraph starting with
Table:, directly above or below the table, separated from it by a blank line. - Column widths are the renderer's business.
| | | |
|--------|-------|-------|
| North | 10 | 12 |
| South | 11 | 13 |
Table: Sales by region
In a plain GFM viewer, the empty header shows as a blank row, and the caption shows as an ordinary line of text.
7.10. Strikethrough, Task Lists, Plain Links, and Footnotes
As on GitHub [GFM]:
- Strikethrough:
~~text~~. - Task lists:
- [ ] to doand- [x] done. - Plain links: bare
https://…andwww.…addresses become links. - Footnotes:
text[^1], with[^1]: the noteanywhere in the body; notes collect at the end.
GFM's tag filter is not part of zd1: zd1 shows no raw HTML at all
(Section 7.12).
7.11. Image Size and Crop
An image may be followed directly by attributes in braces, in the style of Pandoc, Quarto, and kramdown:
{width=50%}
{width=100% height=20lh position=top}
{width=100% height=8lh position="50% 0%"}
- Syntax.
{immediately after the image's closing parenthesis, thenkey=valuepairs separated by spaces, then}. A value that contains a space is quoted with double quotes. widthis a percentage of the column, such as50%. No other unit.heightis inlh, lines of body text, such as20lh. No other unit.- Out: pixels, which mean nothing across display densities;
em; and viewport units, which WebKit handles inconsistently. - One size: the image keeps its own aspect ratio. Every image type has one: raster images, SVG through its viewBox, and PDF pages.
- Both sizes: the image fills the box, and the overflow is cropped. This is the only behavior for both sizes; there is no fit or stretch keyword.
positionsets where the crop happens, in CSSobject-positionsyntax: the keywordstop,bottom,left,right, andcenter, percentages, or a mix. The default is centered.- Unknown attributes and bad values are ignored, and the default
applies, the same way an unknown
zd-key is ignored in the header. - The attributes apply to every image asset, not only PDF.
In a plain renderer the braces show as text next to the image. The image is broken there anyway, because the asset lives inside the payload, so the leftover costs little.
7.12. HTML
zd1 shows no HTML. Raw HTML in the body is never rendered, and
there is no allowlist.
- Reserved tags:
br,sup,sub,kbd,details,summary,table,thead,tbody,tr,th, andtd. They have no meaning inzd1; a later version may give them one. The tags themselves have no effect, and whatever sits between them is ordinary text:x<sup>2</sup>shows asx2. They raise no notice. - Any other tag is left out. Text between inline tags stays text:
<span style="color:red">plain</span>shows asplain. An HTML block, in CommonMark's sense, is left out whole, unless it holds only reserved tags, in which case its text is shown as a paragraph. - Code is never shown, not even as text: the contents of
script,style,iframe,object,embed,noscript,template,textarea,title,svg,math, and similar tags are left out with the tags, and nothing a viewer offers puts them back. - Viewers say so loudly. When anything other than a reserved tag was left out, a viewer shows a prominent notice outside the document: the document contains HTML, and none of it is shown. Source mode shows the file as written.
- Comments are not tags; they follow Section 17.1.
Flavors other than zd1 keep their own rules for raw HTML, narrowed
by Section 20.
8. Separator
Directly after the body come exactly these three lines, 34 octets in all:
***
-----END OF DOCUMENT-----
***
- The separator is mandatory and exact. Each line ends with LF. It is English only and has no translation.
- It tells a person reading in a plain renderer that the document is over: everything below it, if shown at all, is there for tools.
- The framing borrows from PEM [RFC7468], whose
-----END ...-----lines mark where encoded content stops. ***is a thematic break, a horizontal rule. It is used instead of---because a---line directly under text turns that text into a heading;***never does. Because the body ends with an empty line, the first***always starts a new block.- The separator is not part of the body. MLO and
zd-markdown-sha256do not cover it. - A Zipdown renderer does not show it, any more than it shows the marker line or the index.
9. Boundary Marker
marker = hex( R XOR H[0..191] ) "-" hex( H[192..255] ) 65 characters
\______ 48 hex ______/ \____ 16 hex _____/
H = SHA-256 of the body (256 bits)
R = 192 bits from a cryptographically secure random source
The last 16 hex digits of the marker equal the last 16 hex digits of
zd-markdown-sha256, so the two can be checked by eye. If the marker
appears in the body, the writer replaces R with the first 192 bits of
SHA-256(R) and tries again. The marker MUST NOT contain -->; one
built this way never does. The marker fits MIME's 70-character
boundary limit.
10. Delimiters and the Comment
After the separator, in order:
--<marker>LF opening delimiter line (visible, on purpose)
<!--LF comment opens
<payload> asset blocks (Section 11)
--<marker>--LF closing delimiter line
--><br/>LF comment closes
- The opening delimiter line is visible in plain renderers by design. It tells a reader this is not plain Markdown.
<!--is on its own line so that CommonMark treats it as an HTML comment block, which runs until-->across blank lines.- Writers MUST NOT emit trailing whitespace on either delimiter line. Readers ignore trailing spaces and tabs on them.
- Renderers SHOULD follow the comment rules of CommonMark 0.31 or later, under which the payload is hidden. Hiding is guaranteed only there.
11. Payload
11.1. Asset Blocks
- Each asset is encoded as standard base64 with padding [RFC4648], wrapped at 64 characters per line. Every line but the last is exactly 64 characters. Every line ends with LF.
- The base64 is canonical: decoding and re-encoding it gives the same
characters. An asset of N octets takes exactly
4 * ceil(N / 3)characters, and its decoded length MUST equal its size in the index. - Blocks appear in the same order as the index rows.
- Consecutive blocks are separated by exactly one empty line.
- Between the
<!--line and the closing delimiter line there is nothing but the blocks and those single empty lines. A conforming writer never puts anything else there. Anything else makes the file invalid, and readers flag it as a possible hidden channel: data riding inside a comment that every renderer hides. - A zero-byte asset is allowed. Its block is empty, so two empty lines
appear in a row. Its index row shows size 0 and a range of
-.
With blank lines between blocks, vip in vi selects one asset's
whole block.
11.2. Paths
- Asset paths use only ASCII letters, digits, hyphen, underscore, dot, and slash.
- Absolute paths are invalid. So are empty segments (
a//b), a trailing/, any segment made only of dots (.,..,...), any segment ending in a dot (x., which Windows silently turns intox), and any segment named.gitin any case. - One path may not be both a file and a folder:
aanda/bin the same file make it invalid. - Other dotfiles, such as
.env, are allowed. - Windows reserved names (
CON,PRN,AUX,NUL,COM1toCOM9,LPT1toLPT9) are judged by the part of a segment before its first dot, in any case, socon.txtcounts. They are allowed in the file. A tool that extracts assets on Windows MUST rename or refuse them. - A tool that extracts assets MUST extract into a new, empty folder, MUST NOT follow links that already exist there, and MUST NOT overwrite any file.
- Writers SHOULD put assets under
assets/. - Mixed case is allowed. Two identical paths, or two paths that differ only in case, make the file invalid.
- Any kind of file may be an asset. Section 20 says what that means for readers.
11.3. Linking
- The body refers to assets with plain relative paths, such as
. - Readers strip a leading
./from a link before lookup. - Readers strip a fragment, everything from
#on, before lookup, and hand it to the renderer.#page=Nselects a page of a PDF [RFC8118]. Paths never contain#, so this is unambiguous. - Readers match a link to a path exactly, case included, so a Zipdown reader, a web server, and extracted files all agree.
- Links resolve only against the file's assets. A file has no outside dependencies.
- Links with a URL scheme are ordinary Markdown links, outside this memo.
11.4. Image Formats and Alternates
- SVG is the first-class vector format. Most figures drawn in design sessions are SVG, so every renderer is expected to show it well.
- PDF is allowed and never required.
- Raster images are allowed.
- The format has no alternates: a file carries one version of each figure, and there is no way to declare two assets interchangeable, such as an SVG and a PDF of the same drawing. Alternates would let a payload hide, because readers on different machines could pick different versions and see different pictures.
- What a receiver does with an asset after verifying it, such as sanitizing an SVG or converting it to PDF for local display, is the receiver's business, as long as the file itself is never changed.
12. Index
Directly after the comment-close line, with nothing in between, comes
a fenced code block with the info string zipdown-index, one row per
asset. A file has exactly one index:
```zipdown-index
path size lines(1-based,inclusive) base64-sha256 sha256
assets/dot.png 70 14-15 <b64-sha256> <sha256>
assets/chart.svg 812 17-30 <b64-sha256> <sha256>
assets/empty.txt 0 - e3b0c442...b855 e3b0c442...b855
```
The first row is mandatory and is exactly
path size lines(1-based,inclusive) base64-sha256 sha256, its
words separated by spaces. It makes every index self-describing.
Columns are separated by one or more ASCII spaces (0x20), never other whitespace, and aligned by the writer. Each row has exactly five columns:
- Path. Paths never contain spaces, so splitting on whitespace is safe.
- Size of the decoded asset, in octets, in decimal, with no leading zeros.
- Line range of the asset's block,
first-last, 1-based and inclusive, counted from the first line of the file, assed,awk, andvicount. A zero-byte asset shows-. - Base64 SHA-256: the hash of the asset's block exactly as it sits in the file, every line with its LF, 64 lowercase hex digits. A reader can hash the bytes in the line range directly, without decoding.
- Decoded SHA-256: the hash of the decoded asset, 64 lowercase hex digits.
Both hashes are full length. For a zero-byte asset both are the SHA-256 of nothing.
line 12 --<marker>
line 13 <!--
line 14 iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4 \ base64-sha256
line 15 2mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg== / covers these lines
line 16 with their LFs
line 17 PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZp \ next asset's
... / base64-sha256
line 30 ...
line 31 --<marker>--
sha256 (decoded) = SHA-256( base64-decode( lines 14-15 ) )
The index MUST be internally consistent:
- Every line range lies strictly between the
<!--line and the closing delimiter line. - Rows appear in ascending order of their ranges, and no two ranges overlap.
- Each range spans exactly the number of lines its size implies. An
asset of N octets (N > 0) encodes to
4 * ceil(N / 3)base64 characters, which isceil(4 * ceil(N / 3) / 64)lines.
A reader can check all three without decoding anything, so a lying index is caught before memory is allocated for an asset. Readers MUST compare a size against its range's length in octets before doing any arithmetic with it, so a huge size cannot overflow the check.
The index is visible in plain renderers, in monospace, as a list of what the file carries. Fenced code blocks are core CommonMark.
13. Epilogue
After the index comes the epilogue: the English line, then zero or more translation lines, with no prefixes. The English line is:
Psst, this is a Zipdown file! There are images and files tucked inside that a plain Markdown viewer can't show you. Grab a Zipdown reader at zd.treeflow.ai to see everything.
- The English line is mandatory, canonical, and exact, and readers check it byte for byte.
- Translations are not canonical. A writer may add any number of translation lines, in any languages and any wording, or none.
- Every epilogue line but the last ends with one space followed by a
backslash (
\). The backslash is CommonMark's hard line break, so each line renders on its own, even in renderers that escape raw HTML. In renderers older than CommonMark, the backslash shows as a literal character. With no translations, the English line has no backslash. - Each translation line is one line of plain text. It MUST NOT be
empty, and MUST NOT begin with whitespace, a digit, or any of
#>-+*_=`~<|. Any of those could start a new Markdown block, end the epilogue's paragraph, or swallow the lines after it.
The reference writer adds one translation, in Chinese, so its epilogue reads:
Psst, this is a Zipdown file! There are images and files tucked inside that a plain Markdown viewer can't show you. Grab a Zipdown reader at zd.treeflow.ai to see everything. \
嘿,这是一个 Zipdown 文件!里面藏着普通 Markdown 查看器显示不了的图片和文件。到 zd.treeflow.ai 下载 Zipdown 阅读器,就能看到全部内容。
14. Pointer
The line just before the terminator is:
<!-- zd-index <decimal> <32 lowercase hex digits> -->LF
- Both fields give the octet offset, from the start of the file (byte 0 is the first byte of the header), of the index's opening fence line.
- The decimal field has no leading zeros.
- The hex field is always 32 digits. The top 8 hex digits (32 bits) are reserved and MUST be zero. The low 24 hex digits (96 bits) equal the decimal value.
- Because the hex field is fixed width and the terminator is always the same two octets, the hex field sits at a fixed distance from the end of the file: its first digit is 39 octets before the end.
- The two fields MUST agree, and the offset MUST be that of the one index, directly after the comment-close line. Anything else makes the file invalid.
- Nonzero reserved bits make the file invalid. So does an offset at or past the end of the file.
- A reader whose integers cannot hold the offset treats the file as too large to read. (The limit in Java and PHP is 2^63 - 1 octets, about 8 exabytes.)
- The pointer is a hint. Readers SHOULD check that the fence is really there.
- The line starts with
<!--, so renderers hide it.
15. Terminator
The last line of the file is a single period:
.LF
- The convention is SMTP's, where a line holding only
.ends the message data [RFC5321]. - A file whose last line is not exactly
.was truncated, or its writer never finished it. So was a file whose pointer is missing or garbled. - There is no dot-stuffing. SMTP doubles the leading
.of any data line that starts with one, because it finds the end by scanning. Zipdown finds the terminator at the end of the file and never scans for it, so lines elsewhere that hold only.mean nothing. - In a plain renderer, the period shows as a one-character last paragraph, under the epilogue.
16. Signatures
A file MAY carry one signature, in its header:
zd-signature: ed25519 <key-id> <signature>
It covers the header, the index, and the epilogue. The header and the index hold the body's hash and every asset's hashes, and every other part of the file is fixed or checked byte for byte, so a valid signature vouches for the whole document.
- Algorithm: Ed25519 [RFC8032], and nothing else. Verification follows RFC 8032 strictly: non-canonical encodings and small-order keys are rejected.
<key-id>: 16 lowercase hex digits, the first 8 octets of the SHA-256 of the signer's 32-octet public key. It only picks which known key to try. Readers compare full public keys, and viewers never present a key id as an identity.<signature>: the 64-octet signature in standard base64 with padding, 88 characters.- Signed message, the concatenation of:
Zipdown signature v1followed by LF, a context string, so a Zipdown signature can never be passed off as a signature over anything else;- the header, from its first octet to the header end, with exactly the space before the signature and the 88 signature characters deleted;
- the index, from the first octet of its opening fence line through the LF of its closing fence line;
- the epilogue, from the first octet of its first line through the LF of its last line.
- Not covered, because they are fixed or checked: the separator, the delimiters, the payload (pinned by the index's hashes), the pointer (checked against the index), and the terminator.
zd-date, being in the header, is covered.- Writing. The signature has a fixed length, so a writer can lay out the whole file with the signature line in place, then sign and fill it in without moving any offset. Adding a signature to an existing file adds a header line and shifts every offset, so the file is rewritten.
- Knowing a signer is outside the format. Each reader keeps its
own list of trusted public keys, the way SSH keeps
known_hosts. - The test key in Section 24 is public. Readers MUST NOT trust it.
What a reader and a viewer do:
- Valid signature, known key: the header, the index, and the epilogue are authentic, so the reader may trust MLO, the line ranges, and the sizes without cross-checking them against each other. It still checks the pointer, bounds every range by the end of the file, checks the body's hash before showing the body, and checks each asset's hashes before showing that asset. The viewer shows a badge outside the document area, such as "Signed by qrux, November 11th, 2026", never inside it.
- Well-formed signature, unknown key: the viewer shows
"unverified signature, key
<key-id>", never plain "unsigned". - Invalid signature: the viewer flags the file. It MUST NOT hide a failed signature.
- No signature: the file is unsigned and verified as usual.
- A CR anywhere: no badge at all (Section 3).
A known signer means the content is authentic, not that it is safe. Stripping, sanitizing, and the rule against remote loads apply to signed files the same way (Sections 17.1 and 20).
No encryption. Zipdown defines no encryption: an encrypted file is
no longer readable Markdown. To protect a file in transit, encrypt the
whole file with a standard tool such as age [age].
17. Reading a File
Informative.
read the first few KB
|
v
zd-version in the header? --no--> plain Markdown, done
| yes
v
MLO > 0, separator at --no--> scan line by line for
header end + MLO, delimiter ----------> the opening delimiter;
line right after it? body ends 34 octets before it
| yes |
v v
body known: render it <----------------------+
|
v
last line is "."? read the pointer above it --> seek to the index
|
v
check the index (ranges inside the payload, ascending,
no overlaps, sizes match line counts)
|
v
for each asset a link needs: hash the line range (base64-sha256),
decode, hash again (sha256), hand the bytes to the renderer
A tool that never shows the file to a person may skip verification. A tool that shows it MUST verify what it shows (Section 20). A valid signature from a known key lets it skip some cross-checks (Section 16).
17.1. Verify, Then Strip
Some parts of a file are invisible in every renderer: HTML comments in the body, assets nothing links to, and invisible characters. People cannot see them, but software that reads the file can, which makes them a channel for hiding instructions aimed at AI agents. So a conforming reader, after verifying the original bytes, strips them before handing the document on:
-
Body comments (
<!-- … -->) are removed. They are legal in the file; they just never get past the reader. -
Unlinked assets are dropped.
-
Image metadata is pulled out of every image asset the body shows: EXIF, XMP, IPTC, comments, PNG text chunks, the time stamp, application data, and any bytes after the end of the image; in SVG, comments,
metadata, anddesc. Two things stay, because the picture needs them:- Color. An ICC profile keeps its color data. Its four text
tags are replaced by fixed placeholders:
descbyColor profile,cprtbyUnspecified, anddmndanddmddbyUnspecified. The profile ID, which no longer matches, is set to zero, meaning "not computed". - Orientation. The EXIF orientation is kept, alone, so the picture still shows upright. (A reader MAY instead rotate the pixels, if it keeps the color profile.)
This works on the verified bytes the reader hands on. The file itself, and its hashes, never change.
- Color. An ICC profile keeps its color data. Its four text
tags are replaced by fixed placeholders:
-
Invisible characters are removed: Unicode tag characters (U+E0000 to U+E007F), zero width space (U+200B), word joiner (U+2060), byte order marks (U+FEFF), and the bidirectional controls (U+202A to U+202E, U+2066 to U+2069). Zero width joiner (U+200D) and zero width non-joiner (U+200C) stay: emoji and several scripts need them.
Everything downstream of the reader, agents and tools included, sees only the stripped document. A reader that hands a document to an agent SHOULD pass images as pixels only, never as the original file bytes.
A viewer shows what was stripped in a sidebar beside the document: each comment anchored where it sat, the unlinked assets, a mark wherever invisible characters were removed, and each image's metadata, original profile text included. HTML is not in the sidebar; it gets the notice in Section 7.12. Source mode, the raw file, shows everything. People can see all of a file; agents cannot be steered by what people cannot see.
18. Appearance in Plain Renderers
Informative. A renderer that knows nothing about Zipdown shows:
+--------------------------------------------------+
| Title | body, as usual;
| ... [broken image: dot]{width=10%} ... | asset images break
| ------------------------------------------------ | rule
| -----END OF DOCUMENT----- | separator
| ------------------------------------------------ | rule
| --3e9c81245fe5...dbd9ba-af2fb38cc34248a2 | marker line, visible
| | on purpose
| path size lines(1-based,inclusive) ... | index, monospace
| assets/dot.png 70 14-15 ... |
| |
| Psst, this is a Zipdown file! ... | epilogue, one
| (the same, in Chinese) | language per line
| |
| . | terminator
+--------------------------------------------------+
(payload and pointer: hidden inside HTML comments)
Renderers vary. Some escape raw HTML, in which case the payload shows
as text. Renderers that follow comment rules older than CommonMark
0.31, which forbid -- inside a comment, may end the comment early at
the closing delimiter line.
19. Extracting an Asset by Hand
Informative. Given a line range L1-L2 from the index:
sed -n 'L1,L2p' file.zd | base64 -d > out
awk 'NR>=L1 && NR<=L2' file.zd | base64 -d > out
head -n L2 file.zd | tail -n +L1 | base64 -d > out
In vi, from command mode:
:L1,L2w !base64 -d > out
Older macOS base64 spells decode -D.
20. Security Considerations
A Zipdown file is untrusted input. Most risks of Markdown itself, including raw HTML in the body, are already described in [RFC7763], and renderers apply their usual sanitization. What Zipdown adds:
- Hints can lie. MLO, index sizes and ranges, and the pointer are all hints. Readers SHOULD check them (Sections 4.4, 12, and 14) and MUST NOT read past the end of the file on their say-so.
- Parser differentials. Duplicate
zd-keys (Section 4.3), duplicate or case-colliding paths (Section 11.2), and an inconsistent index (Section 12) make a file invalid, because two readers could otherwise disagree about what the file contains. - Header parsing. The header is parsed as plain data only (Section 4.1). Some YAML libraries can construct objects or run code from tags.
- Unsafe paths. Absolute paths,
..segments, and the other path forms in Section 11.2 are invalid. A tool that extracts assets MUST NOT write outside its target directory, and on a case-insensitive file system it MUST NOT let one asset overwrite another. - The reader serves the assets. Plain Markdown links to assets hosted somewhere else, and the browser's origin rules apply. A Zipdown reader hands embedded assets to its renderer itself, so it effectively becomes their server. It SHOULD treat every asset like content from an unknown website, never as its own.
- Active content. Any kind of file may be an asset. SVG can carry scripts, event handlers, and embedded HTML; HTML assets are pages. A renderer MUST display SVG only through an image element, never as inline markup, and MUST NOT open any asset as a document in its own origin. Assets that are not images are offered as files to save. Assets are served from an isolated origin with a content policy that allows no scripts.
- Raw HTML in the body. In
zd1none is rendered (Section 7.12). Under any other flavor it MUST be sanitized to this allowlist, with every attribute removed:br,sup,sub,kbd,details,summary,table,thead,tbody,tr,th, andtd. Some Markdown libraries pass raw HTML through by default. - Metadata in images is text people do not see, so it is a channel too; readers pull it out (Section 17.1).
- Links. An image source resolves only to an asset path. Links
that start with
//(scheme-relative) orfile:are never followed: on Windows,//host/sharereaches out to a network share and leaks the user's credentials. positionvalues are checked against a strict grammar: one or two items, each one oftop,bottom,left,right,center, or a percentage from 0% to 100%. Anything else is ignored, so a value can never inject CSS.- Depth limits. Indent (block and region together) and nested inline marks are drawn at most 16 levels deep. Region markers still count fully for the balance rule in Section 7.8.
- Sanitizing is a separate step. A reader that verifies hashes and a sanitizer that rewrites content do not mix: sanitizing changes the bytes. The natural order is read, verify, sanitize, render.
- No alternates. One figure, one asset (Section 11.4), so a file cannot show different pictures to different readers by design.
- Attributes carry no style. Image attributes are limited to the keys in Section 7.11. They are not CSS and cannot reach anything outside the image's box.
- Showing means verifying. A renderer that shows a file to a person MUST check the body's hash before showing the body, and an asset's hashes before showing that asset. Otherwise two apps could show the same file differently: one trusting a stale MLO, one catching the mismatch. Tools that never show content, such as indexers, MAY skip verification.
- Lookalikes in the body. The body is free text, so it can contain lines that look like a separator, an index, an epilogue, or a terminator. Readers find the real ones by position (the header, the marker, and the pointer), never by appearance, so a lookalike is just body text. A person reading the raw file can still be fooled.
- No remote loads. A renderer MUST NOT load anything from outside the file, such as remote images, fonts, or styles, unless the person viewing it turns that on. A file carries everything it needs; that is the point of the format. A remote load would also tell a third party when the file was opened.
- Integrity and authenticity. The hashes catch damage and careless edits; anyone who can change an unsigned file can recompute them. A signature from a known key adds authenticity (Section 16). It does not make the content safe, and it does not cover the unsigned parts listed there.
21. Media Type, Extension, and Site
- Extension:
.zd. - Media type:
text/markdown; charset=UTF-8; variant=zipdown[RFC7763]. - Site:
zd.treeflow.aiis the address written into every file. It permanently redirects totreeflow.ai/zd, where the site is served. If the site ever moves, only the redirect changes, and every file already written still points to the right place. - The site keeps two living lists that are not part of this memo:
- Emoji shortcodes (Section 7.7), at
treeflow.ai/zd/emojiandtreeflow.ai/zd/emoji.json. The first version is GitHub'sgemojilist [gemoji]. - Mention platform codes (Section 7.6), at
treeflow.ai/zd/platformsandtreeflow.ai/zd/platforms.json. The first version:ghGitHub,liLinkedIn,ytYouTube,twTwitter,bsBluesky, andsuSubstack. - Viewers bundle a versioned copy of each list. They never fetch a list while showing a file, because that would be a remote load.
- Emoji shortcodes (Section 7.7), at
- A future binary encoding would not be valid text and would get its own type and its own RFC.
22. Implementations
- Reference: C, PHP, and Java.
- Also provided: Python and JavaScript.
- The C reference is dependency-free: SHA-256 from the reference code
in RFC 6234, base64 from BIND's
b64_ntopandb64_pton, with license notices kept. - Every other language listed uses only its standard library.
- A conformance test suite, with files that must be accepted, files
that must be rejected, and the exact rendering of every
zd1feature, accompanies the reference implementations.
23. Future Work
- Spans for combined turns: syntax letting a combined Zipdown file give back each original turn with a matching hash.
24. Example
A complete, verified Zipdown file carrying a 1x1 PNG and a short text
file. For reproducibility, R was derived from a fixed seed instead of
a random source; a real writer MUST use a cryptographically secure
random source. The same bytes travel inside this document as
assets/example.zd.
---
zd-version: 1
zd-markdown-length-octets: 107
zd-markdown-sha256: 701b9eb2ff1a4cd68184c120c84f68d715fe0bceab30cfbd41703a59dcbf40ce
zd-boundary: bd0805a4e5e77c086265536f5094c682f8dc0d06a8c26e62-41703a59dcbf40ce
---
# Hello, Zipdown
Here is a dot: {width=10%}
And a note: [notes](assets/notes.txt)
***
-----END OF DOCUMENT-----
***
--bd0805a4e5e77c086265536f5094c682f8dc0d06a8c26e62-41703a59dcbf40ce
<!--
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhf
DwAChwGA60e6kgAAAABJRU5ErkJggg==
SGVsbG8gZnJvbSBpbnNpZGUgYSBaaXBkb3duIGZpbGUuCg==
--bd0805a4e5e77c086265536f5094c682f8dc0d06a8c26e62-41703a59dcbf40ce--
--><br/>
```zipdown-index
path size lines(1-based,inclusive) base64-sha256 sha256
assets/dot.png 70 18-19 a666a27bb8de48cd9fa7e147d451a1d5b7ed400fd7f8e3a3944607c68ff1e6d4 497790947d4666760ce38f3c00e852c71fdb66cae849bae8e9ede352719e1581
assets/notes.txt 34 21-21 4eb8c6623242149abd72ed676f95c77a595766610c2e8b34dcf33634b8537843 2733e8b5c00a8b570f9fd273463a75600f35ec69d6533877568ebf5b809b1c76
```
Psst, this is a Zipdown file! There are images and files tucked inside that a plain Markdown viewer can't show you. Grab a Zipdown reader at zd.treeflow.ai to see everything. \
嘿,这是一个 Zipdown 文件!里面藏着普通 Markdown 查看器显示不了的图片和文件。到 zd.treeflow.ai 下载 Zipdown 阅读器,就能看到全部内容。
<!-- zd-index 658 00000000000000000000000000000292 -->
.
Checks performed on this example: it contains no CR; its header ends at octet 217, and the separator begins exactly at that plus MLO (107), with the opening delimiter line right after it; the marker's tail equals the last 16 hex digits of zd-markdown-sha256; the index rows (assets/dot.png, 70 octets, lines 18-19; assets/notes.txt, 34 octets, lines 21-21) lie inside the payload, ascend, and span the line counts their sizes imply; both hashes on each row match; the epilogue starts with the exact English line and has one translation; the pointer (658) lands on the index fence; and the last line is the terminator.
A signed copy travels as assets/example-signed.zd. It is signed
with a test key whose 32-octet private seed is the SHA-256 of the
text Zipdown example key: published, never trust. Anyone can
recompute that key, so it MUST NOT be trusted for anything real. Its
public key, in hex:
3cec666df17aaa5394160c49bf387f23b91356fd8917c3b6febf495d9d493cd0
Its header:
---
zd-date: 2026-11-11T21:04:00.000000000Z
zd-version: 1
zd-markdown-length-octets: 107
zd-markdown-sha256: 701b9eb2ff1a4cd68184c120c84f68d715fe0bceab30cfbd41703a59dcbf40ce
zd-boundary: bd0805a4e5e77c086265536f5094c682f8dc0d06a8c26e62-41703a59dcbf40ce
zd-signature: ed25519 ad7117d6ed57a87b ENM7uyF9j23ETthigh+g4iDvJRqRGaV2Mq7CdLzH/EcnZ2/B/qIAMIp0CKuvqJ7CyBpPrf7dYpd3H0greJbqAA==
---
Checks performed on the signed copy: every check above passes for it too; its key id matches the public key; and the Ed25519 signature over the context string, the header with the signature removed, the index, and the epilogue verifies.
25. Open Issues
- Metadata in other formats. Section 17.1 names what is pulled out of JPEG, PNG, WebP, GIF, and SVG. AVIF, HEIC, TIFF, and PDF carry metadata too, and are not covered yet; until they are, readers pass them to agents as pixels only.
- The conformance test suite (Section 22) is still to be written.
26. References
26.1. Normative References
- [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, March 1997.
- [RFC8174] Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words", BCP 14, RFC 8174, May 2017.
- [RFC3629] Yergeau, F., "UTF-8, a transformation format of ISO 10646", STD 63, RFC 3629, November 2003.
- [RFC4648] Josefsson, S., "The Base16, Base32, and Base64 Data Encodings", RFC 4648, October 2006.
- [RFC7763] Leonard, S., "The text/markdown Media Type", RFC 7763, March 2016.
- [CommonMark] MacFarlane, J., "CommonMark Spec", version 0.31.2, https://spec.commonmark.org/0.31.2/.
- [GFM] GitHub, "GitHub Flavored Markdown Spec", version 0.29-gfm, https://github.github.com/gfm/.
- [RFC8032] Josefsson, S. and I. Liusvaara, "Edwards-Curve Digital Signature Algorithm (EdDSA)", RFC 8032, January 2017.
- [FIPS180-4] NIST, "Secure Hash Standard (SHS)", FIPS PUB 180-4.
26.2. Informative References
- [RFC7468] Josefsson, S. and S. Leonard, "Textual Encodings of PKIX, PKCS, and CMS Structures", RFC 7468, April 2015.
- [gemoji] GitHub, "gemoji", https://github.com/github/gemoji.
- [RFC3676] Gellens, R., "The Text/Plain Format and DelSp Parameters", RFC 3676, February 2004.
- [age] Valsorda, F. and B. Cartwright-Cox, "age-encryption.org/v1", https://age-encryption.org/v1.
- [RFC5321] Klensin, J., "Simple Mail Transfer Protocol", RFC 5321, October 2008.
- [RFC2046] Freed, N. and N. Borenstein, "Multipurpose Internet Mail Extensions (MIME) Part Two: Media Types", RFC 2046, November 1996.
- [RFC6234] Eastlake, D. and T. Hansen, "US Secure Hash Algorithms (SHA and SHA-based HMAC and HKDF)", RFC 6234, May 2011.
- [RFC8118] Hardy, M., Masinter, L., Markovic, D., Johnson, D., and M. Bailey, "The application/pdf Media Type", RFC 8118, March 2017.
- [Pandoc] "Pandoc User's Guide: Extension: attributes", https://pandoc.org/MANUAL.html.
Acknowledgments
Site version only.
The author thanks TurboAsshole™ for several proposals:
- That a single CR anywhere invalidate the file. Rejected on review, although its spirit lives on in the rule that a CR voids every guarantee.
- That
zd-markdown-flavourbe punished. Adopted: the key is quietly ignored, as if it had never been written. - That the epilogue include French. Reversed; Japanese took its place, then left with five others to wait in the lobby.
- That PowerShell be offered an extraction snippet. Declined.
- That Rust receive an implementation. Declined.
- That the pointer hold a 128-bit offset, enough to number every atom in a small lake. The committee settled on 96 bits, enough for a hot tub full of people, and reserved the rest.
Ever since we dropped ZIP, the file's really opened up.
Appendix A. Notes for Viewers and Editors
Informative. What the reference viewer and editor do; other tools are free to differ.
- Four modes. Clean shows the rendered document with no marks and cannot be edited. Marked shows every mark, dimmed. Live shows marks only in the element being edited, in the style of Obsidian. Source shows the raw file. Any mode can sit beside any other.
- One parser. The editor and the renderer use the same parser, so they always agree on where a span starts and ends.
- No speculative styling. An opening mark with no closer styles nothing until the closer exists. A style ends exactly at its closer, and typing past the closer never extends it.
- No sticky state. Leaving a span by moving the caret, deleting its closer, pasting, undoing, or redoing leaves no style behind; the text's style always follows from the text.
- Flavor follows the file, not a setting (Section 6.2).