Specification · Version 2

Eddie Doc Review Format

A portable, versioned, schema-validated JSON file — <file>.review.json — that maps PDF review annotations back to AsciiDoc source. Built to be committed, diffed, and read by tools other than the editor.

Media type application/vnd.eddie-doc.review+json  ·  Canonical schema volland.github.io/eddie-doc/schema/review-v2.schema.json

Design goals

Portable

No absolute paths. A sidecar checked into a repo works on any machine and any clone.

Verifiable

Content fingerprints (SHA-256, byte size) let a consumer detect that the source or PDF changed since mapping ran — without re-running the matcher.

Layered

Each item separates what came from the PDF (immutable), what the matcher computed (a cache), and what the user owns (review state).

Evolvable

A top-level integer version and a $schema URL, so consumers can branch on format and validate.

Top-level shape

A session is metadata + two file references + an array of items.

{
  "$schema": "https://volland.github.io/eddie-doc/schema/review-v2.schema.json",
  "version": 2,
  "producer": { "name": "eddie-doc", "version": "0.1.5" },
  "createdAt": "2026-07-03T07:15:17.937Z",
  "updatedAt": "2026-07-12T09:02:00.000Z",
  "source": { "path": "chapter-01.adoc", "sha256": "…", "bytes": 4213 },
  "pdf":    { "path": "chapter-01.annotated.pdf", "sha256": "…", "annotationCount": 5 },
  "items": [ /* … */ ]
}
FieldReqMeaning
$schema–Format identifier; also the canonical schema URL.
version✓On-disk format version. 2 for this spec.
producer–Tool that wrote the file (name, optional version).
createdAt / updatedAt✓ISO-8601 timestamps.
source✓The .adoc input — a file reference.
pdf✓The annotated PDF input, plus annotationCount.
items✓The review items.

File references

Both source and pdf are file references:

FieldMeaning
pathRelative to the sidecar's own directory, always with POSIX (/) separators, even on Windows.
sha256Hex SHA-256 of the file's bytes at the time mapping last ran. Optional — a migrated v1 session has none until it re-maps.
bytesSize at map time. pdf additionally carries annotationCount, a cheap staleness signal.

Items

Each item is a PDF annotation, its match into source, and its review state — in three separate blocks so they evolve and diff independently.

{
  "id": "p2-highlight-48-719",
  "annotation": {
    "kind": "highlight",
    "author": "Editor",
    "comment": "Strong framing — keep this.",
    "anchoredText": "…relationships at the center of the model.",
    "markedText": "relationships at the center of the model",
    "geometry": { "page": 2, "unit": "pt", "origin": "bottom-left",
                   "rect": [48.24, 718.58, 547.04, 748.64] }
  },
  "match": { "startLine": 6, "endLine": 7, "score": 0.887, "method": "fuzzy" },
  "state": { "resolved": false }
}

id

A stable identifier that survives re-import so review state can be reattached. Producers should prefer the PDF's own annotation id (NM) when available, since geometry-derived ids change if the PDF is re-exported at a different position.

annotation — from the PDF (immutable)

FieldReqMeaning
kind✓highlight, strikeout, underline, comment, insert, replace, other.
author–Annotation author, if the PDF records it.
comment–The editor's comment body.
anchoredText–Text physically under the markup. Over-captures the whole line for robust matching.
markedText–The tightly-bounded text inside the markup quads — used for character-precise delete/replace.
beforeText–For insert (caret) marks: text left of the caret on the same line, to place the insertion at the exact offset.
geometry✓Position in the PDF — page (1-based), unit: "pt", origin: "bottom-left", rect: [x0,y0,x1,y1]. Optional reserved quadPoints.

match — computed (a cache, null when unmatched)

The matcher's best mapping onto the source. It is recomputable — running Re-map rebuilds it — so a consumer must treat it as a cache, not ground truth.

FieldReqMeaning
startLine / endLine✓0-based, inclusive, line range in the source.
score✓0–1 similarity of the matched span.
method–"fuzzy" (token/bigram) or "semantic" (embedding fallback).
sourceExcerpt–A snapshot of the matched source text for display/debug only. Goes stale the moment the line is edited — never authoritative.

state — user-owned (the only hand-edited block)

FieldReqMeaning
resolved✓The reviewer has handled this item.
confirmed–The user vouched for the link (hand-picked, or accepted a low-confidence/semantic auto-match). Keeps it out of Needs review.
manualLine–0-based line the user manually linked to; overrides match.
note–Free-form reviewer note.
Line numbers are 0-based throughout the format (match.startLine, state.manualLine). Editors that display 1-based line numbers add 1 for presentation. The effective line of an item is manualLine when set, otherwise match.startLine, otherwise unmatched.

Validate a sidecar

Any JSON Schema (draft 2020-12) validator works.

npx ajv-cli validate --spec=draft2020 --strict=false \
  -s schema/review-v2.schema.json -d path/to/chapter-01.review.json

Or reference the published schema directly from your tooling at https://volland.github.io/eddie-doc/schema/review-v2.schema.json.

Compatibility & migration

  • Reading: a consumer branches on version. Eddie Doc reads both 1 and 2. Unknown top-level fields are ignored, not rejected.
  • v1 → v2: version 1 was a flat shape with absolute paths and item fields at the top level. Eddie Doc migrates it transparently on load; the next write upgrades the file to v2. No user action required.
  • Forward changes: additive fields do not bump version. A breaking change bumps it to 3.

Relationship to the W3C Web Annotation model

This format is intentionally close to the W3C Web Annotation Data Model: an item is an annotation whose body is annotation.comment and whose target is the PDF (via geometry) plus the source (via match/state). The mapping is mechanical — anchoredText → TextQuoteSelector, match.startLine → TextPositionSelector, geometry → a quad FragmentSelector — leaving a clean path to emit standards-compliant JSON-LD in a future version without reworking the data captured here.