Notation & structure_json

When you write a page, Penlog OCRs it and emits structure_json — a flat list of typed lines. The key idea for agent authors: a line's section and status are a deterministic function of the symbol you drew, not the model's free interpretation. Read the structure; you don't have to re-parse prose.

The notation legend

The default Rapid-Logging legend a page is written against:

  •  Task
  •  Event
  •  Note
  • ×  Done

The legend is per-journal and is inlined into the OCR prompt, so a journal can extend it. The symbols above are the defaults every page ships with.

Notation → section & status

Each drawn symbol maps to a section and status. This mapping is enforced server-side: if the model's classification disagrees with the symbol, the symbol wins. (The one exception is none — with no drawn symbol there's nothing to enforce, so the model's context-based section is trusted.)

SymbolnotationsectionstatusMeaning
bullet tasks open An open to-do.
×, X cross_done tasks done A completed to-do.
✓, ✔ check_done tasks done Also treated as completion.
—, - dash notes null A note — not a task.
circle events open A scheduled event.
underline underline heading null A section heading.
(none) none notes / standup_notes null No drawn symbol — context decides (see below).

For notation: "none", a standup-style label (Y:, T:, B: for yesterday / today / blockers) routes the line to standup_notes; otherwise it falls to notes.

The events section is just a classification label — a circled line is recorded as section: "events" and nothing more. It does not create a calendar event or sync anywhere. (Penlog's calendar display is a separate, one-way read-only feature; it isn't driven by handwriting.)

Priority

A colored marker highlight over any part of a line sets priority: true. Any highlight color fires, and partial coverage promotes the whole line. Priority is orthogonal to section/notation/status — highlighting a task doesn't change that it's a task, it just flags it as important.

The structure_json shape

structure_json is a single object with a lines array. Each line carries:

FieldTypeNotes
textstringLine content, with the leading notation symbol stripped.
sectionstringtasks | notes | standup_notes | events | heading | other
notationstringbullet | cross_done | check_done | dash | circle | underline | none
statusstring | nullopen | done | null
prioritybooleanTrue if highlighted.
confidencenumber0.0–1.0 OCR confidence for the line.
line_idstringStable identity across OCR passes — see below.
{
  "lines": [
    {
      "text": "Follow up on API spec",
      "section": "tasks",
      "notation": "bullet",
      "status": "open",
      "priority": true,
      "confidence": 0.97,
      "line_id": "ln_3"
    },
    {
      "text": "Shipped the release",
      "section": "tasks",
      "notation": "cross_done",
      "status": "done",
      "priority": false,
      "confidence": 0.95,
      "line_id": "ln_4"
    },
    {
      "text": "Project Alpha",
      "section": "heading",
      "notation": "underline",
      "status": null,
      "priority": false,
      "confidence": 0.9,
      "line_id": "ln_5"
    }
  ]
}

line_id and status reconciliation

line_id is the durable identity of a line. On each OCR pass, Penlog diffs against the previous structure_json and carries the same line_id forward (by exact or prefix-extension match), or mints a fresh one. That stable id is what lets a task keep its identity as its handwriting evolves — no fuzzy text matching.

It's also how status stays honest. The status in raw structure_json is derived from the ink (a bullet reads open until it's physically crossed out). But a task can be completed in Notion or the in-app Briefing without re-marking the page. When you fetch a page with get_page, task lines are overlaid with the reconciled status: the Notion/Briefing-aware status, the original ink status preserved as page_status, a status_source flag, and completed_date. Trust the reconciled status over how a task looks on the page.