From de09bcc66d4be9567a5c5a9003ef80cf95e2ac0e Mon Sep 17 00:00:00 2001 From: Quang Tran <16215255+trmquang93@users.noreply.github.com> Date: Mon, 27 Apr 2026 23:19:35 +0700 Subject: [PATCH 1/3] feat: enable hotspot/connection editing on component instances Drop "hotspots" from VISUAL_FIELDS in resolveInstanceVisuals: instance screens no longer inherit canonical hotspots at render time. Image and dimensions still inherit. screen.hotspots[] on an instance now holds the placement's own additive local hotspots, rendered as-is. Remove every isInstanceVisual gate in ScreenNode (the + Link button, rubber-band draw, hotspot mouse/double-click, the hotspot connect-drag handle, and resize handles). Drop the prop pass-through from CanvasArea and the unused isResolvedInstance import. Tests: - resolveInstanceVisuals: invert the merged-hotspots assertion; add cases for non-empty locals returning verbatim, empty locals returning empty (canonical's NOT copied), and image fields still inheriting. This is a pure semantic shift on instance.hotspots[]; no schema or file format change. --- src/components/CanvasArea.jsx | 4 +- src/components/ScreenNode.jsx | 16 ++---- src/utils/resolveInstanceVisuals.js | 20 ++++--- src/utils/resolveInstanceVisuals.test.js | 72 +++++++++++++++++++++++- 4 files changed, 88 insertions(+), 24 deletions(-) diff --git a/src/components/CanvasArea.jsx b/src/components/CanvasArea.jsx index b88d933..d3b0de5 100644 --- a/src/components/CanvasArea.jsx +++ b/src/components/CanvasArea.jsx @@ -3,7 +3,7 @@ import { DEFAULT_SCREEN_WIDTH, DEFAULT_SCREEN_HEIGHT } from "../constants"; import { copyScreenForFigma, copyScreensForFigma, copyScreensForFigmaEditable, downloadScreenSvg } from "../utils/copyToFigma"; import { copyScreensAsImage } from "../utils/copyAsImage"; import { ScreenNode } from "./ScreenNode"; -import { resolveInstanceVisuals, isResolvedInstance } from "../utils/resolveInstanceVisuals"; +import { resolveInstanceVisuals } from "../utils/resolveInstanceVisuals"; import { ConnectionLines } from "./ConnectionLines"; import { ConditionalPrompt } from "./ConditionalPrompt"; import { ConnectionTypePrompt } from "./ConnectionTypePrompt"; @@ -163,12 +163,10 @@ export function CanvasArea({ ))} {screens.map((screen) => { const visualScreen = resolveInstanceVisuals(screen, screens); - const instanceVisual = isResolvedInstance(screen) && visualScreen !== screen; return ( { clearSelection(); setSelectedScreen(id); setSelectedStickyNote(null); }} onDragStart={onDragStart} diff --git a/src/components/ScreenNode.jsx b/src/components/ScreenNode.jsx index 941a9ee..4d5aac2 100644 --- a/src/components/ScreenNode.jsx +++ b/src/components/ScreenNode.jsx @@ -21,10 +21,6 @@ export function ScreenNode({ selectedCommentId, onCommentPinClick, onDeselectComment, - // True when this is an instance whose image/hotspots are inherited from - // its canonical at render time. Hotspot edits are read-only here — users - // must edit the canonical instead. - isInstanceVisual, }) { const [imgLoaded, setImgLoaded] = useState(false); const [isEditingDesc, setIsEditingDesc] = useState(false); @@ -370,7 +366,7 @@ export function ScreenNode({ > S+ - {!isInstanceVisual && } +