Swipe direction is determined by which edge the finger started on AND
the drag direction matching (right edge + swipe left = next, left edge
+ swipe right = prev). Both no-op at their respective boundaries.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Edge swipe:
- Only detect from RIGHT edge (swipe left = next page)
- Left edge no longer triggers page nav (avoids conflict with system
back gesture)
- Previous page navigation via binder dropdown or system back only
Page list navigation:
- Simplified selectedPageId handling — read and clear synchronously
during recomposition instead of async StateFlow observation
- Fixes issue where clicking a page in view-all-pages went to last
visited page instead of the selected one
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Page list: Library button moved to actions (right side) with titleLarge
- Notebook list: title changed to "Engineering Pad :: Library"
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- X button in top app bar navigates back to notebook list
- Swipe left gesture (>200px) on the page grid closes to notebook list
- Both clear the notebook and view-all-pages state in SharedPreferences
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- CopyStrokesAction: use insertAll return value (List<Long>) to get
inserted IDs directly instead of fragile takeLast(n) re-fetch
- StrokeDao.insertAll now returns List<Long>
- Grid bitmap cached separately from stroke bitmap, redrawn only on
zoom/pan/resize changes. Eliminates per-frame grid line rendering
during active drawing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- MoveStrokesAction.undo(): was offsetting by (0,0), now restores
original pre-move point data correctly
- Stroke commit flicker: new strokes render incrementally onto existing
backing bitmap instead of triggering full redraw
- Selection state leak: view-side selection cleared on page navigation
via onPageNavigated callback
- Initial page fallback: blank canvas prevented if initialPageId
not found in DB — falls back to first page
- Arrow head Paint: preallocated reusable object, no per-call allocation
- Updated PROGRESS.md with audit findings and backup design notes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Drag reorder:
- Separated long-press drag from tap/menu: long-press-and-drag reorders,
tap opens page, overflow menu (three dots) for delete/export
- Fixed combinedClickable conflict that prevented drag from working
Page navigation:
- View-all-pages state persisted in SharedPreferences, restored on startup
- Cleared when navigating back to editor from page list
Delete renumbering:
- After deleting a page, remaining pages are renumbered sequentially
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The reorderable pages list was a plain mutableListOf which Compose
doesn't observe. Changed to mutableStateListOf so the grid recomposes
when pages are loaded.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Drag-to-reorder:
- Added sh.calvin.reorderable library for LazyVerticalGrid drag support
- Long-press and drag page thumbnails to reorder
- Page numbers updated in DB on drop
- Visual feedback: slight scale + transparency while dragging
Page navigation fix:
- Clicking a page in "view all pages" now correctly opens that page
(key(pid) forces EditorScreen recreation with the selected page)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Long-press a page thumbnail to show context menu with Delete and
Export as JPG options
- Delete shows confirmation dialog
- Export renders page at 300 DPI and shares via intent
- FAB always creates new pages (no empty-page restriction in page list)
- Note: drag-to-reorder deferred — requires custom grid drag handling
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Flicker: removed View background and super.onDraw() call — canvas is
filled with white explicitly in onDraw before compositing the backing
bitmap, eliminating the clear-then-redraw flash.
Empty page: swiping right on the last page only creates a new page if
the current page has strokes. Empty pages don't spawn more empties.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Completed strokes are now rendered to a backing bitmap at the view's
pixel resolution. The bitmap is only redrawn when strokes change or
zoom/pan changes. During active drawing, onDraw just composites the
cached bitmap + the in-progress stroke, eliminating flicker from
re-rendering all paths every frame.
Unlike the earlier 1/4-resolution bitmap that caused blur, this one
uses full screen resolution with the view matrix applied, so strokes
remain pixel-perfect.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Eraser: angled rectangle with dividing line (eraser tip)
- Select: dashed rectangle (marquee selection)
- Move: four-way arrow cross with arrowheads
All icons match the existing pen dot, line, and box icon style.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Pen sizes consolidated into single button with dot icon showing
current size; long-press for 0.38/0.5/0.6/0.7mm selection
- Line button shows thick line icon; long-press for style variants
- Box button shows rectangle outline icon
- Undo/redo replaced with curved arrow icons (gray when disabled)
- Added PenSize enum (MM_038, MM_050, MM_060, MM_070)
- Removed PEN_FINE/PEN_MEDIUM tools, replaced with single PEN tool
that uses the selected PenSize from CanvasState
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cut + paste: inserts at same position, selects the pasted strokes.
Copy + paste: offsets pasted strokes by ~10mm diagonally to distinguish
from originals, selects the new copies.
Both operations leave the pasted strokes selected so they can be
immediately moved, copied again, or deleted. After a cut+paste,
subsequent pastes behave as copy+paste (no repeated cuts).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Line styles:
- Store stroke style in DB (style column, migration v2->v3)
- Dashed lines rendered with DashPathEffect on the paint
- Arrow heads drawn dynamically from stored two-point line data
- Line tool stores just two endpoints; style determines rendering
- Removed buildLinePoints — arrow heads no longer baked into point data
Move tool:
- If there's an active selection, Move drags the entire selection
- Otherwise, hit-tests a single stroke and drags it
- Selection stays highlighted after move completes
Line dropdown:
- Removed conflicting onClick from Surface, combinedClickable now
correctly handles both tap (select tool) and long-press (style menu)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. Line long-press dropdown: removed conflicting onClick from Surface,
combinedClickable now works correctly for long-press style menu
2. Move tool: tap a stroke and drag to reposition it, with undo support
and visual highlight during drag
3. Snap fix: timer now re-arms continuously — pausing at ANY point
during a stroke (not just near origin) triggers snap. Checks pen
stillness at the moment the timer fires (~3mm threshold)
4. EMR eraser: now checks BUTTON_STYLUS_PRIMARY in buttonState
(side button) in addition to TOOL_TYPE_ERASER
5. Notebook title: keyboard defaults to Title Case capitalization
6. Tool order in enum matches toolbar: pen, line, box, eraser, select, move
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Toolbar:
- Replace FilterChip with custom ToolButton (no ripple, instant response)
- Reorder tools: 0.38 0.5 Line Box Eraser Select
- DropdownMenus anchored to buttons via Box wrapper (drop below)
- Page indicator styled as OutlinedButton (visible affordance)
- Cut/Del/Copy/Paste operations (internal clipboard)
Line snap fix:
- Check pen position when timer fires instead of canceling on movement
- Handles EMR stylus micro-tremor correctly
EMR eraser button:
- TOOL_TYPE_ERASER events route to eraser handler regardless of active tool
- Physical eraser end of pen works as temporary eraser
App state:
- Startup navigates to last-visited notebook/page via SharedPreferences
- Closing notebook (X button) clears last notebook so app starts at list
- Updated app icon
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Navigation:
- Notebooks remember last page (last_page_id column, migration v1->v2)
- Opening a notebook goes directly to last page's editor
- Edge swipe (finger from screen edge) navigates prev/next page,
auto-adds pages at end, no-op on first page
- X button closes notebook back to list
- Binder dropdown: "View all pages" and "Go to page" dialog
- Page list shows stroke previews and page numbers below cards
Line tool:
- LINE tool: drag start-to-end for straight lines
- Long-press Line chip for style variants: plain, arrow, double arrow, dashed
- Arrow heads rendered as path segments, stored in stroke data
- Dashed lines use DashPathEffect
Export:
- JPG export at full 300 DPI resolution
- Export dropdown: PDF or JPG options
- Refactored PdfExporter with shared drawStrokes helper
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Updated DESIGN.md: rendering strategy (no backing bitmap, screen-space
grid, viewport clamping), added line snap and box tool documentation
- Updated PROGRESS.md: DC-1 testing results and fixes applied
- Updated CLAUDE.md: key conventions for rendering
- Fix toolbar overlap: use clipToBounds on canvas AndroidView to prevent
it from drawing over the toolbar area
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dynamic min zoom computed from page and view dimensions — page can
never be smaller than the visible area. Combined with pan clamping
from previous commit, the page always covers the full canvas with
no non-drawable area visible.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Grid:
- Draw grid in screen space with pixel-snapped positions, fixing
the uneven rectangles caused by sub-pixel positioning in canonical space
- 1px screen-space lines for uniform weight at any zoom level
Line snap:
- Track max distance from origin (not per-move), increase threshold
to 60pt (~5mm) to handle EMR stylus hand tremor
- Snap timer stays active until pen moves significantly from start
Viewport:
- Clamp pan so page always covers the full viewport
- Background changed to white (no dark gray borders)
- Page centers when smaller than viewport (e.g., zoomed out landscape)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rendering fixes for e-ink displays:
- Remove backing bitmap (was 1/4 resolution, caused blurry strokes)
- Draw strokes directly as paths for pixel-perfect rendering
- Disable anti-aliasing on stroke and grid paint (no edge fading)
- Grid: darker color (rgb 180,180,180), 2pt stroke width for crispness
New features:
- Line snap: hold pen still for 1.5s to snap to straight line from origin
- Box tool: draw rectangles by dragging corner to corner
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- High-contrast theme with expanded color palette for e-ink readability
- Updated source trees in DESIGN.md
- Updated PROJECT_PLAN.md and PROGRESS.md — all phases complete
(only on-device performance profiling remains)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- PdfExporter: creates PdfDocument, scales 300 DPI canonical coords to
72 DPI PDF points, renders strokes without grid
- Share via Intent.ACTION_SEND + FileProvider
- PDF button in editor toolbar exports current page
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- UndoableAction interface with AddStrokeAction and DeleteStrokeAction
- UndoManager: undo/redo stacks (depth 50), canUndo/canRedo StateFlows
- EditorViewModel: stroke operations routed through UndoManager,
visual callbacks sync canvas view without full DB reload
- Toolbar: undo/redo buttons with enabled state
- 9 unit tests for UndoManager
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Eraser mode: stylus touch/drag hit-tests against stroke bounding boxes
expanded by 42pt radius (~3.5mm), processes historical points
- Deletes hit strokes from canvas view and Room DB
- Backing bitmap rebuilt automatically on stroke removal
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ScaleGestureDetector for pinch zoom (0.5x-4x) with focal-point anchoring
- Finger drag for pan with multi-pointer tracking
- Zoom/pan state managed locally in PadCanvasView for responsiveness,
synced to EditorViewModel on gesture end
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Makefile wraps Gradle commands and adds emulator/device launch targets:
- run: builds, starts DC-1 emulator if needed, installs and launches
- devrun: builds, installs and launches on connected USB device
- Updated CLAUDE.md and DESIGN.md source trees
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Android project with Kotlin, Jetpack Compose, and Room. Includes:
- Gradle build system with version catalog, foojay JDK resolver, lint config
- Room entities (Notebook, Page, Stroke) with packed float BLOB encoding
- DAOs and repositories for all entities
- Unit tests for blob roundtrip and PageSize enum (10 tests, all passing)
- Minimal Application class and stub MainActivity
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>