Extract opacityFromBuffer as a pure function in core/editor and test
it directly. Covers Figma semantics: 0→100%, 5→50%, 28→28%, 00→0%,
100→100%, clamp at 100%, NaN guard. Refactor the keyboard handler to
reuse the shared function instead of inlining the buffer math.
- Move setOpacity to core editor action (packages/core/src/editor/nodes.ts)
so it is testable directly without duplicating logic in tests
- Replace external window listener with tinykeys bindings in
registerKeyboardShortcuts, reusing shared input/focus guards
- Add 20 bindings for Digit0-9 and Numpad0-9 with modifier guards
- Implement Figma-style multi-digit buffer: 2→20%, 28→28%, 00→0%, 100→100%
with 400ms timeout and auto-apply at 3 digits
- Simplify selection.setOpacity command to delegate to editor.setOpacity
- Rewrite unit tests to call editor.setOpacity directly
- Add ofetch to dependencies (was imported but missing from package.json)
- Update CHANGELOG
Add opacity keyboard shortcuts matching Figma: pressing Digit1-Digit9
sets the selected layers to 10%-90% opacity, and Digit0 sets 100%.
- Add 'selection.setOpacity' EditorCommandId with registry metadata
(shortcut display '1-9, 0') so it appears in the command cheatsheet
- Implement the command in selection.ts using editor.undo.runBatch +
updateNodeWithUndo, following the toggleVisibility/setBlendMode pattern
- Add setOpacityTarget closure in useEditorCommands so the external
keyboard handler can inject the digit value before runCommand
- Add canSetOpacity capability (enabled when there is a selection)
- Add bindOpacityKeys handler in src/app/shell/keyboard/opacity.ts,
following the nudging.ts external-handler pattern since the opacity
value comes from event.code and cannot be expressed as a fixed
keybinding in the registry
- Guard against text editing, number field focus, and modifier keys
- Add i18n label 'setOpacity' across all 8 locales
- Add unit tests for single/multi selection, undo batch, and no-op
- Normalize auto-layout text imported from Figma when fixed bounds match derived text layout
- Recompute auto-size text bounds on typography metric changes
- Clear stale Figma derived text layout caches when text sizing changes
- Cover FIG import text sizing and line-height resize regressions
* fix(resize): scale children when resizing GROUP or BOOLEAN_OPERATION
Resizing a group (or boolean operation) only moved the bounding box and
rescaled the selection outline — the rendered fill stayed at its original
size, so the blue outline and the filled content visibly diverged.
Two root causes, both in the resize preview path:
1. During drag, children were scaled in the preview graph via
updateNodePreview(), which mutates nodes without emitting a node:updated
event. The renderer's vector/geometry caches (vectorPathCache,
fillGeometryCache, strokeGeometryCache) were therefore never invalidated,
so fills kept drawing from stale, unscaled cached paths while outlines
drew live from the new dimensions. Fixed by calling
renderer.invalidateVectorPath(childId) after each child preview update.
2. On commit, the final scaled values were written with updateNode() but no
repaint was requested, leaving the canvas frozen on the last mismatched
drag frame until the next interaction. Fixed by calling requestRepaint()
after commitGroupResize().
collectDescendants() now also collects children for BOOLEAN_OPERATION nodes,
which render purely from child geometry (no node-level scale), so scaling the
children is the only way to scale the result — same mechanism as GROUP, no
double-transform. Group resize verified visually; boolean resize verified in
-browser via the editor automation API.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(editor): preserve vector undo during group resize
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Danila Poyarkov <dev@dannote.net>
- Update EffectType schema assertion for modern Figma enum values
- Treat unsupported OpenType font buffers as unavailable
- Skip CJK outline fallback coverage when the LFS font fixture is not materialized
- Move codec, NodeChange conversion, and instance overrides under kiwi/fig
- Flatten the codec entrypoint to kiwi/fig/codec
- Keep schema-runtime as the generic Kiwi parser/compiler boundary
Paste now targets the selected frame/group/component/section instead of
always placing nodes on the page root, matching Figma's behavior:
- Single container selected: paste as child
- Non-container child selected: paste as sibling in its parent
- Entered container (double-click): paste inside it
- Nothing or multiple nodes selected: paste on the page
Also applies to image paste via placeImageFiles.
Fixes#79
- Move newly split tests under explicit domain subfolders
- Update nested helper imports after the moves
- Add a lint guard against repeating existing sibling domains as filename prefixes
- Extract color picker fill comparison helpers
- Guard derived text assertions without optional chains
- Split resize math into smaller helper functions
- Remove the remaining complexity test exceptions
- Remove remaining test non-null assertions and delete the broad test override
- Add explicit guards for canvas, design panel, auto-layout, SVG, Kiwi, hit-test, scene graph, text editor, and clipboard tests
- Validate check plus affected engine and E2E coverage