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.)
| Symbol | notation | section | status | Meaning |
|---|---|---|---|---|
• | 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.
Theeventssection is just a classification label — a circled line is recorded assection: "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:
| Field | Type | Notes |
|---|---|---|
text | string | Line content, with the leading notation symbol stripped. |
section | string | tasks | notes | standup_notes | events | heading | other |
notation | string | bullet | cross_done | check_done | dash | circle | underline | none |
status | string | null | open | done | null |
priority | boolean | True if highlighted. |
confidence | number | 0.0–1.0 OCR confidence for the line. |
line_id | string | Stable 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.