feat: instance-specific hotspots and connections (additive)#47
Merged
trmquang93 merged 3 commits intomainfrom Apr 27, 2026
Merged
feat: instance-specific hotspots and connections (additive)#47trmquang93 merged 3 commits intomainfrom
trmquang93 merged 3 commits intomainfrom
Conversation
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.
When a canonical component screen is removed (single delete, batch delete) or unlinked while instances remain, the auto-promoted instance must inherit the canonical's hotspots so the spec is not lost and any existing hotspot-driven connections stay live. Add a mergeOnPromotion helper that unions the canonical's hotspots with the promoted instance's locals, deduped by id (canonical wins on collision). Wire it into the three call sites in useScreenManager: removeScreen, removeScreens, and the setScreenComponent unlink branch. In each, capture firstInstance.id and canonicalHotspotIds BEFORE the setters so neither closure reads stale state, then re-point connections that originated from the removed canonical and referenced one of the canonical-owned hotspot ids onto the promoted instance — BEFORE the existing orphan-filter drops connections still touching the removed id. setScreenComponent's unlink branch previously did not touch connections; add a paired setConnections call scoped to the unlink-canonical-with-instances case only. Tests cover: single delete promotion + re-point, multi-delete via removeScreens, manual unlink, and id-collision dedup with canonical winning.
In generateComponentFiles, after the Placements table and before the Spec block, iterate non-canonical placements and emit a "### Placement: <name>" subsection for each instance with non-empty content. The block includes: - Per-hotspot detail via the shared renderHotspotDetailBlock renderer, so canonical and placement hotspot details look identical to the AI agent. - Screen-level outgoing connections (no hotspotId) listed as "- → <target.name> (<action>)". Skip the canonical placement — its spec is already emitted by the Spec block. Skip instances with no local hotspots and no screen-level outgoing connections. Tests cover: emission of the Placement section for an instance with a local hotspot, omission for the canonical itself, omission when the instance has no local content, and regression that screens.md keeps its one-line instance stub. Update userGuide.md (and therefore the #/docs page, which imports the markdown raw) with a new "Instance-specific hotspots" subsection explaining the additive semantics and the auto-promotion merge. Soften the prior TIP that called instance hotspots read-only. Update MCP create_hotspot and list_hotspots tool descriptions to clarify canonical-vs-instance hotspot semantics. No logic change.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR makes hotspots and connections on component instances fully editable and additive over the canonical. Today instances inherit hotspots at render time and are read-only; this is too restrictive when the same component appears in different flows and one placement needs an extra tap area or navigation link (e.g. a Skip button that only belongs on the onboarding placement of
LoginScreen).The semantic shift is small but high-leverage:
screen.hotspots[]on an instance now means placement-local additive hotspots, not "ignored at render time."No file format version bump (v15 stays v15). Pure semantic shift on
instance.hotspots[].Phases
"hotspots"fromVISUAL_FIELDSinresolveInstanceVisuals. Instances render their ownhotspots[].isInstanceVisualgates fromScreenNode.jsx; drop the prop pass-through fromCanvasArea.jsx.mergeOnPromotionhelper inuseScreenManager.js, wired intoremoveScreen,removeScreens, and thesetScreenComponent("unlink")branch. CapturesfirstInstance.idandcanonicalHotspotIdsBEFORE both setters so neither closure reads stale state. Re-points connections that referenced canonical-owned hotspot ids before the existing orphan filter runs.### Placement: <name>subsection incomponents/<slug>.mdafter the placements table and before the Spec block, for non-canonical placements with non-empty local content. ReusesrenderHotspotDetailBlock. Canonical placement skipped (its spec is the Spec block).screens.mdinstance stubs unchanged (regression covered).userGuide.md(auto-mirrored to#/docs); MCPcreate_hotspotandlist_hotspotsdescription text clarifies canonical-vs-instance semantics. No MCP logic change.Tests added
resolveInstanceVisuals.test.js— instance keeps own hotspots; image fields still inherit; canonical's hotspots NOT copied to empty instance.useScreenManager.test.js— single delete, multi-delete viaremoveScreens, manual unlink: all promote, merge, and re-point. Dedup case asserts canonical wins on hotspot id collision.generateInstructionFiles.test.js— Placement subsection emitted for instance with local hotspot; canonical not emitted as placement block; instance with no local content emits no block; screens.md stub regression.Test plan
+ Linkon instance: modal opens; saving creates instance-local hotspot.components/<slug>.mdshows canonical Spec + a "Placement: instance feat: extend Delete key to single hotspots, sticky notes, and screen groups #1" block with the local hotspot detail.removeScreens. Same expectations.list_hotspots(instanceId)returns the local hotspots only.