Papure/spec.md
2026-09-26 19:13:02 +02:00

5.8 KiB

Lecture Notes App — Architecture Doc v1

1. Page Tree Model ("Comb")

One canvas = one tree = one .pdf file.

  • Trunk: a single vertical chain of pages. Grows up or down (both allowed).
  • Branch: a horizontal chain of pages attached to exactly one trunk page, going left or right.
    • A branch can only ever continue in the direction it started (strictly linear — no sub-branches off a branch).
    • A trunk page can have at most one left branch and one right branch.
  • PDF import: creates a chain of pages in whatever direction you pick when placing it (up/down = extends the trunk, left/right = becomes a branch off the trunk page you clicked). Only one trunk exists per canvas — additional imported PDFs must attach as a branch off some trunk page, or extend an existing branch further outward in its established direction.
  • Hidden/unopened neighbor pages render as a gray tile with a "+" in a circle; clicking (or a shortcut) instantiates them as real pages.
  • Deleting a page removes it (undo via Ctrl+Z); no permanent trash.
  • Default zoom: one page fills the screen. Zoom out reveals the tree.
  • Pages default to A4, or auto-match the aspect ratio of an imported PDF page; resizable manually otherwise.

2. Flatten Algorithm (tree → linear page order, e.g. for PDF page order / print)

Starting at the topmost trunk node, walk down:

for each trunk_node, top → bottom:
    emit trunk_node
    emit left_branch pages, closest-to-trunk → outward
    emit right_branch pages, closest-to-trunk → outward

This is recomputed at export/save time (not creation order), since the trunk can grow upward later and shift what "topmost" means.

3. File Format

One canvas = one .pdf file, dual-layer:

  • Visual layer (any standard PDF viewer sees this): rendered markdown as real vector text, ink drawn as PDF Ink annotations, original imported PDF content where applicable. Fully readable/printable by anyone, no app required.
  • Source-of-truth layer (embedded file attachments, invisible in normal viewers): raw markdown source per page, full vector stroke data (points + pressure + tilt — richer than what Ink annotations can hold), and grid metadata (each page's tree position, size, and which PDF import it originated from, for provenance search).
  • App reads the attachment layer first to reconstruct the live editable canvas; falls back to parsing visual content only if attachments are missing.

Libraries: pdf-lib (write/edit/attach), pdf.js (render/import).

Trade-off accepted: binary PDFs mean no line-level git diffs. Flag for later if this becomes annoying — we could add a parallel auto-generated .md export per canvas purely for git-diff readability, PDF stays canonical.

4. Organization

Canvases (PDF files) live in a file tree you define, e.g. school/english/lecture1.pdf, school/math/lecture2.pdf, work/.... Folders are purely organizational, no canvas of their own.

5. Sync & Storage

  • Repo-backed (GitHub/GitLab), one file per canvas.
  • PWA: offline-first, caches locally, explicit save/sync pushes to repo.
  • Multi-device: git pull/push is the sync mechanism (not real-time collab).

6. Tech Stack (tentative)

  • Frontend: SvelteKit, deployed on your VPS, installable as PWA.
  • Ink input: Pointer Events + pressure (your existing artifact — pending).
  • PDF read/write: pdf.js + pdf-lib.
  • Storage: Git repo via GitHub/GitLab API (or local backend proxy holding the token).

7. Confirmed Product Features

  • Live-rendered markdown (background layer) + vector ink (foreground layer), never converted to text.
  • Full ink toolset: colors, highlighter, eraser.
  • Search across typed markdown text.
  • Pages remember their PDF-import origin, searchable.
  • No export needed beyond the PDF itself (for now).

8. Sync, Auth & Conflicts (resolved)

  • Git auth: personal access token stored in the browser/PWA (no backend proxy, for now).
  • Save model: autocommit + periodic push, plus a manual "sync now" button that does pull + push on demand.
  • Conflicts: newer version wins (timestamp-based), no merge UI for now.
  • Repo size: not a concern for now — revisit if it becomes a problem.

9. Pen Input & Palm Rejection

Source: user-provided palm-rejection-test.html test harness. The acceptance policy carries over almost verbatim:

  • pointerType === 'pen' is always accepted; its down/move/up updates lastPenActivityTime.
  • A touch pointer is rejected if any of: a pen stroke is active or was active within the last ~5s, its contact ellipse (width/height) exceeds ~35px (palm heel vs. fingertip), it's within 150ms of the last pen activity, or a pen is currently hovering (armed lockout before it even touches down).
  • A touch stroke already in progress gets cut off mid-stroke if pen activity appears.

Gaps before this is the real ink engine (currently a raster test, not vector):

  • draw() writes straight to canvas via ctx.lineTo/stroke with pressure-scaled line width, and throws points away. Needs to become recordPoint(): push {x, y, pressure, tiltX, tiltY, t} into a Stroke.points[] array per stroke; rendering = replaying that array. This is what makes strokes resizable/erasable/undoable and exportable to both the PDF Ink-annotation layer and the JSON attachment layer.
  • tiltX/tiltY exist on the pen's PointerEvent but aren't read anywhere yet — needed for tilt support.
  • No smoothing yet (raw straight segments) — fine as a v1, can layer in curve smoothing later without changing point storage.
  • No undo/redo, multi-color, eraser yet — needs a Stroke[] array per page's ink layer with add/remove, which undo/redo just pops/pushes.

10. Open Items

  • Nothing blocking — ready to move into implementation planning (component breakdown, page-tree data structures in Svelte, PDF encode/decode module)