Skip to content

Wireframe: anchorless captured blocks can't be exploded and fail silently #54

Description

@kstohrer

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:

  1. 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.

  2. 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

  1. 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.
  2. Open the route in Annotask, enter Wireframe mode, and Capture.
  3. 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

  1. 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.

  2. 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.

  3. 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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions