JSONHTL Linter Specification

Specification for a strict validator (linter) for JSONHTL documents. The linter is stricter than any renderer -- it flags anything not in the spec or a registered extension, even if renderers silently ignore it. The goal is to catch bad documents before they enter the store, not to fix them. When a document fails, the linter should tell the writer where to find the correct spec so they can update their source of information.

Related Notes

Code

The linter is implemented in two Python files on the DAP AWS instance:

Test Findings

2026-07-06 first test run -- 4 notes tested; gdata-server/todo failed with 2 unregistered inline types (em, strong) and 1 bare-string para. Decision pending: register or fix.

Schema Location and Sync Rule

The machine-readable schema is at JSONHTL_SCHEMA (JSON Schema Draft-07). The linter loads it dynamically from that note at runtime. This note and JSONHTL_SCHEMA must be kept in sync for structural changes. Semantic-only changes (e.g. adding a description to a field) may not require updating the other. When in doubt, update both.

Consumers -- Things That Use JSONHTL

Any structural change to the spec or schema may require updates in these:

The GUI renderer (notes_browser.py) currently accepts the wrong codeblock field name text as a fallback. This fallback should be removed once the linter is enforced at the write path. See Future Improvements.

What the Linter Checks

The linter first validates the document against the JSON Schema in JSONHTL_SCHEMA. It then applies additional checks that JSON Schema cannot express.

Document Level

Content

Blocks

para block

heading block

codeblock block

list block

table block

image / svg / details blocks (JSONHTL_SPEC base)

section block (nested container)

link inline element

code inline element

bold inline element (registered extension)

Checks Beyond JSON Schema

These cannot be expressed in JSON Schema and must be implemented in linter code:

Error Messages

When the linter finds a problem it should say what is wrong, what the correct form is, and where to find the spec. The intent is that an LLM or human writer can immediately update their source of information and not repeat the mistake.

Example for codeblock alias:

ERROR block 3 (codeblock): field 'text' is not valid -- the correct field name is 'body'. Where did you get this syntax from? The authoritative spec is JSONHTL_SCHEMA (note key) and gdata-server/linter. Update your source of information before writing further notes.

Example for unknown block type:

ERROR block 5: unknown block type 'bullet_list'. Valid types are: para, heading, codeblock, list, table, image, svg, details, section. Where did you get this syntax from? See JSONHTL_SCHEMA and gdata-server/linter.

Example for schema validation failure (generic):

ERROR block 2 (heading): missing required field 'level'. See JSONHTL_SCHEMA for the correct structure.

CLI Design

The linter is a Python command-line script. It fetches the schema from the live notes store (or accepts a local schema file for offline use), validates a document, and prints all errors to stdout.

# Lint a single note by key
python lint_jsonhtl.py --key gdata-server/linter

# Lint a local JSON file
python lint_jsonhtl.py --file /path/to/doc.json

# Walk all keys and lint each in turn
python lint_jsonhtl.py --all

# Use a local schema file instead of fetching from the store
python lint_jsonhtl.py --schema /path/to/schema.json --key some/note

# Exit code: 0 if clean, 1 if any errors found

The notes CLI loader (notes load) should enforce a linter pass before uploading a document. A --no-lint flag overrides this. This keeps bad documents out of the store at the write path.

Future Improvements

version 5  ·  updated 2026-08-04