Files
Marcos Paulo e8c8c06d1e
ci / ci (macos-latest) (push) Has been cancelled
ci / ci (ubuntu-latest) (push) Has been cancelled
ci / ci (windows-latest) (push) Has been cancelled
feat: smart folder model, media time fragments, link card URL guard (MiniMax); specs for 10 more gaps (Codex) and the registry with bloat measurements
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 14:41:26 -03:00

6.9 KiB

PDF annotations and visual excerpts (agy gap 1, roadmap 32)

Research (2026-10-21; agy claims checked)

Heptabase documents PDF annotation; its deep links include PDF pages and highlights. Obsidian embeds document page links. These verify demand, not the agy rect= syntax: that syntax below is a NotesCraft extension. Logseq/SiYuan feature equivalence was not established and is dropped. The suggested new parser crate is not justified: crates/pdf/src/lib.rs is an exporter, not an arbitrary-PDF reader. crates/engine/src/vault/pdf_preview.rs already rasterizes via PDFKit/JXA on macOS and pdftoppm elsewhere; UI crates/ui-egui/src/views/media_view/pdf.rs has paging/zoom but no text geometry or page count. Its initial comment understates current macOS support; inspect implementation.

Problem and goal

Annotate an existing PDF without rewriting it; select a text passage or page rectangle, attach a comment, copy a linked quote or visual excerpt into a note, and navigate back to exact page/region. Reuse existing PDF crate for pure geometry/sidecar types, engine for IO, existing PDF tab for interaction. No new crate.

Design decisions

D1 Portable sidecar and anchors

Version 1 sidecar at .notescraft/pdf-annotations/<sha256-of-vault-relative-path>.json, via Vault::write_private only; original PDF byte-identical. Shape {version:1,path,source_sha256,annotations:[{id,page,kind,rects,quote,comment,colour}]}. page is 1-based; rectangles [x,y,width,height] normalized to displayed crop box, top-left origin after page rotation. kind is highlight (one or more line rectangles) or excerpt (one rectangle). Colour is a theme-independent named token, never arbitrary UI colour. IDs are opaque stable local IDs, unique within document; edits retain IDs. No fresh randomness dependency required for sequential IDs under serialized engine writes. Caps: 4 MiB sidecar, 10,000 annotations, 256 rects per annotation, quote/comment each 16 KiB; finite coordinates, positive area wholly in unit square; page 1..10,000. Unknown version, duplicate ID, invalid kind/geometry return Err before mutation. Corrupt sidecar remains byte-identical. Store source SHA-256 and mark anchors stale on content replacement; never silently remap. Renames migrate path-keyed sidecar atomically with existing rename semantics; failure preserves original association and reports error.

[[paper.pdf#page=4&annotation=pa_1]] is stable annotation navigation. ![[paper.pdf#page=4&rect=0.1,0.2,0.3,0.1]] is derived raster crop, not a mutation to PDF. Plain #page=4 remains supported. Parse only these recognized keys, reject duplicate/malformed/out-of-range values; do not reinterpret as heading. Stable-ID navigation requires sidecar; standalone rect navigation remains usable without it. Copy quote emits Markdown blockquote plus stable-ID link; excerpt command returns link and bounded PNG via attachment helper only when explicitly saving excerpt. Cache crop by content hash/page/geometry/render version, no source mutation. Missing/stale anchor shows diagnostic and opens page without claiming exact match.

D3 Engine and backend capabilities

Commands pdf.annotations.list {path}, pdf.annotations.upsert {path,annotation,expected_revision}, pdf.annotations.delete {path,id,expected_revision}, pdf.excerpt {path,page,rect,save?}, pdf.page_info {path,page}. Return revision/hash and explicit stale/capability status. Mutation rejects read-only; optimistic revision avoids lost update. pdf.excerpt defaults save=false; classify entire command WRITE in assistant gate because save=true writes an attachment (including when default is used); read-only permits only save=false. list/page_info READ; upsert/delete WRITE. Register in existing viewer group, AI read/write gate, CORE_IDS tests. pdf.page_info returns rotated crop geometry and text runs with normalized rectangles only when backend supports them. Existing PNG renderer cannot supply text runs: add bounded native-tool extraction adapter only after Q1 approval; unsupported backend disables text selection with explanation, retains rectangle tools. Never claim raster OCR is original PDF text. Worker performs rendering/extraction; timeout 15 seconds, 200 MiB source cap, 16 MiB PNG cap match existing preview; text <= 2 MiB/page, <= 100,000 runs, cancellation on tab close/change. Original text highlight capability must pass platform fixture gate before feature marked complete.

D4 UI and integration

PDF toolbar: Select text / Rectangle / Annotate; keyboard access, selected-region highlight across zoom and scroll, comment editor, delete confirmation, copy quote/excerpt. Engine mutations queued after frame; no IO during paint. Embed renderer draws clipped page texture and click opens page/annotation through shared link dispatch; no arbitrary path access. i18n tables/English keys use existing media-view pattern. No PDF editing/export annotation import, freehand drawing, or OCR in this feature.

Acceptance criteria

  1. Multi-line highlight, comment and rectangle survive reopen; original PDF SHA-256 unchanged; source replacement visibly stale.
  2. Text copy on supported backend matches fixture text; raster-only backend reports unavailable, never invents text.
  3. Stable ID opens page and overlays exact region after zoom/scroll; crop round trip works with 90/180/270-degree pages and nonzero crop-box origin.
  4. Read-only/invalid geometry/stale revision/corrupt JSON reject without any writes; missing renderer produces recoverable error.
  5. Engine tests cover rename, Unicode path, traversal/symlink rejection, duplicate IDs, malformed fragments, caps and timeout/cancel; existing page links unchanged.
  6. egui tests and read screenshots cover annotation, crop embed, keyboard navigation, Mocha/Latte and scale 2; no per-frame worker restart.

Open owner questions

  • Q1: Approve native PDFKit text extraction and installed Poppler tools (pdftotext -bbox plus geometry inspection) as platform capability contract? Or fund/approve a bundled PDF parser dependency and license review? No dependency added without answer; arbitrary-PDF text highlighting blocked until adapter proven.
  • Q2: Sync annotation sidecars as user data? Proposal yes, derived raster caches no; revision conflicts remain visible, no last-write overwrite.
  • Q3: Accept NotesCraft normalized rect/annotation extension and stale-anchor behavior? No claim of Obsidian rect interoperability.

Feature ideas (researched, ranked)

  1. PDF highlight cards on canvas, informed by Heptabase PDF annotation.
  2. Cross-device highlight deep links, informed by Heptabase deep links.
  3. Page-only fallback for other apps, preserving Obsidian PDF embed syntax.