refactor(stammbaum): carry child id on the connector centre object (#703)

The shared parent-pair child loop read group.childIds[i] while iterating
the filtered childCenters, so a child without a position would desync the
id from the centre — and that index now also drives the active-connector
lookup. Ride the id on the mapped {id,x,y} centre so the two never drift;
a positionless child drops out of both together.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-05-31 19:17:34 +02:00
parent da3067150d
commit 7cc2ddc6ad

View File

@@ -106,8 +106,11 @@ const parentLinks = $derived.by<ParentLinks>(() => {
{@const aCenter = nodeCenter(group.parentA)} {@const aCenter = nodeCenter(group.parentA)}
{@const bCenter = nodeCenter(group.parentB)} {@const bCenter = nodeCenter(group.parentB)}
{@const childCenters = group.childIds {@const childCenters = group.childIds
.map((id) => nodeCenter(id)) .map((id) => {
.filter((c): c is { x: number; y: number } => c !== null)} const c = nodeCenter(id);
return c ? { id, x: c.x, y: c.y } : null;
})
.filter((c): c is { id: string; x: number; y: number } => c !== null)}
{#if aCenter && bCenter && childCenters.length > 0} {#if aCenter && bCenter && childCenters.length > 0}
{@const midX = (aCenter.x + bCenter.x) / 2} {@const midX = (aCenter.x + bCenter.x) / 2}
{@const parentBottomY = aCenter.y + NODE_H / 2} {@const parentBottomY = aCenter.y + NODE_H / 2}
@@ -138,13 +141,14 @@ const parentLinks = $derived.by<ParentLinks>(() => {
/> />
{/if} {/if}
</g> </g>
{#each childCenters as cc, i (group.childIds[i])} {#each childCenters as cc (cc.id)}
<!-- Each vertical joins the parent pair to one child: active only when <!-- Each vertical joins the parent pair to one child: active only when
both parents and that child are active, so the same bar can stay lit both parents and that child are active, so the same bar can stay lit
to the lineage child while dimming to a collateral sibling. --> to the lineage child while dimming to a collateral sibling. The child
id rides on the centre object, so it never desyncs from the filtered
centres (a child without a position drops out of both together). -->
{@const childActive = {@const childActive =
isConnectorActive(group.parentA, group.childIds[i]) && isConnectorActive(group.parentA, cc.id) && isConnectorActive(group.parentB, cc.id)}
isConnectorActive(group.parentB, group.childIds[i])}
<g class="lineage-fade" opacity={connectorOpacity(childActive)}> <g class="lineage-fade" opacity={connectorOpacity(childActive)}>
<line <line
x1={cc.x} x1={cc.x}