Replace the split snapshot/native renderer paths with a single
composition pipeline (compositionRenderer.ts) that:
- Loads actual source images and uses naturalWidth/Height for
correct full-resolution sampling (fixes top-left-corner-only bug
caused by data.w/h being capped to 600px display dimensions)
- Routes image-only selections through native Canvas 2D composition
- Falls back to viewport snapshot for mixed/unsupported selections
with explicit warnings instead of silent degradation
- Resolves group children via itemResolver for proper group export
- Rejects group children from native composition (local coords
incompatible with world-space drawing)
- Adds canvas size safety limits with auto-downscale
- Guards VideoSprite._drawFrame against null texture source race
condition during zoom-triggered culling
New files:
- compositionRenderer.ts — unified composition module
- compositionRenderer.test.ts — 16 tests for entry flattening,
bounds, dimensions, group handling
Modified:
- clipboard.ts — uses composeSelection() instead of direct renderers
- export.ts — uses composeSelection() + getCompositionDimensions()
- Editor.tsx, useShortcutHandler.ts — pass scene for group resolution
- VideoSprite.ts — null guard on texture source in frame loop
The crop editor (getImageEditorGeometry) was anchoring on a view-space
point that maps differently in cropped vs uncropped local space when
flipped. For asymmetric crops + flip, the full image would appear
shifted from where the visible image was — the user saw the image
"jump" when entering crop mode.
Fix: anchor on the container's local origin instead. In the cropped
sprite, local (0,0) corresponds to source pixel (srcRect.x, srcRect.y).
Position the editor so that same source pixel maps to the same world
point in both cropped and uncropped states. This works correctly for
all flip/rotation/crop combinations.
- Fix crop confirm anchor drift for rotated images by using crop corner
world point instead of AABB min in useCanvasSetup.ts
- Document ungroup shear limitation for non-uniform scale + rotation
- Add 6 integration tests covering crop confirm and ungroup paths
Fix text staying blurry on zoom by ensuring the first setZoomBucket call
always applies (PixiJS v8 defaults to auto-resolution which skipped the
initial bucket=1 assignment), listening on both 'moved' and 'zoomed'
viewport events, and tightening bucket thresholds from 6 to 9 levels.
- Move markdown editor from canvas overlay to side panel (70% width)
for better performance and editing experience
- Add @blocknote/mantine for full BlockNoteView with default UI components
- Fix socket reconnect loop caused by unstable pasteOpts object reference
(memoize with useMemo)
- Add ResizeObserver to markdown overlay cards for automatic height sync
- Call mdOverlay.refreshAll() on every canvas change so overlays track
pack/grid/arrange/save operations
- Paste goes to BlockNote editor when contentEditable is focused
- Simplify toolbar: single color picker (accent + auto-derived bg),
title/name field, width presets S/M/L
- Guard normalize/flip operations to skip markdown and sticky items
- Fix title not updating on card preview (pass name prop, bump revision)
- Fix preview not refreshing after editor save (revision counter + overlay refresh)
Simplify Ctrl+V shortcut to only handle recent internal copies, letting
native paste events flow to setupPaste for external clipboard content.
Extend setupPaste with text/HTML detection and popup callbacks. Wire
PasteChoicePopup in Editor.tsx with markdown (turndown HTML-to-MD),
plain text, and image paste choices.