expandChild method

bool expandChild(
  1. String nodeId,
  2. String childName
)

Partially expand a specific hidden child by name.

Finds the hidden child whose label (in hwMeta) matches childName and marks it (plus its connecting hyperedges) as partially visible. Used by the search/navigate routine to reveal only the path to a target without fully expanding every level.

Returns true if a new child was revealed.

Implementation

bool expandChild(String nodeId, String childName) {
  final node = nodeMap[nodeId];
  if (node == null) {
    return false;
  }

  if (node.hiddenChildren == null || node.hiddenChildren!.isEmpty) {
    return false;
  }

  // Find the hidden child by name.
  //
  // We check both hwMeta.name (display name) AND the last segment of
  // hierarchyNodeId (instance name from the netlist). These can
  // differ for operator/primitive cells whose display name is the
  // translated operator name (e.g. "MUX") while the hierarchy uses the
  // cell instance name (e.g. "mux_0").
  String? targetChildId;
  for (final child in node.hiddenChildren!) {
    if (child.hwMeta.name == childName) {
      targetChildId = child.id;
      break;
    }
    // Fallback: match on instance name from hierarchy path.
    final hid = child.hierarchyNodeId;
    if (hid != null) {
      final lastSeg =
          hid.contains('/') ? hid.substring(hid.lastIndexOf('/') + 1) : hid;
      if (lastSeg == childName) {
        targetChildId = child.id;
        break;
      }
    }
  }
  if (targetChildId == null) {
    return false;
  }

  // If the target is already visible in the partial set, nothing to do.
  // Do NOT recompute matching hyperedges here: the parent may be in
  // blocks-only mode (edges intentionally hidden) and recalculating
  // edges would upgrade it to a wired partial expansion — defeating
  // the purpose of blocks-only mode.
  final existingChildren = node.partialChildIds ?? {};
  if (existingChildren.contains(targetChildId)) {
    return false;
  }

  // Find hyperedges that connect only visible children (including
  // the newly-revealed target) and/or the parent boundary.
  // Edges that reference hidden siblings are excluded — they would
  // create dangling wires to nodes the user hasn't expanded yet.
  final matchingHyperedgeIds = <String>{};
  final mergedChildren = <String>{...existingChildren, targetChildId};
  // Also include already-visible children (from full expansion).
  for (final child in node.children) {
    mergedChildren.add(child.id);
  }
  final hyperedges = node.hyperedges;
  if (hyperedges != null) {
    for (final h in hyperedges) {
      final allEndpointsVisible = h.sources.every(
            (s) => s.$1 == node.id || mergedChildren.contains(s.$1),
          ) &&
          h.targets.every(
            (t) => t.$1 == node.id || mergedChildren.contains(t.$1),
          );
      if (allEndpointsVisible) {
        matchingHyperedgeIds.add(h.id);
      }
    }
  }

  final existingEdges = node.partialHyperedgeIds ?? {};

  // Additive: merge into existing partial sets.
  node
    ..partialChildIds = mergedChildren
    ..partialHyperedgeIds = {...existingEdges, ...matchingHyperedgeIds};

  return true;
}