Papure/spec.md
agent 2df91b561d Bring spec and README up to date
The spec still listed undo, the eraser, colours and smoothing as
missing. Describe the ink engine and palm rejection as built (draw vs
navigate intents, settings toggles, cancelled pointers), the plus
buttons, fling offer, grid and page size dialog, note that sync is
GitHub-only, and replace the stale open item with the real ones.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GaoZtJQZwLXkEgWs8twCia
2026-09-27 21:31:54 +00:00

102 lines
6.8 KiB
Markdown

# 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.
- Each free side of the active page shows a small "+" circle just off its edge; tapping it (or `Alt+Arrow`) adds a blank page there. A quick finger fling past a free edge highlights that side's "+" for a few seconds, but never adds a page by itself.
- The canvas background is a faint grid with one cell per active page, aligned to its edges; Settings can hide it or subdivide it.
- 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 (page menu: presets, or a custom size in mm).
## 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 for now; GitLab not implemented), 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**: IndexedDB locally; Git repo via the GitHub REST API (token in the browser, no proxy).
## 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.
**How the app applies it** (`src/lib/ink/palm.ts`):
- Each touch is evaluated with an intent. `draw` (finger ink) gets the full policy above. `navigate` (pan, pinch, and taps on canvas buttons such as the add-page "+") skips the 5 s pen session and the hover lockout, so fingers can move around and tap between pen strokes; pen-down, wide contacts and the 150 ms window still reject it.
- Once a pen has been used, fingers only navigate. "Draw with finger" in Settings turns finger ink off entirely.
- Each rule (pen session, wide contact, timing, hover) can be switched off in Settings → Pen & touch.
- An interrupted pointer (`pointercancel`) drops the stroke or erase in progress instead of committing it.
**Ink engine (implemented)**:
- `StrokeRecorder.recordPoint()` stores `x, y, pressure, tiltX, tiltY, t` per sample in a flat `Stroke.points` array (stride 6); rendering replays it. Tilt is recorded but not yet used for rendering.
- Outlines are smoothed with `perfect-freehand`; mouse and finger strokes simulate pressure.
- Pen, highlighter (translucent, no thinning) and stroke eraser, with configurable colour palettes; the pen's eraser end erases too.
- Undo/redo covers ink, erasing and page-tree changes.
- Strokes are written to the PDF both as Ink annotations and as JSON attachments.
## 10. Open Items
- [ ] Conflict copies: sync currently lets the newer version win silently; keep the losing version as a separate file.
- [ ] Tell the user when a new version of the app has been deployed (the service worker updates, but the open tab keeps old code until reload).
- [ ] CI that runs `check`, `test` and `build` on every pull request.
- [ ] Use recorded tilt for rendering.
- [ ] GitLab sync.