Skip to content

feat: instance-specific hotspots and connections (additive)#47

Merged
trmquang93 merged 3 commits intomainfrom
feat/instance-hotspots
Apr 27, 2026
Merged

feat: instance-specific hotspots and connections (additive)#47
trmquang93 merged 3 commits intomainfrom
feat/instance-hotspots

Conversation

@trmquang93
Copy link
Copy Markdown
Collaborator

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."
  • Image and dimensions still inherit from the canonical at render.
  • Generated component files emit a per-Placement subsection so the AI agent sees both the canonical spec and any per-placement extras.
  • When a canonical is deleted/unlinked, the auto-promoted instance now keeps the canonical's hotspots (merged with its own locals) and any connections that referenced canonical-owned hotspot ids are re-pointed to it.

No file format version bump (v15 stays v15). Pure semantic shift on instance.hotspots[].

Phases

  1. Render-time inheritance change — drop "hotspots" from VISUAL_FIELDS in resolveInstanceVisuals. Instances render their own hotspots[].
  2. Unblock canvas editing on instances — remove all 8 isInstanceVisual gates from ScreenNode.jsx; drop the prop pass-through from CanvasArea.jsx.
  3. Auto-promotion merge & connection re-point — new mergeOnPromotion helper in useScreenManager.js, wired into removeScreen, removeScreens, and the setScreenComponent("unlink") branch. Captures firstInstance.id and canonicalHotspotIds BEFORE both setters so neither closure reads stale state. Re-points connections that referenced canonical-owned hotspot ids before the existing orphan filter runs.
  4. Export merge in component files — new ### Placement: <name> subsection in components/<slug>.md after the placements table and before the Spec block, for non-canonical placements with non-empty local content. Reuses renderHotspotDetailBlock. Canonical placement skipped (its spec is the Spec block). screens.md instance stubs unchanged (regression covered).
  5. Documentation & MCP tool descriptions — new "Instance-specific hotspots" subsection in userGuide.md (auto-mirrored to #/docs); MCP create_hotspot and list_hotspots description 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 via removeScreens, 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

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.
@trmquang93 trmquang93 merged commit ee5fcc8 into main Apr 27, 2026
1 check passed
@trmquang93 trmquang93 deleted the feat/instance-hotspots branch April 27, 2026 16:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant