Wireframe: anchorless captured blocks can't be exploded and fail silently
Summary
In the snapshot Wireframe tool, a captured block that has no source anchor
(anchor.file === '') cannot be broken down: double-clicking "Explode into
child blocks" does nothing — no decomposition, and no feedback. To the user
the MFE/region "is a single image that cannot be broken down."
A block ends up anchorless in two common situations, and both happen with
ordinary, fully-instrumented MFEs — not just exotic setups:
-
Instrumented MFE whose content is rendered by un-instrumented components.
Annotask transforms an MFE's own JSX, but not code imported from a
pre-built bundle (a shared component-library dist, a node_modules
package, etc.). DOM produced by those components carries no data-annotask-*
attributes. When a captured region block lands on such a subtree,
wfResolveMfeAnchor() honestly resolves it to { file: '' } — and explode
then refuses it.
-
Un-instrumented MFE region (pending). A framework mount
(single-spa-application:*, qiankun wrapper, <application>) with no
data-annotask-mfe descendant (production bundle, third-party remote, or an
async bundle that hasn't mounted yet) is emitted as one opaque block with an
empty anchor.
The geometry and pixels of these blocks are fully present in the live DOM — only
the source mapping is missing. The block is therefore decomposable/explodable
in principle; the tool just declines because it keys explode off anchor.file.
- Affects:
annotask@0.4.3 (and 0.4.2, where MFE-aware capture shipped)
- Area:
src/shell/composables/useWireframeMode.ts, src/plugin/bridge/wireframe-walker.ts
Steps to reproduce
- Run an MFE whose UI is built largely from a shared component library that
ships as a pre-built bundle (so its rendered DOM has no data-annotask-*),
e.g. a React MFE rendering @your-org/ui-primitives aliased to
.../primitives/dist/index.js.
- Open the route in Annotask, enter Wireframe mode, and Capture.
- Double-click the MFE's block (or its Explode into child blocks affordance).
Expected
The block decomposes into its child blocks (geometric is fine when there is no
source anchor), so the region can be wireframed — or, if explode is genuinely not
possible, the UI says why.
Actual
Nothing happens. explodeBlock() returns early and silently.
Root cause
Explode is gated on anchor.file, and fails silently
// src/shell/composables/useWireframeMode.ts (~L782) — explodeBlock()
if (!parent.anchor?.file || !parent.originalRect) return false // <- silent, no error.value
The same gate is on the hover "can this explode?" probe, so the block is never
even highlighted as drillable:
// src/shell/composables/useWireframeMode.ts (~L748)
if (!block.anchor?.file || !block.originalRect) return []
The explode button, however, is still rendered for any
kind === 'captured' && !shell block (WireframeCanvas.vue), so the user clicks
it and gets no response.
Why instrumented MFEs still produce anchorless blocks
wfResolveMfeAnchor() correctly returns an empty anchor when an element's
nearest instrumented ancestor is host glue and neither it nor its descendants
carry the region's MFE stamp — which is exactly what happens for DOM rendered by
un-instrumented shared/library/dist components:
// src/plugin/bridge/wireframe-walker.ts (~L259)
// Honest unanchored — never the host file.
return { file: '', line: '', component: '', source_tag: '', mfe: region.mfe };
Un-instrumented (pending) regions are one opaque block
// src/plugin/bridge/wireframe-walker.ts (~L303)
if (region.pending) {
// ...emits the whole mount as ONE opaque block (empty anchor at ~L265)
return wfChildren(mount).length > 0 ? { els: [mount], truncated: false } : { els: [], truncated: false };
}
Proposed fix
-
Let explode operate on anchorless blocks. Relax the explodeBlock() /
can-explode gates so a block with anchor.file === '' can still be exploded
by re-resolving its live element from its session eid (and/or originalRect
geometry) instead of findTemplateGroup(file, line, …). Children captured
this way carry empty anchors — consistent with the existing "anchorless blocks
are filtered from the apply snapshot" behavior (CHANGELOG 0.4.2). This keeps
such regions useful for layout wireframing even though they can't drive
source codegen.
-
Geometric decomposition for pending regions. In wfDiscoverRegion(),
run the same wfRegionUnwrap + wfContentKids passes over a pending
region's live DOM (producing anchorless sub-blocks) instead of returning the
mount as one block.
-
Always give feedback. If a block truly cannot be decomposed, set
error.value (e.g. "This block isn't mapped to source (it's rendered by an
un-instrumented component) — it can still be moved/resized as one block.")
instead of returning false silently, and disable/hide the explode affordance
for those blocks so the button never looks clickable when it isn't.
Why this can't be fixed downstream (host/gateway side)
The host can instrument MFEs it builds (serve them from a dev server running the
Annotask plugin). It cannot instrument the third-party / pre-built bundles
those MFEs render from (shared UI-primitive dist, node_modules components) —
Annotask transforms source JSX, not shipped bundles. So anchorless blocks are
unavoidable in any real app that consumes a component library, and the tool needs
to handle them.
Acceptance criteria
- Double-click explode works on anchorless captured blocks (geometric children),
or — if intentionally unsupported — the UI clearly explains why and the
affordance is disabled (never a no-op click).
- A
pending MFE region is captured as multiple geometric sub-blocks, not one
opaque box.
- Anchored-block, instrumented-MFE, and non-MFE capture/explode output is
unchanged (byte-identical legacy paths preserved).
Wireframe: anchorless captured blocks can't be exploded and fail silently
Summary
In the snapshot Wireframe tool, a captured block that has no source anchor
(
anchor.file === '') cannot be broken down: double-clicking "Explode intochild blocks" does nothing — no decomposition, and no feedback. To the user
the MFE/region "is a single image that cannot be broken down."
A block ends up anchorless in two common situations, and both happen with
ordinary, fully-instrumented MFEs — not just exotic setups:
Instrumented MFE whose content is rendered by un-instrumented components.
Annotask transforms an MFE's own JSX, but not code imported from a
pre-built bundle (a shared component-library
dist, anode_modulespackage, etc.). DOM produced by those components carries no
data-annotask-*attributes. When a captured region block lands on such a subtree,
wfResolveMfeAnchor()honestly resolves it to{ file: '' }— and explodethen refuses it.
Un-instrumented MFE region (
pending). A framework mount(
single-spa-application:*, qiankun wrapper,<application>) with nodata-annotask-mfedescendant (production bundle, third-party remote, or anasync bundle that hasn't mounted yet) is emitted as one opaque block with an
empty anchor.
The geometry and pixels of these blocks are fully present in the live DOM — only
the source mapping is missing. The block is therefore decomposable/explodable
in principle; the tool just declines because it keys explode off
anchor.file.annotask@0.4.3(and 0.4.2, where MFE-aware capture shipped)src/shell/composables/useWireframeMode.ts,src/plugin/bridge/wireframe-walker.tsSteps to reproduce
ships as a pre-built bundle (so its rendered DOM has no
data-annotask-*),e.g. a React MFE rendering
@your-org/ui-primitivesaliased to.../primitives/dist/index.js.Expected
The block decomposes into its child blocks (geometric is fine when there is no
source anchor), so the region can be wireframed — or, if explode is genuinely not
possible, the UI says why.
Actual
Nothing happens.
explodeBlock()returns early and silently.Root cause
Explode is gated on
anchor.file, and fails silentlyThe same gate is on the hover "can this explode?" probe, so the block is never
even highlighted as drillable:
The explode button, however, is still rendered for any
kind === 'captured' && !shellblock (WireframeCanvas.vue), so the user clicksit and gets no response.
Why instrumented MFEs still produce anchorless blocks
wfResolveMfeAnchor()correctly returns an empty anchor when an element'snearest instrumented ancestor is host glue and neither it nor its descendants
carry the region's MFE stamp — which is exactly what happens for DOM rendered by
un-instrumented shared/library/
distcomponents:Un-instrumented (
pending) regions are one opaque blockProposed fix
Let explode operate on anchorless blocks. Relax the
explodeBlock()/can-explode gates so a block with
anchor.file === ''can still be explodedby re-resolving its live element from its session
eid(and/ororiginalRectgeometry) instead of
findTemplateGroup(file, line, …). Children capturedthis way carry empty anchors — consistent with the existing "anchorless blocks
are filtered from the apply snapshot" behavior (CHANGELOG 0.4.2). This keeps
such regions useful for layout wireframing even though they can't drive
source codegen.
Geometric decomposition for
pendingregions. InwfDiscoverRegion(),run the same
wfRegionUnwrap+wfContentKidspasses over apendingregion's live DOM (producing anchorless sub-blocks) instead of returning the
mount as one block.
Always give feedback. If a block truly cannot be decomposed, set
error.value(e.g. "This block isn't mapped to source (it's rendered by anun-instrumented component) — it can still be moved/resized as one block.")
instead of returning
falsesilently, and disable/hide the explode affordancefor those blocks so the button never looks clickable when it isn't.
Why this can't be fixed downstream (host/gateway side)
The host can instrument MFEs it builds (serve them from a dev server running the
Annotask plugin). It cannot instrument the third-party / pre-built bundles
those MFEs render from (shared UI-primitive
dist,node_modulescomponents) —Annotask transforms source JSX, not shipped bundles. So anchorless blocks are
unavoidable in any real app that consumes a component library, and the tool needs
to handle them.
Acceptance criteria
or — if intentionally unsupported — the UI clearly explains why and the
affordance is disabled (never a no-op click).
pendingMFE region is captured as multiple geometric sub-blocks, not oneopaque box.
unchanged (byte-identical legacy paths preserved).