Replace the ad-hoc tool palette with a shared tool system
(EditorToolKind) across the PDF, note and slide editors, and add
the core writing tools.
- Multiple brushes, each remembering its OWN color (rnote-style):
selecting a brush restores its color, changing color updates only
that brush, and each brush button shows its current color.
- Select tool: tap-select a committed stroke, drag to move it,
delete it — persisted and undoable.
- Shape tool: line / rectangle / ellipse / arrow, drawn with a live
preview and committed as generated PenStrokes (shape_geometry.dart)
so they reuse stroke rendering, erase, persistence and undo.
- Highlighter + eraser fold into the same tool system.
Text/bookmark/search+OCR/backgrounds/Windows-Ink are later batches
(TODO). Brush opacity still deferred. analyze clean, 302 tests.
Replace the rejected standalone sticky-card board with the real
feature: place a link anchor anywhere on a PDF page, tap it to open
split view whose right pane is THAT anchor's own infinite scratchpad
(keyed by anchor id) — like a paper sticky-note tab.
- ScratchLink model + scratch_links table (id, doc, page, nx, ny).
- PDF editor: "place link" tool drops/loads/shows tappable markers;
tap opens SplitViewScreen for that anchor; long-press deletes.
- SplitViewScreen rebuilt on pdfrx (was syncfusion), right scratchpad
keyed by scratchLinkId, new brush palette (was AnnotationToolbar).
- Remove board_screen + its test + the home board entry.
analyze clean, tests green.
Wire the F7 双链 + 无限便利贴 model (Board/LinkGraph) into a
reachable screen. Previously the model existed but had no UI and
no entry point.
board_screen.dart: an infinite InteractiveViewer canvas of
draggable, editable sticky cards. Card text renders [[links]] as
tappable chips that pan to the target card (dangling links styled
apart). A backlinks panel lists "linked from" via backlinksOf.
"Add card" FAB drops a card at the viewport center.
Persistence: a board_cards table (DB v7), one row per card,
debounced 800ms like the ink editors, loaded on open — boards
survive restart. Entry added to the home screen app bar
(dashboard_customize icon).
Ink-on-cards, multi-board management and link autocomplete are
deferred (TODO board-ink / board-multi / board-link-autocomplete).
analyze clean, 285 tests green.
The pen-first scratchpad opened at identity transform, showing only the
empty top-left corner of the 4000x4000 world — so existing ink (drawn
elsewhere) was off-screen and the pane looked blank ("草稿纸根本没看到").
On first layout, fit the strokes' world bounding box into the pane (padded,
scale clamped 0.15-1.5) so saved ink is immediately visible; an empty
scratchpad falls back to a 1:1 view near the origin.
flutter analyze: 0 issues.
The split-view scratchpad was the last surface on the old ink_canvas. Move
its inking engine to the performant PenCanvas while keeping the infinite
auto-expanding world.
Key idea: store strokes in absolute WORLD pixels (InkStroke — unchanged
saveScratchpad format) and render through PenCanvas by normalizing against
the CURRENT world size. When the world auto-expands, stored world coords do
not move — only the normalization divisor grows — so ink stays put with zero
drift (proven by the world-expand-stability test).
- Replace InteractiveViewer+SizedBox+InkCanvas with PenCanvas (own pan/zoom,
minScale 0.1 to survey the big world); keep the AnnotationToolbar.
- Stroke callbacks go through ink_stroke_adapter; load filters to freehand
so the canvas list stays 1:1 with the undo manager.
- Pen/highlighter/eraser map from the toolbar's PenTool; width is world px.
The left PDF-reference pane (SfPdfViewer, read-only) is unchanged.
Tests: world-expand stability added. flutter analyze: 0. Suite: 270/270.
PPT slides now annotate with the single performant inking engine
(PenCanvas) instead of the old ink_canvas, per "all note features on the
pen-first canvas".
- PenSlideScreen: per-slide normalized strokes over each slide image,
prev/next + slider nav, undo/redo, shared M3 palette, and the pressure
curve / eraser size+mode / palm rejection from the shared canvas.
- slide_export: pure, tested export geometry. Because strokes are now
normalized to the page rect, the PDF exporter maps them straight into
each slide's draw rect — fixing the old exporter's known ink
misalignment (it guessed live-widget size).
- Route PPT import + open -> PenSlideScreen; delete the dead old
ppt_annotator_screen. (ink_canvas/annotation_toolbar remain for
split_view, the last old-canvas screen.)
Tests: slide_export geometry (4). flutter analyze: 0 issues. Suite: 269/269.
Notes now use the single performant inking engine (PenCanvas) instead of
the old ink_canvas, per "all note features on the pen-first canvas".
- ink_stroke_adapter: pure InkStroke<->PenStroke bridge (normalize against
a logical note page; drop non-freehand shapes/text). Round-trip tested.
- pen_palette_widgets: shared M3 ToolButton/PaletteDivider/RoundIconButton
so PDF + note editors use identical chrome (PenEditorScreen migrated to
them; its private copies deleted).
- PenNoteScreen: PenCanvas over a white logical page, undo/redo, title,
save -> Note.strokes (+ local OCR for search). Pressure curve, eraser
size/mode and palm rejection all inherited from the shared canvas.
- Route home (new/open) + search note hits -> PenNoteScreen; remove the
now-redundant "Pen Canvas (beta)" spike button; delete the dead old
note_editor_screen.
Tests: ink_stroke_adapter (5) + pen_note_screen widget (load + commit, 2).
flutter analyze: 0 issues. Full suite: 265/265.
Now that every PDF entry point (home import, home open, search jump)
routes to PenEditorScreen, the old SfPdfViewer-based annotator is
unreachable. Remove it and the two widgets it solely owned:
- screens/pdf_annotator_screen.dart (981 lines)
- widgets/page_thumbnail_sidebar.dart
- widgets/pdf_annotation_layer.dart
annotation_toolbar and ink_canvas stay (still used by the note/ppt/
split-view screens). No references remain to the deleted files.
flutter analyze: 0 issues. Full suite: 258/258.
The search-result document jump still opened the OLD PdfAnnotatorScreen,
the last live entry to it. Route it to PenEditorScreen instead, and add
an initialPage param to the editor so the jump lands on the hit's page
(clamped to the document range once it loads).
With this, PenEditorScreen is the ONLY reachable PDF surface; the old
annotator is now dead code (no remaining references).
flutter analyze: 0 issues. Full suite: 258/258.
Extend the en/zh localization to the two screens reached from the home
app bar (the "全都用 + 多语言" ask):
- settings: title, theme-mode segments (System/Light/Dark), seed-color
description, color-picker + clear-data dialogs.
- search: hint, error, empty/no-results states, Notes/Documents section
headers, page label.
New ARB keys regenerated; l10n_test now asserts the new keys resolve in
both English and Chinese.
flutter analyze: 0 issues. l10n_test: 3/3.
The app had zero localization ("软件多语言做了吗" — no). Add Flutter's
official gen-l10n pipeline and localize the core flow the user sees.
- pubspec: flutter_localizations + intl + generate: true
- l10n.yaml + lib/l10n/app_en.arb + app_zh.arb (37 strings)
- main.dart: localizationsDelegates + supportedLocales (follows OS locale)
- pen editor: all tool tooltips, page pill, error states localized
- home: app bar actions + empty-state buttons localized
Proven end-to-end: l10n_test pumps the same widget under Locale('en')
and Locale('zh') and asserts English vs Chinese strings resolve.
flutter analyze: 0 issues. l10n_test: 3/3 pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The night's pen-first rebuild (PenEditorScreen + PenCanvas + zoom fix +
eraser + M3 tool palette + render cache) was unreachable from the running
app: home_screen opened the OLD PdfAnnotatorScreen, so the user saw zero
change. Wire both PDF-open sites (import + open-existing) to PenEditorScreen,
making the entire editor/* stack LIVE on the real PDF path.
flutter analyze: 0 issues.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Clean-room reimplementation of Saber's input model: we own the gesture
pipeline so pen draws with real pressure (perfect_freehand), two-finger
pinch zooms/pans, and palm is rejected (stylus-priority, 2nd-pointer
cancels stroke). pdfrx renders one page at a time (PdfPageView, no
gestures) under a shared transform; page-based nav. Material You theme
via dynamic_color (system accent + seed fallback) and a floating tonal
tool palette + page pill. Old pdfrx-overlay spike no longer wired.
Add pdfrx 2.4.4. PenCaptureRegion routes stylus to ink (arena-bypass
via PenCaptureBinding) while touch falls through to pdfrx scroll/zoom.
Spike pane hosts PdfViewer with page-overlay ink at normalized coords +
frame-time HUD; reachable from home screen for on-device testing.
Bench/coordinate harness under integration_test. Generated assets are
gitignored (regenerate via tool/gen_*.dart).
Bug fixes (Flutter):
- Wrap multi-statement DB writes (insert/update/delete note, deleteDocument,
deletePageData, OCR FTS merge, migrations) in transactions to prevent data
loss on interruption and a read-modify-write FTS race.
- Fix PdfDocument leaks on exception (try/finally dispose) and preserve image
aspect ratio when stamping images onto PDF pages.
- Guard file-picker against empty selection (was .single -> crash).
- Fix eraser ConcurrentModificationError and unmodifiable-list crash on PDF
pages; capture page synchronously on save to stop wrong-page data loss.
- Fix Riverpod DB-not-ready races, broken pull-to-refresh, settings load race,
and search N+1; transform stored annotations on PDF page rotation.
- Normalize pen pressure for devices without a pressure range.
- PPT: single source of truth for slide strokes so ink displays and exports.
UI/UX:
- Material 3 typography, theme-aware colors (dark-mode fixes), hover cursors
and right-click/visible actions on desktop, keyboard shortcuts (undo/redo/
save/find), toolbar overflow handling, friendlier empty states, semantic OCR
status badges, relative timestamps, 1-based page indicators, large-deck PPT
navigation, and a scratchpad-scope label in split view.
Server (optional backend):
- Persist JWT secret (was per-process random), block path traversal in storage,
fix CORS '*'+credentials, add OCR job ownership checks, last-writer-wins sync
guard, constant-time login, and split out heavy OCR deps so the API/tests run
without them.
CI: Gitea workflows for format+analyze+test (Linux, system sqlite) and a
Windows release build; pristine `flutter analyze`, all Flutter and server tests
green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>