Palm tests, erase fix, swipe-to-add, size dialog, grid settings, docs #12

Merged
reudy merged 6 commits from canvas-polish into main 2026-09-27 23:34:25 +02:00
2 changed files with 24 additions and 11 deletions
Showing only changes of commit 2df91b561d - Show all commits

View file

@ -23,7 +23,8 @@ The image builds the PWA and serves it with nginx. Put an HTTPS reverse proxy in
## Using it ## Using it
- **Tools** (bottom bar): Text `T`, Pen `P`, Highlighter `H`, Eraser `E`, Import PDF, Undo/Redo, zoom. The pen's eraser end erases too. - **Tools** (bottom bar): Text `T`, Pen `P`, Highlighter `H`, Eraser `E`, Import PDF, Undo/Redo, zoom. The pen's eraser end erases too.
- **Pages**: the grey "+" tiles next to the active page add a page there; `Alt+Arrow` moves to (or creates) the neighbour. Right-click a page or use its `⋯` button to insert, resize or delete it. - **Pages**: the "+" circles beside the active page add a page there (a quick finger fling past a free edge highlights that side's "+"); `Alt+Arrow` moves to (or creates) the neighbour. Right-click a page or use its `⋯` button to insert, resize or delete it.
- **Grid**: the faint background grid has one cell per active page; Settings → Appearance can hide it or subdivide it.
- **Importing a PDF** into a non-empty canvas shows every free spot; click one to place the chain. Into a blank canvas it simply replaces the empty page. The Files panel can also import a PDF as a new canvas. - **Importing a PDF** into a non-empty canvas shows every free spot; click one to place the chain. Into a blank canvas it simply replaces the empty page. The Files panel can also import a PDF as a new canvas.
- **View**: wheel/trackpad pans, `Ctrl`+wheel or pinch zooms, `Ctrl+0` fits the page, `Ctrl+9` shows the whole tree, space-drag or middle-drag pans. - **View**: wheel/trackpad pans, `Ctrl`+wheel or pinch zooms, `Ctrl+0` fits the page, `Ctrl+9` shows the whole tree, space-drag or middle-drag pans.
- **Sync**: Settings → Sync. Use a fine-grained token with *Contents: read & write* on the repo. Edits autosave locally; pushes happen every N minutes and on `Ctrl+S` / the sync button. Conflicts: newer wins. - **Sync**: Settings → Sync. Use a fine-grained token with *Contents: read & write* on the repo. Edits autosave locally; pushes happen every N minutes and on `Ctrl+S` / the sync button. Conflicts: newer wins.

32
spec.md
View file

@ -9,10 +9,11 @@ One canvas = one tree = one `.pdf` file.
- A branch can only ever continue in the direction it started (strictly linear — no sub-branches off a branch). - 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. - 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. - **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. - 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. - Deleting a page removes it (undo via Ctrl+Z); no permanent trash.
- Default zoom: one page fills the screen. Zoom out reveals the tree. - 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. - 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) ## 2. Flatten Algorithm (tree → linear page order, e.g. for PDF page order / print)
@ -45,7 +46,7 @@ Canvases (PDF files) live in a **file tree** you define, e.g. `school/english/le
## 5. Sync & Storage ## 5. Sync & Storage
- Repo-backed (GitHub/GitLab), one file per canvas. - Repo-backed (GitHub for now; GitLab not implemented), one file per canvas.
- PWA: offline-first, caches locally, explicit save/sync pushes to repo. - PWA: offline-first, caches locally, explicit save/sync pushes to repo.
- Multi-device: git pull/push is the sync mechanism (not real-time collab). - Multi-device: git pull/push is the sync mechanism (not real-time collab).
@ -54,7 +55,7 @@ Canvases (PDF files) live in a **file tree** you define, e.g. `school/english/le
- **Frontend**: SvelteKit, deployed on your VPS, installable as PWA. - **Frontend**: SvelteKit, deployed on your VPS, installable as PWA.
- **Ink input**: Pointer Events + pressure (your existing artifact — pending). - **Ink input**: Pointer Events + pressure (your existing artifact — pending).
- **PDF read/write**: pdf.js + pdf-lib. - **PDF read/write**: pdf.js + pdf-lib.
- **Storage**: Git repo via GitHub/GitLab API (or local backend proxy holding the token). - **Storage**: IndexedDB locally; Git repo via the GitHub REST API (token in the browser, no proxy).
## 7. Confirmed Product Features ## 7. Confirmed Product Features
@ -79,12 +80,23 @@ Source: user-provided `palm-rejection-test.html` test harness. The acceptance po
- 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` 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. - 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): **How the app applies it** (`src/lib/ink/palm.ts`):
- `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. - 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.
- `tiltX`/`tiltY` exist on the pen's `PointerEvent` but aren't read anywhere yet — needed for tilt support. - Once a pen has been used, fingers only navigate. "Draw with finger" in Settings turns finger ink off entirely.
- No smoothing yet (raw straight segments) — fine as a v1, can layer in curve smoothing later without changing point storage. - Each rule (pen session, wide contact, timing, hover) can be switched off in Settings → Pen & touch.
- 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. - 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 ## 10. Open Items
- [ ] Nothing blocking — ready to move into implementation planning (component breakdown, page-tree data structures in Svelte, PDF encode/decode module) - [ ] 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.