This document covers formatting rules that extend the base JSONHTL specification.
String values within JSONHTL may contain markdown inline formatting. For example, a paragraph string may include backtick code, bold, or emphasis using standard markdown syntax.
Where markdown and JSONHTL objects express the same thing, both are valid. For example, these are equivalent:
{"para": ["See ", {"code": "config.yaml"}, " for details."]}
{"para": ["See `config.yaml` for details."]}
Similarly, a heading can be expressed as a JSONHTL block or as a markdown-formatted string within a para element, though the structured form is preferred for machine processing.
Items in a para array are rendered inline (concatenated). To display items on separate lines, use separate para blocks.
WRONG — items run together:
{"para": ["1. First item", "2. Second item", "3. Third item"]}
Renders as: "1. First item2. Second item3. Third item"
CORRECT — each item on its own line:
{"para": ["1. First item"]},
{"para": ["2. Second item"]},
{"para": ["3. Third item"]}
This applies to both numbered lists and bullet lists. Each list item should be a separate para block.
Systems may normalise all content to one form (structured JSONHTL objects or markdown strings). During the current phase, both coexist. A normaliser tool is planned — see Future.
content may be a plain string instead of a list. The string "Just some text." is equivalent to [{"para": ["Just some text."]}]. Parsers should normalise to list form internally.
Likewise, where any value is specified as a list, a bare scalar is shorthand for a single-element list.
Tabular data uses the table block type. A table contains columns (list of column header strings) and rows (list of rows, where each row is a list of cell values in column order).
{
"table": {
"columns": [
"Layer",
"Role"
],
"rows": [
[
"GPT",
"thinking"
],
[
"Email",
"communication"
],
[
"Notes DB",
"memory"
]
]
}
}
Cell values are strings. Inline markup (bold, code, etc.) within cell strings is permitted as with para text.
This structure maps directly to a Pandas DataFrame:
import pandas as pd
table = block["table"]
df = pd.DataFrame(table["rows"], columns=table["columns"])
And back again:
table_block = {"table": {"columns": list(df.columns), "rows": df.values.tolist()}}
Tables should be used for genuinely tabular data. For simple key‑value pairs where only two columns exist, a series of para blocks may be clearer. Use judgement.
Structured lists use the list block type. A list has an optional label (string), an optional ordered flag (boolean, default false), and an items array.
{
"list": {
"label": "issues",
"ordered": false,
"items": [
"A plain string item",
["An item with ", {"code": "inline elements"}],
{"id": "foo", "title": "An object item", "status": "open"}
]
}
}
Each item may be:
• A string — plain text, markdown inline formatting permitted.
• A list of inline elements — same content model as para.
• An object — arbitrary key-value pairs. Renderers display each pair as key: value within the list item. Values that look like internal note paths (containing /, no spaces, not http) should be rendered as navigate links.
The label, if present, is rendered as a visible bold prefix immediately before the list. Renderers use <ul> for unordered and <ol> for ordered lists.
Human view: the label appears as bold text, items appear as a bullet or numbered list; object items show their properties inline.
Machine view: a dict with keys label, ordered, items; items are strings, inline arrays, or dicts and can be iterated and filtered directly.