✨ feat(renderer): ABSOLUTE 子は親 clip 外で描画し、drag 中の bypass frame は clip 一時無効化#5
Conversation
… 一時無効化 Cmd+ ドラッグで auto-layout 親の外に出した子が clipsContent=true の clip mask で見えなくなる問題を解決する。 Figma 同様に layoutPositioning='ABSOLUTE' の子は親 frame の clipsContent でも clip 外で描画されるよう scene.ts の renderChildren を変更。 主な変更: - editor.state に draggingClipBypassFrameId を追加し handleMoveMove で drag 中の bypass 親 frame ID を保持、handleMoveUp でクリア - renderer の RenderOverlays / pipeline で draggingClipBypassFrameId を scene.ts に伝播 - scene.ts renderChildren で getChildClipRuns helper を導入し、子の layoutPositioning ごとに clip run を切り替えて z-order を維持しつつ ABSOLUTE 子だけ clip 外描画 - drag bypass 中の frame は clip 自体を skip して preview が親 bbox 外でも見えるように - tests/engine/render/canvas/clips-content.test.ts (新規) と cmd-bypass-auto-layout.test.ts (追加) で AC1-5 を固定 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e 厳格化 Round 1 で発見された 3 件の指摘を修正: - F-A001 (HIGH): renderChildren の AUTO/ABSOLUTE run 分割で mask とその target が別 run に分かれ mask が機能しなくなる regression を修正。getRenderableChildRuns で mask 検出時は続く target まで同一 run にまとめ、内部に非 ABSOLUTE が 1 つでもあれば clip 維持 - F-A002 (MEDIUM): draggingClipBypassFrameId の state leak を防ぐため clearDraggingClipBypassFrame helper を追加し、window blur / document visibilitychange / editor tool:changed / onScopeDispose の 4 経路でクリーンアップ - F-A003 (MEDIUM): unclip rule を parent.layoutMode !== 'NONE' で auto-layout 親に限定。layoutMode='NONE' の通常 frame では ABSOLUTE 子も従来通り clip 内に留まる getVisibleMaskType helper を抽出して mask 判定を SSOT 化し、scene.ts 内の重複を排除。 regression test 4 件追加 (clips-content 3 + cmd-bypass 1)、24 pass / 0 fail。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Round 2 で発見した R2-001 (MEDIUM) を修正。 従来の cleanup paths (blur / visibilitychange / tool:changed / onScopeDispose) は draggingClipBypassFrameId を瞬間的にクリアするだけで、drag.value はそのまま残るため、次の mousemove で handleMoveMove が syncDraggingClipBypassFrame を再実行して bypass が復活していた。 cancelMoveDragInterruption helper を追加し、interruption 時は: - preview を originals 位置に戻す (commit せず破棄) - drag.value = null で drag 自体を cancel - layoutInsertIndicator / snapGuides / dropTarget も同時クリア - 最後に draggingClipBypassFrameId をクリア (idempotent) これで drag が cancel された後の mousemove は `if (!drag.value) return` で no-op、bypass が再 arm されない。 regression test 追加で interruption 後の drag null + preview 復元 + bypass 非復活を固定。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughドラッグ中の絶対配置 auto-layout 子と Cmd/Ctrl プレビューが親フレームのクリップで誤って隠れないようにする機能を実装。エディタ状態にバイパスフレームIDを追加し、シーン描画ロジックを子ノード単位の選別クリップへ改造し、入力ハンドラで状態同期と後片付けを追加。 ChangesAuto-layout クリップバイパス
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/vue/src/canvas/useCanvasInput.ts (1)
28-40:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winJSDoc が誤った関数に付与されています。
28-34 行の説明は
useCanvasInputを記述したものですが、新規のclearDraggingClipBypassFrameの直上に移動してしまっています。useCanvasInput(Line 71)の上に戻すか、各新規エクスポート関数に専用の説明を付けてください。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/vue/src/canvas/useCanvasInput.ts` around lines 28 - 40, ファイル内の JSDoc ブロックは useCanvasInput の説明が意図せず clearDraggingClipBypassFrame の上に移動しているため、JSDoc を正しい場所に戻すか各エクスポートに適切な説明を付けてください;具体的には現在の JSDoc を useCanvasInput(関数名 useCanvasInput を参照)直上へ移動するか、clearDraggingClipBypassFrame(関数名 clearDraggingClipBypassFrame と引数 editor を参照)に別個の短い説明コメントを追加して、各関数の責務が正しく文書化されるようにしてください。
🧹 Nitpick comments (2)
tests/engine/vue/input/cmd-bypass-auto-layout.test.ts (1)
119-119: ⚡ Quick win重複するテストラベル。
"AC5"(Line 119 と 147)および"R2-001"(Line 206 と 230)が重複しています。テスト失敗時にどのケースか判別しづらいため、一意なラベルに整理することを検討してください。Also applies to: 147-147, 206-206, 230-230
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/engine/vue/input/cmd-bypass-auto-layout.test.ts` at line 119, The test labels "AC5" and "R2-001" are duplicated which makes failures ambiguous; locate the test declarations like test('AC5: mouseup always clears draggingClipBypassFrameId', ...) and the other test(s) using 'AC5' and similarly the test(s) using 'R2-001', and rename each duplicated label to unique, descriptive identifiers (e.g., 'AC5-1' / 'AC5-2' or include the scenario detail) so every test title is unique; update all duplicate occurrences consistently to avoid collisions in test output.packages/vue/src/canvas/useCanvasInput.ts (1)
339-343: ⚡ Quick win
useEditorEventコンポーザブルの利用を検討。
editor.onEditorEvent('tool:changed', ...)を手動購読しonScopeDisposeで解除していますが、Vue SDK には scope cleanup で自動解除されるuseEditorEvent(event, handler)が用意されています。これを使うと購読解除の取りこぼしリスクが減ります(onScopeDispose内のclearClipBypassFrame()呼び出しは別途残してください)。Based on learnings: "Vue SDK provides
useEditorEvent(event, handler)composable that auto-disposes on scope cleanup".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/vue/src/canvas/useCanvasInput.ts` around lines 339 - 343, editor.onEditorEvent('tool:changed', ...) を手動で購読して stopToolChangeCleanup を onScopeDispose で解除している箇所は、Vue SDK の自動解除される useEditorEvent を使って置き換えてください:editor.onEditorEvent の呼び出しを削除し代わりに useEditorEvent('tool:changed', clearClipBypassFrame) を登録し、onScopeDispose 内の clearClipBypassFrame() の呼び出しはそのまま残す(stopToolChangeCleanup 変数とその呼び出しを削除)。これで購読解除の取りこぼしを防げます。
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tests/engine/render/canvas/clips-content.test.ts`:
- Around line 118-251: Add Playwright canvas snapshot tests under tests/e2e that
render the same scenarios exercised in clips-content.test.ts: (1) auto-layout
frame with both auto and ABSOLUTE children (auto-layout + ABSOLUTE), (2) drag
clip bypass limited to the targeted frame using draggingClipBypassFrameId, (3)
mask with absolute target kept in same clipped run and an all-absolute mask
group outside the clip, and (4) layoutMode: 'NONE' frame showing absolute
children still clipped; reuse the scene construction helpers
(createClippingFrame, renderChildRecords/getRenderableChildRuns or equivalent
scene setup) and capture PNG snapshots named to reflect
clip|absolute|auto-layout|bypass so the e2e snapshot suite verifies the actual
canvas pixel output for those cases.
---
Outside diff comments:
In `@packages/vue/src/canvas/useCanvasInput.ts`:
- Around line 28-40: ファイル内の JSDoc ブロックは useCanvasInput の説明が意図せず
clearDraggingClipBypassFrame の上に移動しているため、JSDoc
を正しい場所に戻すか各エクスポートに適切な説明を付けてください;具体的には現在の JSDoc を useCanvasInput(関数名
useCanvasInput を参照)直上へ移動するか、clearDraggingClipBypassFrame(関数名
clearDraggingClipBypassFrame と引数 editor
を参照)に別個の短い説明コメントを追加して、各関数の責務が正しく文書化されるようにしてください。
---
Nitpick comments:
In `@packages/vue/src/canvas/useCanvasInput.ts`:
- Around line 339-343: editor.onEditorEvent('tool:changed', ...) を手動で購読して
stopToolChangeCleanup を onScopeDispose で解除している箇所は、Vue SDK の自動解除される
useEditorEvent を使って置き換えてください:editor.onEditorEvent の呼び出しを削除し代わりに
useEditorEvent('tool:changed', clearClipBypassFrame) を登録し、onScopeDispose 内の
clearClipBypassFrame() の呼び出しはそのまま残す(stopToolChangeCleanup
変数とその呼び出しを削除)。これで購読解除の取りこぼしを防げます。
In `@tests/engine/vue/input/cmd-bypass-auto-layout.test.ts`:
- Line 119: The test labels "AC5" and "R2-001" are duplicated which makes
failures ambiguous; locate the test declarations like test('AC5: mouseup always
clears draggingClipBypassFrameId', ...) and the other test(s) using 'AC5' and
similarly the test(s) using 'R2-001', and rename each duplicated label to
unique, descriptive identifiers (e.g., 'AC5-1' / 'AC5-2' or include the scenario
detail) so every test title is unique; update all duplicate occurrences
consistently to avoid collisions in test output.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 1c9728bc-9bd7-4248-bf55-d185c06c8afa
📒 Files selected for processing (11)
CHANGELOG.mdpackages/core/src/canvas/renderer/pipeline.tspackages/core/src/canvas/renderer/types.tspackages/core/src/canvas/scene.tspackages/core/src/editor/selection/overlays.tspackages/core/src/editor/state.tspackages/core/src/editor/types.tspackages/vue/src/canvas/useCanvasInput.tspackages/vue/src/shared/input/move.tstests/engine/render/canvas/clips-content.test.tstests/engine/vue/input/cmd-bypass-auto-layout.test.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (17)
**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
Keep TypeScript files under ~600 lines and split by domain when they grow (reference:
packages/core/src/tools/pattern)
Files:
packages/core/src/editor/types.tspackages/core/src/canvas/renderer/types.tspackages/core/src/editor/state.tspackages/core/src/editor/selection/overlays.tspackages/core/src/canvas/renderer/pipeline.tspackages/vue/src/shared/input/move.tstests/engine/vue/input/cmd-bypass-auto-layout.test.tspackages/vue/src/canvas/useCanvasInput.tspackages/core/src/canvas/scene.tstests/engine/render/canvas/clips-content.test.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Do not useanytype in TypeScript; use proper types and generics instead
Do not use!non-null assertions in TypeScript; use guards,?., or??instead
No inline type definitions when a named type exists; useColor,Vector,SceneNode,Effect,Fill,Strokeinstead of redefining inline
Useculorifor color conversions; do not reimplement parseColor/colorToRgba
UsestructuredClonefor deep copies; never use shallow spread when mutating nested objects
Do not hand-roll what a dependency already does; check existing deps inpackage.jsonandpackages/*/package.jsonfirst
Files:
packages/core/src/editor/types.tspackages/core/src/canvas/renderer/types.tspackages/core/src/editor/state.tspackages/core/src/editor/selection/overlays.tspackages/core/src/canvas/renderer/pipeline.tspackages/vue/src/shared/input/move.tstests/engine/vue/input/cmd-bypass-auto-layout.test.tspackages/vue/src/canvas/useCanvasInput.tspackages/core/src/canvas/scene.tstests/engine/render/canvas/clips-content.test.ts
**/*.{ts,tsx,js,jsx,vue}
📄 CodeRabbit inference engine (AGENTS.md)
Do not use
Math.random()anywhere in codebase; usecrypto.getRandomValues()instead
Files:
packages/core/src/editor/types.tspackages/core/src/canvas/renderer/types.tspackages/core/src/editor/state.tspackages/core/src/editor/selection/overlays.tspackages/core/src/canvas/renderer/pipeline.tspackages/vue/src/shared/input/move.tstests/engine/vue/input/cmd-bypass-auto-layout.test.tspackages/vue/src/canvas/useCanvasInput.tspackages/core/src/canvas/scene.tstests/engine/render/canvas/clips-content.test.ts
packages/**/*.{ts,tsx,vue}
📄 CodeRabbit inference engine (AGENTS.md)
Use package-local aliases inside workspace packages:
#vue/*inpackages/vue,#cli/*inpackages/cli,#mcp/*inpackages/mcp,#core/*when core needs alias; prefer relative imports within nearby core modules
Files:
packages/core/src/editor/types.tspackages/core/src/canvas/renderer/types.tspackages/core/src/editor/state.tspackages/core/src/editor/selection/overlays.tspackages/core/src/canvas/renderer/pipeline.tspackages/vue/src/shared/input/move.tspackages/vue/src/canvas/useCanvasInput.tspackages/core/src/canvas/scene.ts
packages/core/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
packages/core/**/*.ts:es-toolkitis available in core for small utility helpers; use subpath imports likees-toolkit/object,es-toolkit/array,es-toolkit/predicate; avoides-toolkit/compat
Core code must guard browser APIs: usetypeof window !== 'undefined',typeof document === 'undefined'for headless compatibility
Files:
packages/core/src/editor/types.tspackages/core/src/canvas/renderer/types.tspackages/core/src/editor/state.tspackages/core/src/editor/selection/overlays.tspackages/core/src/canvas/renderer/pipeline.tspackages/core/src/canvas/scene.ts
**/*.{ts,tsx,vue}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,vue}: Mac keyboards: usee.codenote.keyfor shortcuts with modifiers (Option transforms characters)
showOpenFilePicker/showSaveFilePicker are File System Access API (Chrome/Edge), not Tauri-only — code has fallbacks
Tauri detection:IS_TAURIconstant frompackages/core/src/constants.ts— do not use'__TAURI_INTERNALS__' in windowinline
.fig export: compression with fflate (browser) or Tauri Rust commands
Files:
packages/core/src/editor/types.tspackages/core/src/canvas/renderer/types.tspackages/core/src/editor/state.tspackages/core/src/editor/selection/overlays.tspackages/core/src/canvas/renderer/pipeline.tspackages/vue/src/shared/input/move.tstests/engine/vue/input/cmd-bypass-auto-layout.test.tspackages/vue/src/canvas/useCanvasInput.tspackages/core/src/canvas/scene.tstests/engine/render/canvas/clips-content.test.ts
packages/core/src/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
packages/core/src/**/*.ts: Shared types (GUID, Color, Vector, Matrix, Rect) live inpackages/core/src/types.ts
Yoga WASM handles flexbox; CSS Grid blocked on upstream (react/yoga#1893)
Runtimecanvaskit-wasmimport exists only incanvaskit.ts— all other files useimport type; CanvasKit instance passed as parameter everywhere
Files:
packages/core/src/editor/types.tspackages/core/src/canvas/renderer/types.tspackages/core/src/editor/state.tspackages/core/src/editor/selection/overlays.tspackages/core/src/canvas/renderer/pipeline.tspackages/core/src/canvas/scene.ts
packages/core/src/editor/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
packages/core/src/editor/**/*.ts: When creating auto-layout, sort children by geometric position first
Dragging a child outside a frame should reparent it, not clip it
Groups: creating a group must preserve children's visual positions
Editing a component must callsyncIfInsideComponent()to propagate to instances
computeAllLayouts()must be called after demo creation and after opening .fig files
Auto-layout creation (Shift+A) must recompute layout immediately to update selection bounds
renderVersionbumped byrequestRender();renderVersionvssceneVersion: renderVersion = canvas repaint (pan/zoom/hover), sceneVersion = scene graph mutations
requestRepaint()bumps onlyrenderVersion;renderNow()only for surface recreation and font loading (need immediate draw)
All selection mutations in core usectx.setSelectedIds()and all tool changes usectx.setActiveTool()so event bus fires consistently
Files:
packages/core/src/editor/types.tspackages/core/src/editor/state.tspackages/core/src/editor/selection/overlays.ts
{src,packages}/**/*.{ts,tsx,vue}
📄 CodeRabbit inference engine (AGENTS.md)
{src,packages}/**/*.{ts,tsx,vue}: Code outside core editor internals must not assigneditor.state.selectedIdsoreditor.state.activeTooldirectly; usectx.setSelectedIds()andctx.setActiveTool()oreditor.clearSelection(),editor.select(),editor.setTool()
Committed code must not import scratch/generated/vendor internals
Files:
packages/core/src/editor/types.tspackages/core/src/canvas/renderer/types.tspackages/core/src/editor/state.tspackages/core/src/editor/selection/overlays.tspackages/core/src/canvas/renderer/pipeline.tspackages/vue/src/shared/input/move.tspackages/vue/src/canvas/useCanvasInput.tspackages/core/src/canvas/scene.ts
{packages,src}/**/[a-z]*/
📄 CodeRabbit inference engine (AGENTS.md)
Non-component domain folders use lowercase or kebab-case:
scene-graph/,figma-api/,node-edit/
Files:
packages/core/src/editor/types.tspackages/core/src/canvas/renderer/types.tspackages/core/src/editor/state.tspackages/core/src/editor/selection/overlays.tspackages/core/src/canvas/renderer/pipeline.tspackages/vue/src/shared/input/move.tspackages/vue/src/canvas/useCanvasInput.tspackages/core/src/canvas/scene.ts
{packages,src}/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
{packages,src}/**/*.ts: Non-component TypeScript files use lowercase or kebab-case unless they are conventional entrypoints:index.ts,types.ts,context.ts,use.ts
Use subfolders for multi-file domains instead of sibling files with repeated prefixes: preferselection/container.ts,selection/hit-test.tsoverselection-container.ts,selection-hit-test.ts
When adding second file for domain (e.g.,eval-wrap.tsnext toeval.ts), create folder immediately (eval/index.ts+eval/wrap.ts) instead of prefixing
Files:
packages/core/src/editor/types.tspackages/core/src/canvas/renderer/types.tspackages/core/src/editor/state.tspackages/core/src/editor/selection/overlays.tspackages/core/src/canvas/renderer/pipeline.tspackages/vue/src/shared/input/move.tspackages/vue/src/canvas/useCanvasInput.tspackages/core/src/canvas/scene.ts
packages/{core,vue,mcp,cli}/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
Core, Vue, MCP, and CLI build with tsdown before publishing
Files:
packages/core/src/editor/types.tspackages/core/src/canvas/renderer/types.tspackages/core/src/editor/state.tspackages/core/src/editor/selection/overlays.tspackages/core/src/canvas/renderer/pipeline.tspackages/vue/src/shared/input/move.tspackages/vue/src/canvas/useCanvasInput.tspackages/core/src/canvas/scene.ts
CHANGELOG.md
📄 CodeRabbit inference engine (AGENTS.md)
Update
CHANGELOG.mdby moving "Unreleased" items under new version heading with date during release
Files:
CHANGELOG.md
packages/core/src/canvas/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
packages/core/src/canvas/**/*.ts: Viewport culling skips off-screen nodes; unclipped parents are NOT culled (children may extend beyond bounds)
Selection border width must be constant regardless of zoom — divide by scale
Section/frame title text never scales — render at fixed font size, ellipsize to fit
Rulers rendered on canvas (not DOM) with selection range badges that do not overlap tick numbers
Remote cursors: Figma-style colored arrows with white border + name pill, rendered in screen space
Canvas is CanvasKit (Skia WASM) on WebGL surface, not DOM
Files:
packages/core/src/canvas/renderer/types.tspackages/core/src/canvas/renderer/pipeline.tspackages/core/src/canvas/scene.ts
packages/vue/src/**/*.{ts,vue}
📄 CodeRabbit inference engine (AGENTS.md)
Store portable shortcuts such as
MOD+D,MOD+SHIFT+H,MOD+ALT+K; format them withformatShortcut()at render time
Files:
packages/vue/src/shared/input/move.tspackages/vue/src/canvas/useCanvasInput.ts
{tests/e2e,tests/figma,tests/engine}/**/*.{spec,test}.ts
📄 CodeRabbit inference engine (AGENTS.md)
Test placement is strict: app E2E tests live under
tests/e2e/**using*.spec.ts; Figma automation tests live undertests/figma/**using*.spec.ts; engine/unit tests live undertests/engine/**using*.test.ts
Files:
tests/engine/vue/input/cmd-bypass-auto-layout.test.tstests/engine/render/canvas/clips-content.test.ts
tests/engine/**/*.{ts,bench.ts}
📄 CodeRabbit inference engine (AGENTS.md)
Test helpers, benchmarks, and visual test scripts allowed in engine tests:
helpers.ts,*.bench.ts, andvisual-*support scripts
Files:
tests/engine/vue/input/cmd-bypass-auto-layout.test.tstests/engine/render/canvas/clips-content.test.ts
🧠 Learnings (36)
📓 Common learnings
Learnt from: CR
Repo: cardene777/open-pencil PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-06-02T15:53:00.948Z
Learning: Applies to packages/core/src/editor/**/*.ts : Dragging a child outside a frame should reparent it, not clip it
Learnt from: CR
Repo: cardene777/open-pencil PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-06-02T15:53:00.948Z
Learning: Applies to tests/e2e/**/*.spec.ts : Add or update Playwright canvas snapshot for changes to fills, gradients, images, blend modes, masks, boolean geometry, corners, strokes, shadows, blur, text rendering, or demo showcase
Learnt from: CR
Repo: cardene777/open-pencil PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-06-02T15:53:00.948Z
Learning: Applies to packages/core/src/scene-graph/**/*.ts : Frames clip content by default is OFF (unlike what you'd assume)
📚 Learning: 2026-06-02T15:53:00.948Z
Learnt from: CR
Repo: cardene777/open-pencil PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-06-02T15:53:00.948Z
Learning: Applies to packages/core/src/editor/**/*.ts : Dragging a child outside a frame should reparent it, not clip it
Applied to files:
packages/core/src/editor/types.tspackages/core/src/canvas/renderer/types.tspackages/core/src/editor/state.tspackages/core/src/editor/selection/overlays.tspackages/core/src/canvas/renderer/pipeline.tspackages/vue/src/shared/input/move.tstests/engine/vue/input/cmd-bypass-auto-layout.test.tspackages/vue/src/canvas/useCanvasInput.tspackages/core/src/canvas/scene.tstests/engine/render/canvas/clips-content.test.ts
📚 Learning: 2026-06-02T15:53:00.948Z
Learnt from: CR
Repo: cardene777/open-pencil PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-06-02T15:53:00.948Z
Learning: Applies to CHANGELOG.md : Update `CHANGELOG.md` by moving "Unreleased" items under new version heading with date during release
Applied to files:
CHANGELOG.md
📚 Learning: 2026-06-02T15:53:00.948Z
Learnt from: CR
Repo: cardene777/open-pencil PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-06-02T15:53:00.948Z
Learning: Applies to packages/core/src/editor/**/*.ts : `renderVersion` bumped by `requestRender()`; `renderVersion` vs `sceneVersion`: renderVersion = canvas repaint (pan/zoom/hover), sceneVersion = scene graph mutations
Applied to files:
packages/core/src/canvas/renderer/types.tspackages/core/src/canvas/renderer/pipeline.tspackages/vue/src/canvas/useCanvasInput.tspackages/core/src/canvas/scene.ts
📚 Learning: 2026-06-02T15:53:00.948Z
Learnt from: CR
Repo: cardene777/open-pencil PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-06-02T15:53:00.948Z
Learning: Applies to tests/e2e/**/*.spec.ts : Add or update Playwright canvas snapshot for changes to fills, gradients, images, blend modes, masks, boolean geometry, corners, strokes, shadows, blur, text rendering, or demo showcase
Applied to files:
packages/core/src/canvas/renderer/types.tspackages/core/src/canvas/renderer/pipeline.tstests/engine/vue/input/cmd-bypass-auto-layout.test.tspackages/vue/src/canvas/useCanvasInput.tspackages/core/src/canvas/scene.tstests/engine/render/canvas/clips-content.test.ts
📚 Learning: 2026-06-02T15:53:00.948Z
Learnt from: CR
Repo: cardene777/open-pencil PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-06-02T15:53:00.948Z
Learning: Applies to packages/core/src/canvas/**/*.ts : Rulers rendered on canvas (not DOM) with selection range badges that do not overlap tick numbers
Applied to files:
packages/core/src/canvas/renderer/types.tspackages/core/src/canvas/scene.tstests/engine/render/canvas/clips-content.test.ts
📚 Learning: 2026-06-02T15:53:00.948Z
Learnt from: CR
Repo: cardene777/open-pencil PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-06-02T15:53:00.948Z
Learning: Applies to {src,packages}/**/*.{ts,tsx,vue} : Code outside core editor internals must not assign `editor.state.selectedIds` or `editor.state.activeTool` directly; use `ctx.setSelectedIds()` and `ctx.setActiveTool()` or `editor.clearSelection()`, `editor.select()`, `editor.setTool()`
Applied to files:
packages/core/src/editor/selection/overlays.tspackages/vue/src/canvas/useCanvasInput.ts
📚 Learning: 2026-06-02T15:53:00.948Z
Learnt from: CR
Repo: cardene777/open-pencil PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-06-02T15:53:00.948Z
Learning: Applies to packages/core/src/editor/**/*.ts : All selection mutations in core use `ctx.setSelectedIds()` and all tool changes use `ctx.setActiveTool()` so event bus fires consistently
Applied to files:
packages/core/src/editor/selection/overlays.ts
📚 Learning: 2026-06-02T15:53:00.948Z
Learnt from: CR
Repo: cardene777/open-pencil PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-06-02T15:53:00.948Z
Learning: Applies to packages/core/src/editor/**/*.ts : `requestRepaint()` bumps only `renderVersion`; `renderNow()` only for surface recreation and font loading (need immediate draw)
Applied to files:
packages/core/src/canvas/renderer/pipeline.tspackages/vue/src/canvas/useCanvasInput.ts
📚 Learning: 2026-06-02T15:53:00.948Z
Learnt from: CR
Repo: cardene777/open-pencil PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-06-02T15:53:00.948Z
Learning: Applies to src/components/Canvas*.vue : Canvas/editor overlay code must not import property-panel internals
Applied to files:
packages/core/src/canvas/renderer/pipeline.tstests/engine/vue/input/cmd-bypass-auto-layout.test.tspackages/vue/src/canvas/useCanvasInput.ts
📚 Learning: 2026-06-02T15:53:00.948Z
Learnt from: CR
Repo: cardene777/open-pencil PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-06-02T15:53:00.948Z
Learning: Applies to src/app/**/*.vue : ScrubInput (drag-to-change number) — cursor and pointerdown on outer container, not inner spans
Applied to files:
packages/vue/src/shared/input/move.tstests/engine/vue/input/cmd-bypass-auto-layout.test.tspackages/vue/src/canvas/useCanvasInput.ts
📚 Learning: 2026-06-02T15:53:00.948Z
Learnt from: CR
Repo: cardene777/open-pencil PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-06-02T15:53:00.948Z
Learning: Applies to packages/vue/src/editor/**/*.{ts,vue} : Editor commands share `packages/vue/src/editor/commands/registry.ts` as canonical source for shortcut display tokens, keyboard bindings, and context-menu test IDs
Applied to files:
tests/engine/vue/input/cmd-bypass-auto-layout.test.ts
📚 Learning: 2026-06-02T15:53:00.948Z
Learnt from: CR
Repo: cardene777/open-pencil PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-06-02T15:53:00.948Z
Learning: Applies to packages/vue/src/editor/**/*.{ts,vue} : Canvas context-menu structure lives in `packages/vue/src/editor/menu-model/canvas.ts`; do not hand-build command grouping in components
Applied to files:
tests/engine/vue/input/cmd-bypass-auto-layout.test.tspackages/vue/src/canvas/useCanvasInput.ts
📚 Learning: 2026-06-02T15:53:00.948Z
Learnt from: CR
Repo: cardene777/open-pencil PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-06-02T15:53:00.948Z
Learning: Applies to src/**/*.{ts,vue} : App consumes `open-pencil/core` through targeted core subpath exports and `open-pencil/vue` through public Vue SDK entrypoint
Applied to files:
tests/engine/vue/input/cmd-bypass-auto-layout.test.tspackages/vue/src/canvas/useCanvasInput.ts
📚 Learning: 2026-06-02T15:53:00.948Z
Learnt from: CR
Repo: cardene777/open-pencil PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-06-02T15:53:00.948Z
Learning: Applies to tests/e2e/**/*.spec.ts : Pixel-affecting renderer features need committed visual coverage via Playwright canvas snapshot, not just mock/geometry assertions
Applied to files:
tests/engine/vue/input/cmd-bypass-auto-layout.test.tstests/engine/render/canvas/clips-content.test.ts
📚 Learning: 2026-06-02T15:53:00.948Z
Learnt from: CR
Repo: cardene777/open-pencil PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-06-02T15:53:00.948Z
Learning: Applies to tests/**/*.spec.ts : Test .fig round-trip by exporting and reimporting in Figma
Applied to files:
tests/engine/vue/input/cmd-bypass-auto-layout.test.tstests/engine/render/canvas/clips-content.test.ts
📚 Learning: 2026-06-02T15:53:00.948Z
Learnt from: CR
Repo: cardene777/open-pencil PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-06-02T15:53:00.948Z
Learning: Applies to src/app/**/*.vue : CSS `contain: paint layout style` on side panels to isolate repaints from WebGL canvas
Applied to files:
tests/engine/vue/input/cmd-bypass-auto-layout.test.ts
📚 Learning: 2026-06-02T15:53:00.948Z
Learnt from: CR
Repo: cardene777/open-pencil PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-06-02T15:53:00.948Z
Learning: Applies to tests/e2e/**/*.spec.ts : Use targeted snapshot updates such as `bunx playwright test tests/e2e/canvas/renderer-visuals.spec.ts --project=openpencil --update-snapshots`
Applied to files:
tests/engine/vue/input/cmd-bypass-auto-layout.test.tstests/engine/render/canvas/clips-content.test.ts
📚 Learning: 2026-06-02T15:53:00.948Z
Learnt from: CR
Repo: cardene777/open-pencil PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-06-02T15:53:00.948Z
Learning: Applies to packages/core/src/editor/**/*.ts : Auto-layout creation (Shift+A) must recompute layout immediately to update selection bounds
Applied to files:
tests/engine/vue/input/cmd-bypass-auto-layout.test.ts
📚 Learning: 2026-06-02T15:53:00.948Z
Learnt from: CR
Repo: cardene777/open-pencil PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-06-02T15:53:00.948Z
Learning: Applies to packages/core/src/editor/**/*.ts : When creating auto-layout, sort children by geometric position first
Applied to files:
tests/engine/vue/input/cmd-bypass-auto-layout.test.ts
📚 Learning: 2026-06-02T15:53:00.948Z
Learnt from: CR
Repo: cardene777/open-pencil PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-06-02T15:53:00.948Z
Learning: Applies to packages/core/src/editor/**/*.ts : Groups: creating a group must preserve children's visual positions
Applied to files:
tests/engine/vue/input/cmd-bypass-auto-layout.test.ts
📚 Learning: 2026-06-02T15:53:00.948Z
Learnt from: CR
Repo: cardene777/open-pencil PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-06-02T15:53:00.948Z
Learning: Applies to packages/vue/src/editor/**/*.ts : Vue SDK provides `useEditorEvent(event, handler)` composable that auto-disposes on scope cleanup
Applied to files:
packages/vue/src/canvas/useCanvasInput.ts
📚 Learning: 2026-06-02T15:53:00.948Z
Learnt from: CR
Repo: cardene777/open-pencil PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-06-02T15:53:00.948Z
Learning: Applies to packages/core/src/canvas/**/*.ts : Remote cursors: Figma-style colored arrows with white border + name pill, rendered in screen space
Applied to files:
packages/vue/src/canvas/useCanvasInput.ts
📚 Learning: 2026-06-02T15:53:00.948Z
Learnt from: CR
Repo: cardene777/open-pencil PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-06-02T15:53:00.948Z
Learning: Applies to src/app/**/*.{ts,vue} : Resize observer uses rAF throttle, not debounce — debounce causes canvas skew
Applied to files:
packages/vue/src/canvas/useCanvasInput.ts
📚 Learning: 2026-06-02T15:53:00.948Z
Learnt from: CR
Repo: cardene777/open-pencil PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-06-02T15:53:00.948Z
Learning: Applies to **/*.vue : Use `vueuse/core` hooks; prefer higher-level composables like `useBreakpoints`, `useEventListener`, `onClickOutside` over raw APIs
Applied to files:
packages/vue/src/canvas/useCanvasInput.ts
📚 Learning: 2026-06-02T15:53:00.948Z
Learnt from: CR
Repo: cardene777/open-pencil PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-06-02T15:53:00.948Z
Learning: Applies to **/*.vue : Use VueUse for DOM refs/focus: `templateRef`, `unrefElement`, `useFocus` instead of ref callback plumbing through slots
Applied to files:
packages/vue/src/canvas/useCanvasInput.ts
📚 Learning: 2026-06-02T15:53:00.948Z
Learnt from: CR
Repo: cardene777/open-pencil PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-06-02T15:53:00.948Z
Learning: Applies to src/**/*.vue : No module-level mutable state in components; use the editor store instead
Applied to files:
packages/vue/src/canvas/useCanvasInput.ts
📚 Learning: 2026-06-02T15:53:00.948Z
Learnt from: CR
Repo: cardene777/open-pencil PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-06-02T15:53:00.948Z
Learning: Applies to src/app/editor/session/**/*.ts : App editor session (`src/app/editor/session/create.ts`) is thin Vue wrapper: creates `shallowReactive` state, calls `createEditor()`, assembles app-specific modules
Applied to files:
packages/vue/src/canvas/useCanvasInput.ts
📚 Learning: 2026-06-02T15:53:00.948Z
Learnt from: CR
Repo: cardene777/open-pencil PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-06-02T15:53:00.948Z
Learning: Applies to packages/core/src/canvas/**/*.ts : Viewport culling skips off-screen nodes; unclipped parents are NOT culled (children may extend beyond bounds)
Applied to files:
packages/core/src/canvas/scene.tstests/engine/render/canvas/clips-content.test.ts
📚 Learning: 2026-06-02T15:53:00.948Z
Learnt from: CR
Repo: cardene777/open-pencil PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-06-02T15:53:00.948Z
Learning: Applies to packages/core/src/scene-graph/**/*.ts : Frames clip content by default is OFF (unlike what you'd assume)
Applied to files:
packages/core/src/canvas/scene.tstests/engine/render/canvas/clips-content.test.ts
📚 Learning: 2026-06-02T15:53:00.948Z
Learnt from: CR
Repo: cardene777/open-pencil PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-06-02T15:53:00.948Z
Learning: Applies to packages/core/src/scene-graph/**/*.ts : Domain types (SceneNode, Fill, Stroke, Effect, BlendMode) live in `packages/core/src/scene-graph/` and exported from `open-pencil/core/scene-graph`
Applied to files:
packages/core/src/canvas/scene.ts
📚 Learning: 2026-06-02T15:53:00.948Z
Learnt from: CR
Repo: cardene777/open-pencil PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-06-02T15:53:00.948Z
Learning: Applies to packages/core/src/scene-graph/**/*.ts : Instance children map to component children via `componentId` for 1:1 sync
Applied to files:
packages/core/src/canvas/scene.ts
📚 Learning: 2026-06-02T15:53:00.948Z
Learnt from: CR
Repo: cardene777/open-pencil PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-06-02T15:53:00.948Z
Learning: Applies to packages/core/src/scene-graph/**/*.ts : Nodes live in flat `Map<string, SceneNode>`, tree via `parentIndex` references
Applied to files:
packages/core/src/canvas/scene.ts
📚 Learning: 2026-06-02T15:53:00.948Z
Learnt from: CR
Repo: cardene777/open-pencil PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-06-02T15:53:00.948Z
Learning: Applies to packages/core/src/canvas/**/*.ts : Canvas is CanvasKit (Skia WASM) on WebGL surface, not DOM
Applied to files:
tests/engine/render/canvas/clips-content.test.ts
📚 Learning: 2026-06-02T15:53:00.948Z
Learnt from: CR
Repo: cardene777/open-pencil PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-06-02T15:53:00.948Z
Learning: Applies to packages/core/src/canvas/**/*.ts : Section/frame title text never scales — render at fixed font size, ellipsize to fit
Applied to files:
tests/engine/render/canvas/clips-content.test.ts
📚 Learning: 2026-06-02T15:53:00.948Z
Learnt from: CR
Repo: cardene777/open-pencil PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-06-02T15:53:00.948Z
Learning: Applies to tests/engine/**/*.{ts,bench.ts} : Test helpers, benchmarks, and visual test scripts allowed in engine tests: `helpers.ts`, `*.bench.ts`, and `visual-*` support scripts
Applied to files:
tests/engine/render/canvas/clips-content.test.ts
🔇 Additional comments (13)
CHANGELOG.md (1)
16-16: LGTM!packages/core/src/editor/types.ts (1)
38-38: LGTM!packages/core/src/editor/state.ts (1)
13-13: LGTM!packages/core/src/canvas/renderer/types.ts (1)
22-22: LGTM!packages/core/src/editor/selection/overlays.ts (1)
33-37: LGTM!Also applies to: 59-68
packages/core/src/canvas/scene.ts (3)
156-170: LGTM!
172-218: LGTM!
220-252: LGTM!packages/core/src/canvas/renderer/pipeline.ts (1)
64-64: LGTM!Also applies to: 82-90
tests/engine/render/canvas/clips-content.test.ts (1)
23-116: LGTM!packages/vue/src/shared/input/move.ts (1)
54-62: LGTM!Also applies to: 82-82, 189-190
tests/engine/vue/input/cmd-bypass-auto-layout.test.ts (1)
218-251: LGTM!packages/vue/src/canvas/useCanvasInput.ts (1)
42-69: ⚡ Quick winduplicate ドラッグ中断時の後始末不足(複製ノード残留の可能性)
DragStateにはduplicatedとduplicatedPreviousSelectionがあり、packages/vue/src/shared/input/move.tsのd.duplicated && !moved経路ではeditor.graph.deleteNode(...)で複製ノードを削除し、editor.select(...)で選択を復元しています。一方packages/vue/src/canvas/useCanvasInput.tsのcancelMoveDragInterruptionはdrag.value = nullやプレビュー復元のみで、複製ノード削除/選択復元のd.duplicated相当処理がありません(packages/vue/src/shared/input/duplicate-drag.tsではduplicated: trueを付与して複製ドラッグを開始)。
cancelMoveDragInterruption側でもactiveDrag.duplicatedを見て、handleMoveUpの!movedと同等に「複製ノード削除+duplicatedPreviousSelectionで選択復元」を入れることを検討してください。
| describe('clipsContent rendering', () => { | ||
| test('renders absolute children outside the parent clip while auto-layout children stay clipped', () => { | ||
| const graph = new SceneGraph() | ||
| const frame = createClippingFrame(graph, 'Frame') | ||
| const autoChild = graph.createNode('RECTANGLE', frame.id, { | ||
| x: 0, | ||
| y: 0, | ||
| width: 40, | ||
| height: 40 | ||
| }) | ||
| const absoluteChild = graph.createNode('RECTANGLE', frame.id, { | ||
| x: 140, | ||
| y: 0, | ||
| width: 40, | ||
| height: 40, | ||
| layoutPositioning: 'ABSOLUTE' | ||
| }) | ||
|
|
||
| const records = renderChildRecords(graph, frame.id) | ||
|
|
||
| expect(records).toEqual([ | ||
| { nodeId: autoChild.id, clipped: true }, | ||
| { nodeId: absoluteChild.id, clipped: false } | ||
| ]) | ||
| }) | ||
|
|
||
| test('limits drag clip bypass to the targeted frame', () => { | ||
| const graph = new SceneGraph() | ||
| const bypassFrame = createClippingFrame(graph, 'BypassFrame') | ||
| const normalFrame = createClippingFrame(graph, 'NormalFrame', 200) | ||
| const bypassChild = graph.createNode('RECTANGLE', bypassFrame.id, { | ||
| x: 120, | ||
| y: 0, | ||
| width: 40, | ||
| height: 40 | ||
| }) | ||
| const normalChild = graph.createNode('RECTANGLE', normalFrame.id, { | ||
| x: 120, | ||
| y: 0, | ||
| width: 40, | ||
| height: 40 | ||
| }) | ||
|
|
||
| const bypassRecords = renderChildRecords(graph, bypassFrame.id, { | ||
| draggingClipBypassFrameId: bypassFrame.id | ||
| }) | ||
| const normalRecords = renderChildRecords(graph, normalFrame.id, { | ||
| draggingClipBypassFrameId: bypassFrame.id | ||
| }) | ||
|
|
||
| expect(bypassRecords).toEqual([{ nodeId: bypassChild.id, clipped: false }]) | ||
| expect(normalRecords).toEqual([{ nodeId: normalChild.id, clipped: true }]) | ||
| }) | ||
|
|
||
| test('keeps a mask and its absolute target in the same clipped auto-layout run', () => { | ||
| const graph = new SceneGraph() | ||
| const frame = createClippingFrame(graph, 'MaskedFrame') | ||
| const mask = graph.createNode('RECTANGLE', frame.id, { | ||
| x: 0, | ||
| y: 0, | ||
| width: 50, | ||
| height: 50, | ||
| isMask: true | ||
| }) | ||
| const target = graph.createNode('RECTANGLE', frame.id, { | ||
| x: 120, | ||
| y: 0, | ||
| width: 40, | ||
| height: 40, | ||
| layoutPositioning: 'ABSOLUTE' | ||
| }) | ||
|
|
||
| const parent = getNodeOrThrow(graph, frame.id) | ||
|
|
||
| expect(getRenderableChildRuns(graph, parent, parent.childIds)).toEqual([ | ||
| { childIds: [mask.id, target.id], shouldClip: true } | ||
| ]) | ||
| }) | ||
|
|
||
| test('keeps an all-absolute mask group outside the clip in auto-layout frames', () => { | ||
| const graph = new SceneGraph() | ||
| const frame = createClippingFrame(graph, 'AbsoluteMaskedFrame') | ||
| const mask = graph.createNode('RECTANGLE', frame.id, { | ||
| x: 0, | ||
| y: 0, | ||
| width: 50, | ||
| height: 50, | ||
| isMask: true, | ||
| layoutPositioning: 'ABSOLUTE' | ||
| }) | ||
| const target = graph.createNode('RECTANGLE', frame.id, { | ||
| x: 120, | ||
| y: 0, | ||
| width: 40, | ||
| height: 40, | ||
| layoutPositioning: 'ABSOLUTE' | ||
| }) | ||
|
|
||
| const parent = getNodeOrThrow(graph, frame.id) | ||
|
|
||
| expect(getRenderableChildRuns(graph, parent, parent.childIds)).toEqual([ | ||
| { childIds: [mask.id, target.id], shouldClip: false } | ||
| ]) | ||
| }) | ||
|
|
||
| test('keeps absolute children clipped for layoutMode NONE frames', () => { | ||
| const graph = new SceneGraph() | ||
| const frame = graph.createNode('FRAME', pageId(graph), { | ||
| name: 'RegularFrame', | ||
| x: 0, | ||
| y: 0, | ||
| width: 100, | ||
| height: 100, | ||
| clipsContent: true, | ||
| layoutMode: 'NONE' | ||
| }) | ||
| const absoluteChild = graph.createNode('RECTANGLE', frame.id, { | ||
| x: 140, | ||
| y: 0, | ||
| width: 40, | ||
| height: 40, | ||
| layoutPositioning: 'ABSOLUTE' | ||
| }) | ||
|
|
||
| const parent = getNodeOrThrow(graph, frame.id) | ||
|
|
||
| expect(getRenderableChildRuns(graph, parent, parent.childIds)).toEqual([ | ||
| { childIds: [absoluteChild.id], shouldClip: true } | ||
| ]) | ||
| expect(renderChildRecords(graph, frame.id)).toEqual([ | ||
| { nodeId: absoluteChild.id, clipped: true } | ||
| ]) | ||
| }) | ||
| }) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Look for committed Playwright snapshots / specs touching clip/absolute/auto-layout rendering
fd -e spec.ts . tests/e2e | xargs rg -ln -i 'clip|absolute|auto-?layout|bypass' 2>/dev/null
echo '--- snapshot image artifacts ---'
fd -i -e png . tests/e2e | rg -i 'clip|absolute|layout|bypass' || echo 'no matching snapshot pngs found'Repository: cardene777/open-pencil
Length of output: 490
clipsContent/クリップバイパス/ABSOLUTE の視覚スナップショット追加を確認してください
tests/engine/render/canvas/clips-content.test.ts は mock によるクリップ判定(__clipActive)のジオメトリ検証中心のため、ピクセル影響のあるクリッピング挙動は Playwright canvas スナップショットでの視覚カバレッジも必要です。tests/e2e 配下では clip|absolute|auto-layout|bypass に関連する png スナップショットの一致が見つかりませんでした。該当シナリオ(auto-layout + ABSOLUTE、draggingClipBypassFrameId の限定、mask/absolute run、layoutMode: 'NONE')が反映される canvas snapshot を追加してください。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/engine/render/canvas/clips-content.test.ts` around lines 118 - 251, Add
Playwright canvas snapshot tests under tests/e2e that render the same scenarios
exercised in clips-content.test.ts: (1) auto-layout frame with both auto and
ABSOLUTE children (auto-layout + ABSOLUTE), (2) drag clip bypass limited to the
targeted frame using draggingClipBypassFrameId, (3) mask with absolute target
kept in same clipped run and an all-absolute mask group outside the clip, and
(4) layoutMode: 'NONE' frame showing absolute children still clipped; reuse the
scene construction helpers (createClippingFrame,
renderChildRecords/getRenderableChildRuns or equivalent scene setup) and capture
PNG snapshots named to reflect clip|absolute|auto-layout|bypass so the e2e
snapshot suite verifies the actual canvas pixel output for those cases.
* ✨ feat(renderer): Cmd+ ドラッグで Screen 直下の子も clip 外で見えるよう bypass を Set 化 PR #5 が救えていなかった「layoutMode='NONE' な Screen 直下の子を Cmd+ ドラッグして Screen の bbox 外に出した時、drag 中に本体が消えて枠だけ残る」regression を解消する。 主な変更: - EditorState.draggingClipBypassFrameId (string | null) を draggingClipBypassFrameIds (Set<string> | null) に変更 - setDraggingClipBypassFrameIds setter で Set 等価判定 (size + every has) で idempotent - handleMoveMove の syncDraggingClipBypassFrame で d.autoLayoutParentId に加えて d.originals の各 parentId (Screen 直下の親も含む) を isClippableFrameType でフィルタして Set に集約 - scene.ts の renderChildren で overlays.draggingClipBypassFrameIds?.has(node.id) で clip skip 判定 - useCanvasInput の cancelMoveDragInterruption / clearDraggingClipBypassFrame helper / 4 cleanup paths も Set ベースに統一 - mouseup 後の commit 挙動は PR #4/#5 と同じく変更なし regression test: layoutMode='NONE' Screen 直下の Cmd+ ドラッグで bypass Set に Screen ID が含まれ、scene.ts の renderChildren が clip skip 経路に流れることを assert。 27 tests / 0 fail (PR #5 既存 + 新規 2 件)。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 🔧 refactor(renderer): drag bypass を Set から boolean draggingClipBypassAll に簡略化 直前 commit 542b655 の Set ベース管理では「drag 対象の親 frame」だけを bypass していたが、実機検証で drag 中の子が通過する他の Screen の clip mask で本体が消える regression が判明した。 ユーザー期待値「drag 中はどこに動かしても本体が見える」(Figma 同等) に合わせて、Cmd+ ドラッグ中は全 clipsContent frame の clip を一律 skip するシンプル実装に変更。 主な変更: - EditorState.draggingClipBypassFrameIds (Set<string> | null) を削除し draggingClipBypassAll (boolean) に置換 - setter setDraggingClipBypassAll で boolean 等価判定で idempotent - handleMoveMove で Cmd 押下中なら setDraggingClipBypassAll(true)、handleMoveUp と 4 cleanup paths で false - scene.ts の renderChildren で !overlays.draggingClipBypassAll を shouldClip に追加 - PR #5 の getRenderableChildRuns (auto-layout 限定 unclip + mask chain 維持) はそのまま残し、drag 中だけ全 clip skip 経路に流れる layered design これにより 35 行削減し、Set 管理の複雑度を排除。drag 対象が任意の Screen を通過しても本体が見え続ける。 regression test (clips-content + cmd-bypass) を boolean ベースに更新、27 pass / 0 fail。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 🔧 fix(editor): Cmd+drag mouseup 後の auto-layout 元位置詰めと layoutMode=NONE Screen 直下の ABSOLUTE 子可視化 実機検証で発覚した 2 regression を修正: 問題 1: mouseup 後に auto-layout 元位置に空白が残る → commitMoveWithAbsolutePin の forward / inverse で originals と finals の parentId 集約 Set を作り、各 parentId に runLayoutForNode を呼んで yoga 再計算。ABSOLUTE 化と同時に sibling が詰まる、undo でも sibling 配置が戻る 問題 2: mouseup 後に ABSOLUTE 化された子が layoutMode='NONE' の Screen 直下では見えない → getRenderableChildRuns の parent.layoutMode !== 'NONE' 早期 return を削除し、全 clipsContent frame で run 分割ロジック (mask chain 維持 + ABSOLUTE 子 clip 外描画) を適用。PR #5 の F-A003 で意図的に絞った auto-layout 限定 unclip を Figma 互換に緩和 regression test 追加: mouseup 後の sibling 詰め + undo 復元 (cmd-bypass) / layoutMode='NONE' Screen 直下の ABSOLUTE 子 clip 外描画 (clips-content)。27 pass / 0 fail。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ✨ feat(editor): Cmd+drag で要素を全画面どこでも自由配置できるよう描画と layout を整備 PR #5 で対応した clip 修正の続き。実機検証で発覚した複数 regression を一括解消し、Cmd+drag mouseup 後の挙動を Figma 完全互換に揃える。 主な修正: - 祖先 clip も bypass する遅延キュー render pass を scene.ts に追加 (祖先 frame の clipsContent に関わらず ABSOLUTE 子が見える) - getRenderableChildRuns から auto-layout 限定 early return を削除、layoutMode=NONE Screen 直下でも ABSOLUTE 子は clip 外で描画 - getRenderableChildRuns で ABSOLUTE 子を末尾 run に並べ替え、z-order が AUTO 子の前面に来るよう統一 - mask + ABSOLUTE sibling が同一 run に閉じ込められて mask path に消える regression を修正、mask 直後の ABSOLUTE sibling を独立 run に切り出し - geometry.ts collectDescendantVisualBounds で ABSOLUTE 子の直近の親 clip だけ bypass、祖先 clip は維持 - commitMoveWithAbsolutePin で元 parent と新 parent の runLayoutForNode を集約し yoga を再計算 (auto-layout 元位置の gap が詰まる) - ABSOLUTE pin commit 時に Page 直下へ reparent し canvas 最前面に持ち上げる (他 Screen に重なっても隠れない) - DragMove に frozenParentSizes / frozenSiblingSizes を追加し drag 開始時の auto-layout 親と兄弟要素の width/height を記録 / commit 後に強制復元 (残った要素が parent の全幅に広がる現象を防止) - draggingClipBypassFrameId(s) を boolean draggingClipBypassAll に簡略化し drag 中の全 clip skip と cleanup paths を統一 - useCanvasInput に blur / visibilitychange / tool:changed / onScopeDispose の 4 cleanup paths と cancelMoveDragInterruption を追加し state leak を防止 regression test を tests/engine/render/canvas/clips-content.test.ts と cmd-bypass-auto-layout.test.ts と clips-content + cache + visual-bounds + silhouette-autopsy に多数追加。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
概要
PR #4 (Cmd+ ドラッグで auto-layout 内ノードを自由配置できる bypass キー) でマージした機能の続きで、実機検証で見つかった clip 関連の不具合を解消する。
具体的には Cmd+ ドラッグで auto-layout 親 frame の外に子を移動させた時、子が
clipsContent=trueの clip mask に隠されて見えなくなる問題と、commit 後の ABSOLUTE 子が同じく親 bbox 外で消える問題の両方を Figma 互換の振る舞いに揃える。課題
PR #4 マージ直後の実機ドラッグ確認で以下が判明した。
layoutPositioning='ABSOLUTE'が確定した後も、親 frame bbox の外にある子は同じ理由で見えないFigma では auto-layout 親の clipsContent が true でも、ABSOLUTE 子だけは親 clip 範囲を越えて描画される。
本 PR はこの挙動に揃える。
詳細
scene.ts の renderChildren は親 frame の bbox で全子を一律にクリップしていた。
graph LR A[Parent frame clipsContent=true] --> B[clipRect 0,0 width,height] B --> C[全子を clip 内で描画] C --> D[bbox 外の子は削られる]drag 中の preview もこの経路を通るため、Cmd+ ドラッグで親 frame を越えると preview 位置が clip 範囲外になり画面から消える。
さらに mouseup 後 ABSOLUTE 化された子も同じ clip mask の対象になり、親 bbox 外に置いた瞬間に消える。
解決方法
scene.ts の renderChildren を以下の順序で書き換えた。
layoutMode !== 'NONE') でない場合は従来通り全子を clip 内で描画する (regression 防止)draggingClipBypassFrameId === node.idの時は clip 自体を一時無効化して preview が親 bbox 外でも見えるようにする仕組み
修正後の描画フローと state cleanup の関係。
graph TD A[renderChildren] --> B{auto-layout?} B -->|No| C[全子を clip 内で描画 = 旧挙動] B -->|Yes| D[getRenderableChildRuns] D --> E[run 分割 mask 群は同一 run] E --> F{run.shouldClip} F -->|true AUTO 含む| G[save + clip + render + restore] F -->|false 全 ABSOLUTE| H[clip 外で render] A --> I{dragging bypass frame?} I -->|Yes| J[clip skip 全子を clip 外で render]sequenceDiagram participant User participant useCanvasInput participant editor.state participant scene.ts User->>useCanvasInput: mousedown + Cmd hold + drag useCanvasInput->>editor.state: setDraggingClipBypassFrameId parent scene.ts->>scene.ts: draggingClipBypassFrameId === node.id なら clip skip User->>useCanvasInput: 別 window へ blur (interruption) useCanvasInput->>useCanvasInput: cancelMoveDragInterruption useCanvasInput->>editor.state: preview を originals に戻す useCanvasInput->>useCanvasInput: drag.value = null useCanvasInput->>editor.state: setDraggingClipBypassFrameId null変更したファイルと内容。
packages/core/src/canvas/scene.tsgetRenderableChildRunsを新規 export し、auto-layout 親では子を AUTO/ABSOLUTE run に分割。mask + target を同一 run にまとめて mask chain を維持。getVisibleMaskTypehelper も抽出して SSOT 化。renderChildrenは run ごとに save/clip/restore を切り替え、drag bypass frame は clip 全体を skippackages/core/src/canvas/renderer/pipeline.tspackages/core/src/canvas/renderer/types.tspackages/core/src/editor/types.tspackages/core/src/editor/state.tspackages/core/src/editor/selection/overlays.tspackages/vue/src/shared/input/move.tspackages/vue/src/canvas/useCanvasInput.tstests/engine/render/canvas/clips-content.test.ts(新規)tests/engine/vue/input/cmd-bypass-auto-layout.test.tsCHANGELOG.mdテストと確認
bun --filter @open-pencil/core buildとbun --filter @open-pencil/vue build成功レビューで見てほしいところ。
getRenderableChildRunsの mask + target を同一 run にまとめる判定が Figma 互換で正しいか (mask group 内に AUTO/ABSOLUTE 混在時は clip ON 側を優先する設計)cancelMoveDragInterruptionが drag preview を originals に戻して drag.value を null にする復元順序が安全か (commit せず破棄、layoutInsertIndicator / snapGuides / dropTarget も同時クリア)parent.layoutMode !== undefined && parent.layoutMode !== 'NONE'の auto-layout 判定で SECTION / CANVAS / 古い node の取り扱いが意図通りか既知の限界 / follow-up
関連
このリポジトリは fork で Issues 機能が無効化されているため、本 PR は Issue を伴わない直接 PR として提出する。
Summary by CodeRabbit
リリースノート