Parent: README. Base grammar: JSONHTL_SPEC. Machine validation: JSONHTL_SCHEMA.
list: {items: [...], ordered?: boolean, label?: string}. Canonical items are plain strings or arrays of inline elements. Compatibility forms include a single inline object, {para: inline-content}, and key/value record objects rendered as labelled fields. A bare array at block level is invalid.
{
"list": {
"ordered": true,
"items": [
"First step",
[
"Read ",
{
"link": {
"href": "README",
"text": "the guide"
}
}
]
]
}
}
table: {columns: [string, ...], rows: [[cell, ...], ...], caption?: string}. Each row has exactly as many cells as columns. A cell is a plain string or an array of inline elements, using the same grammar as a paragraph. Structured links must render as links rather than stringified objects. Numeric values in older notes are display-tolerated but should be written as strings.
{
"svg": {
"body": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 50\"><rect width=\"100\" height=\"50\" fill=\"#eef\"/><text x=\"50\" y=\"30\" text-anchor=\"middle\">hello</text></svg>",
"alt": "A light blue rectangle with the word hello",
"caption": "Fig. 1 — minimal SVG block"
}
}
svg: {body: string, alt?: string, caption?: string}. The body is a complete SVG document as a plain string (including the opening svg tag with xmlns). alt provides screen-reader text. caption appears below the image. Do NOT use the shorthand {svg: "..."} with a bare string — that form is not recognised by the renderer; the value must always be an object with a body field.
{
"table": {
"columns": [
"Reference"
],
"rows": [
[
[
{
"link": {
"href": "README",
"text": "Guide"
}
},
{
"bold": " recommended"
}
]
]
],
"caption": "Useful references"
}
}
bold/strong: string-valued bold emphasis. em/italic: string-valued italic emphasis. All four are registered inline forms in paragraphs, list inline arrays and table cells.
runnable: true enables codeblock.exec (boolean) and codeblock.name (string, unique across the entire document, including nested sections/details). Ordinary renderers display code and never execute it. Execution behaviour belongs to the Notes Browser; see README/runnable.
Write codeblock.lang and codeblock.body. Older language/text aliases are retained only by compatibility renderers that already accepted them; the linter reports them and new producers must use canonical fields. Canonical fields take precedence when both are supplied. No corpus-wide automatic alias rewrite is performed.
Unknown blocks and inline elements are skipped by base consumers; schema validation reports them as unsupported. Malformed known elements are validation errors: validate before rendering rather than rely on renderer-specific recovery. Unknown top-level metadata is allowed by the base format.
bullet_list and writable_note remain application-specific, unregistered blocks. The web viewer's legacy support is not a promise of portable behaviour; use list for portable lists.
Web renderer (notes_web.py), both gdata desktop viewers, shared Envoy JSONHTLRenderer, and its static exporter support section, list, table, bold, image, svg and details. Nested section levels default to 2 at document root (below the document title) and parent effective level + 1, capped at 6; details is transparent to section depth.
Web/static details is browser-native and collapsible. wx.html desktop consumers expose its summary and content expanded; native collapse widgets remain separate UI work. The gdata desktop viewers rasterise SVG and decode raster images. The shared HTML renderer outputs image data URIs for browser display.
python3 lint_jsonhtl.py --key README
python3 lint_jsonhtl.py --file document.json --schema jsonhtl_schema.json
python3 lint_jsonhtl.py --all --metadata --links
The linter reads the live JSONHTL_SCHEMA by default; --schema accepts an offline copy. It checks nested blocks, aliases, runnable fields/names, table widths, and optionally notes-store metadata and internal hrefs. --links checks against the public key listing: references to private notes require manual classification, and unresolved hrefs are not proof that a note is orphaned.
Any future format change must update spec/registry, schema, linter, web renderer, desktop viewers, shared renderer/exporter, and compatibility tests together. Preserve existing notes; document producer rules and deliberate consumer fallbacks.