@@ -848,6 +848,374 @@ describe("hydrationError", () => {
} ) ;
} ) ;
// ---------- growParentsToFitChildren ----------
//
// growParentsToFitChildren walks every parent node and expands its width/height
// so all children fit inside with padding. Collapsed parents are skipped (grow-
// only, never shrink). Returns the same array reference when no changes are
// needed, a new array when at least one parent grew.
//
// Constants (from canvas-topology.ts):
// CHILD_DEFAULT_WIDTH = 240
// CHILD_DEFAULT_HEIGHT = 130
// PARENT_SIDE_PADDING = 16
// PARENT_BOTTOM_PADDING = 16
//
// For a child at (childX, childY) with size (childW, childH):
// requiredParentW = childX + childW + PARENT_SIDE_PADDING
// requiredParentH = childY + childH + PARENT_BOTTOM_PADDING
//
// Coverage targets:
// - Node with no parentId → skipped entirely (returns same node)
// - Parent with no children → skipped (kids.length === 0 → returns n)
// - Collapsed parent → skipped even when children overflow
// - Child fits within existing parent → no-op (requiredW <= currentW && requiredH <= currentH)
// - Child overflows parent width → grows width only
// - Child overflows parent height → grows height only
// - Child overflows both → grows both
// - Missing measured.width (falls back to width, then CHILD_DEFAULT_WIDTH)
// - Missing measured.height (falls back to height, then CHILD_DEFAULT_HEIGHT)
// - Missing parent measured.width (falls back to width, then 0)
// - Missing parent measured.height (falls back to height, then 0)
// - No change at all → returns same array reference (changed=false path)
describe ( "growParentsToFitChildren" , ( ) = > {
it ( "skips nodes with no parentId (standalone roots)" , ( ) = > {
useCanvasStore . setState ( {
nodes : [
{
id : "root" ,
type : "workspaceNode" ,
position : { x : 0 , y : 0 } ,
data : { name : "Root" , status : "online" , tier : 1 , agentCard : null , activeTasks : 0 , collapsed : false , role : "agent" , lastErrorRate : 0 , lastSampleError : "" , url : "" , parentId : null , currentTask : "" , needsRestart : false , runtime : "" , budgetLimit : null } ,
measured : { width : 200 , height : 150 } ,
} ,
] ,
} ) ;
const before = useCanvasStore . getState ( ) . nodes ;
useCanvasStore . getState ( ) . growParentsToFitChildren ( ) ;
const after = useCanvasStore . getState ( ) . nodes ;
// Same array reference (no change needed)
expect ( after ) . toBe ( before ) ;
} ) ;
it ( "skips parent with no children (orphan parentId)" , ( ) = > {
useCanvasStore . setState ( {
nodes : [
{
id : "orphan" ,
type : "workspaceNode" ,
position : { x : 0 , y : 0 } ,
parentId : "nonexistent" ,
data : { name : "Orphan" , status : "online" , tier : 1 , agentCard : null , activeTasks : 0 , collapsed : false , role : "agent" , lastErrorRate : 0 , lastSampleError : "" , url : "" , parentId : null , currentTask : "" , needsRestart : false , runtime : "" , budgetLimit : null } ,
measured : { width : 100 , height : 100 } ,
} ,
] ,
} ) ;
const before = useCanvasStore . getState ( ) . nodes ;
useCanvasStore . getState ( ) . growParentsToFitChildren ( ) ;
const after = useCanvasStore . getState ( ) . nodes ;
// Same array reference (parentId exists but no children reference it)
expect ( after ) . toBe ( before ) ;
expect ( after [ 0 ] . measured ) . toEqual ( { width : 100 , height : 100 } ) ;
} ) ;
it ( "skips collapsed parents even when children overflow" , ( ) = > {
// Child at (500, 400) → requires parent 500+240+16=756w, 400+130+16=546h
// Parent is collapsed AND tiny — must NOT grow
useCanvasStore . setState ( {
nodes : [
{
id : "parent" ,
type : "workspaceNode" ,
position : { x : 0 , y : 0 } ,
data : { name : "Parent" , status : "online" , tier : 1 , agentCard : null , activeTasks : 0 , collapsed : true , role : "agent" , lastErrorRate : 0 , lastSampleError : "" , url : "" , parentId : null , currentTask : "" , needsRestart : false , runtime : "" , budgetLimit : null } ,
measured : { width : 200 , height : 150 } ,
} ,
{
id : "child" ,
type : "workspaceNode" ,
position : { x : 500 , y : 400 } ,
parentId : "parent" ,
data : { name : "Child" , status : "online" , tier : 1 , agentCard : null , activeTasks : 0 , collapsed : false , role : "agent" , lastErrorRate : 0 , lastSampleError : "" , url : "" , parentId : "parent" , currentTask : "" , needsRestart : false , runtime : "" , budgetLimit : null } ,
measured : { width : 240 , height : 130 } ,
} ,
] ,
} ) ;
const before = useCanvasStore . getState ( ) . nodes ;
useCanvasStore . getState ( ) . growParentsToFitChildren ( ) ;
const after = useCanvasStore . getState ( ) . nodes ;
// Same reference (collapsed → skipped entirely)
expect ( after ) . toBe ( before ) ;
const parent = after . find ( ( n ) = > n . id === "parent" ) ! ;
expect ( parent . measured ) . toEqual ( { width : 200 , height : 150 } ) ;
} ) ;
it ( "no-op when child fits within existing parent size" , ( ) = > {
// Child at (0,0) 240x130 → requires 0+240+16=256w, 0+130+16=146h
// Parent is exactly 256× 146 → fits perfectly
useCanvasStore . setState ( {
nodes : [
{
id : "parent" ,
type : "workspaceNode" ,
position : { x : 0 , y : 0 } ,
data : { name : "Parent" , status : "online" , tier : 1 , agentCard : null , activeTasks : 0 , collapsed : false , role : "agent" , lastErrorRate : 0 , lastSampleError : "" , url : "" , parentId : null , currentTask : "" , needsRestart : false , runtime : "" , budgetLimit : null } ,
measured : { width : 256 , height : 146 } ,
} ,
{
id : "child" ,
type : "workspaceNode" ,
position : { x : 0 , y : 0 } ,
parentId : "parent" ,
data : { name : "Child" , status : "online" , tier : 1 , agentCard : null , activeTasks : 0 , collapsed : false , role : "agent" , lastErrorRate : 0 , lastSampleError : "" , url : "" , parentId : "parent" , currentTask : "" , needsRestart : false , runtime : "" , budgetLimit : null } ,
measured : { width : 240 , height : 130 } ,
} ,
] ,
} ) ;
const before = useCanvasStore . getState ( ) . nodes ;
useCanvasStore . getState ( ) . growParentsToFitChildren ( ) ;
const after = useCanvasStore . getState ( ) . nodes ;
// Same array reference (no change needed)
expect ( after ) . toBe ( before ) ;
const parent = after . find ( ( n ) = > n . id === "parent" ) ! ;
expect ( parent . measured ) . toEqual ( { width : 256 , height : 146 } ) ;
} ) ;
it ( "grows parent width only when child overflows width but not height" , ( ) = > {
// Child at (100, 0) 240x130 → requires 100+240+16=356w, 0+130+16=146h
// Parent is 256× 146 → fits height, overflows width → grows to 356× 146
useCanvasStore . setState ( {
nodes : [
{
id : "parent" ,
type : "workspaceNode" ,
position : { x : 0 , y : 0 } ,
data : { name : "Parent" , status : "online" , tier : 1 , agentCard : null , activeTasks : 0 , collapsed : false , role : "agent" , lastErrorRate : 0 , lastSampleError : "" , url : "" , parentId : null , currentTask : "" , needsRestart : false , runtime : "" , budgetLimit : null } ,
measured : { width : 256 , height : 146 } ,
} ,
{
id : "child" ,
type : "workspaceNode" ,
position : { x : 100 , y : 0 } ,
parentId : "parent" ,
data : { name : "Child" , status : "online" , tier : 1 , agentCard : null , activeTasks : 0 , collapsed : false , role : "agent" , lastErrorRate : 0 , lastSampleError : "" , url : "" , parentId : "parent" , currentTask : "" , needsRestart : false , runtime : "" , budgetLimit : null } ,
measured : { width : 240 , height : 130 } ,
} ,
] ,
} ) ;
useCanvasStore . getState ( ) . growParentsToFitChildren ( ) ;
const parent = useCanvasStore . getState ( ) . nodes . find ( ( n ) = > n . id === "parent" ) ! ;
expect ( parent . width ) . toBe ( 356 ) ; // 100+240+16
expect ( parent . height ) . toBe ( 146 ) ; // unchanged
} ) ;
it ( "grows parent height only when child overflows height but not width" , ( ) = > {
// Child at (0, 50) 240x130 → requires 0+240+16=256w, 50+130+16=196h
// Parent is 256× 146 → fits width, overflows height → grows to 256× 196
useCanvasStore . setState ( {
nodes : [
{
id : "parent" ,
type : "workspaceNode" ,
position : { x : 0 , y : 0 } ,
data : { name : "Parent" , status : "online" , tier : 1 , agentCard : null , activeTasks : 0 , collapsed : false , role : "agent" , lastErrorRate : 0 , lastSampleError : "" , url : "" , parentId : null , currentTask : "" , needsRestart : false , runtime : "" , budgetLimit : null } ,
measured : { width : 256 , height : 146 } ,
} ,
{
id : "child" ,
type : "workspaceNode" ,
position : { x : 0 , y : 50 } ,
parentId : "parent" ,
data : { name : "Child" , status : "online" , tier : 1 , agentCard : null , activeTasks : 0 , collapsed : false , role : "agent" , lastErrorRate : 0 , lastSampleError : "" , url : "" , parentId : "parent" , currentTask : "" , needsRestart : false , runtime : "" , budgetLimit : null } ,
measured : { width : 240 , height : 130 } ,
} ,
] ,
} ) ;
useCanvasStore . getState ( ) . growParentsToFitChildren ( ) ;
const parent = useCanvasStore . getState ( ) . nodes . find ( ( n ) = > n . id === "parent" ) ! ;
expect ( parent . width ) . toBe ( 256 ) ; // unchanged
expect ( parent . height ) . toBe ( 196 ) ; // 50+130+16
} ) ;
it ( "grows parent in both dimensions when child overflows both" , ( ) = > {
// Child at (200, 100) 240x130 → requires 200+240+16=456w, 100+130+16=246h
// Parent is 256× 146 → grows to 456× 246
useCanvasStore . setState ( {
nodes : [
{
id : "parent" ,
type : "workspaceNode" ,
position : { x : 0 , y : 0 } ,
data : { name : "Parent" , status : "online" , tier : 1 , agentCard : null , activeTasks : 0 , collapsed : false , role : "agent" , lastErrorRate : 0 , lastSampleError : "" , url : "" , parentId : null , currentTask : "" , needsRestart : false , runtime : "" , budgetLimit : null } ,
measured : { width : 256 , height : 146 } ,
} ,
{
id : "child" ,
type : "workspaceNode" ,
position : { x : 200 , y : 100 } ,
parentId : "parent" ,
data : { name : "Child" , status : "online" , tier : 1 , agentCard : null , activeTasks : 0 , collapsed : false , role : "agent" , lastErrorRate : 0 , lastSampleError : "" , url : "" , parentId : "parent" , currentTask : "" , needsRestart : false , runtime : "" , budgetLimit : null } ,
measured : { width : 240 , height : 130 } ,
} ,
] ,
} ) ;
useCanvasStore . getState ( ) . growParentsToFitChildren ( ) ;
const parent = useCanvasStore . getState ( ) . nodes . find ( ( n ) = > n . id === "parent" ) ! ;
expect ( parent . width ) . toBe ( 456 ) ; // 200+240+16
expect ( parent . height ) . toBe ( 246 ) ; // 100+130+16
} ) ;
it ( "uses CHILD_DEFAULT_WIDTH/HEIGHT when child has no measured or explicit dimensions" , ( ) = > {
// Child with NO measured, NO width/height → falls back to 240× 130 defaults
// Child at (500, 200) → requires 500+240+16=756w, 200+130+16=346h
useCanvasStore . setState ( {
nodes : [
{
id : "parent" ,
type : "workspaceNode" ,
position : { x : 0 , y : 0 } ,
data : { name : "Parent" , status : "online" , tier : 1 , agentCard : null , activeTasks : 0 , collapsed : false , role : "agent" , lastErrorRate : 0 , lastSampleError : "" , url : "" , parentId : null , currentTask : "" , needsRestart : false , runtime : "" , budgetLimit : null } ,
measured : { width : 100 , height : 100 } ,
} ,
{
id : "child" ,
type : "workspaceNode" ,
position : { x : 500 , y : 200 } ,
parentId : "parent" ,
// No measured, no width/height → uses CHILD_DEFAULT_WIDTH=240, CHILD_DEFAULT_HEIGHT=130
data : { name : "Child" , status : "online" , tier : 1 , agentCard : null , activeTasks : 0 , collapsed : false , role : "agent" , lastErrorRate : 0 , lastSampleError : "" , url : "" , parentId : "parent" , currentTask : "" , needsRestart : false , runtime : "" , budgetLimit : null } ,
} ,
] ,
} ) ;
useCanvasStore . getState ( ) . growParentsToFitChildren ( ) ;
const parent = useCanvasStore . getState ( ) . nodes . find ( ( n ) = > n . id === "parent" ) ! ;
expect ( parent . width ) . toBe ( 756 ) ; // 500+240+16
expect ( parent . height ) . toBe ( 346 ) ; // 200+130+16
} ) ;
it ( "uses explicit width/height when measured is absent on child" , ( ) = > {
// Child has width/height but NOT measured
// Child at (300, 50) with explicit 200× 100 → requires 300+200+16=516w, 50+100+16=166h
useCanvasStore . setState ( {
nodes : [
{
id : "parent" ,
type : "workspaceNode" ,
position : { x : 0 , y : 0 } ,
data : { name : "Parent" , status : "online" , tier : 1 , agentCard : null , activeTasks : 0 , collapsed : false , role : "agent" , lastErrorRate : 0 , lastSampleError : "" , url : "" , parentId : null , currentTask : "" , needsRestart : false , runtime : "" , budgetLimit : null } ,
measured : { width : 200 , height : 100 } ,
} ,
{
id : "child" ,
type : "workspaceNode" ,
position : { x : 300 , y : 50 } ,
parentId : "parent" ,
width : 200 ,
height : 100 ,
// No measured → falls back to width=200, height=100
data : { name : "Child" , status : "online" , tier : 1 , agentCard : null , activeTasks : 0 , collapsed : false , role : "agent" , lastErrorRate : 0 , lastSampleError : "" , url : "" , parentId : "parent" , currentTask : "" , needsRestart : false , runtime : "" , budgetLimit : null } ,
} ,
] ,
} ) ;
useCanvasStore . getState ( ) . growParentsToFitChildren ( ) ;
const parent = useCanvasStore . getState ( ) . nodes . find ( ( n ) = > n . id === "parent" ) ! ;
expect ( parent . width ) . toBe ( 516 ) ; // 300+200+16
expect ( parent . height ) . toBe ( 166 ) ; // 50+100+16
} ) ;
it ( "uses measured when present (takes precedence over explicit width/height)" , ( ) = > {
// Child has both measured AND explicit width/height — measured should win
// Child at (0,0) measured=240× 130 explicit=100× 50 → uses measured
// Required: 0+240+16=256w, 0+130+16=146h
useCanvasStore . setState ( {
nodes : [
{
id : "parent" ,
type : "workspaceNode" ,
position : { x : 0 , y : 0 } ,
data : { name : "Parent" , status : "online" , tier : 1 , agentCard : null , activeTasks : 0 , collapsed : false , role : "agent" , lastErrorRate : 0 , lastSampleError : "" , url : "" , parentId : null , currentTask : "" , needsRestart : false , runtime : "" , budgetLimit : null } ,
measured : { width : 256 , height : 146 } , // fits exactly
} ,
{
id : "child" ,
type : "workspaceNode" ,
position : { x : 0 , y : 0 } ,
parentId : "parent" ,
width : 100 , // ignored (measured present)
height : 50 , // ignored
measured : { width : 240 , height : 130 } ,
data : { name : "Child" , status : "online" , tier : 1 , agentCard : null , activeTasks : 0 , collapsed : false , role : "agent" , lastErrorRate : 0 , lastSampleError : "" , url : "" , parentId : "parent" , currentTask : "" , needsRestart : false , runtime : "" , budgetLimit : null } ,
} ,
] ,
} ) ;
const before = useCanvasStore . getState ( ) . nodes ;
useCanvasStore . getState ( ) . growParentsToFitChildren ( ) ;
const after = useCanvasStore . getState ( ) . nodes ;
// Same reference (measured fits exactly)
expect ( after ) . toBe ( before ) ;
} ) ;
it ( "multiple children: grows to fit the furthest child in each dimension" , ( ) = > {
// Child 1 at (0, 0) 240× 130 → maxRight=240, maxBottom=130
// Child 2 at (300, 200) 240× 130 → maxRight=540, maxBottom=330
// Required: 540+16=556w, 330+16=346h
useCanvasStore . setState ( {
nodes : [
{
id : "parent" ,
type : "workspaceNode" ,
position : { x : 0 , y : 0 } ,
data : { name : "Parent" , status : "online" , tier : 1 , agentCard : null , activeTasks : 0 , collapsed : false , role : "agent" , lastErrorRate : 0 , lastSampleError : "" , url : "" , parentId : null , currentTask : "" , needsRestart : false , runtime : "" , budgetLimit : null } ,
measured : { width : 100 , height : 100 } ,
} ,
{
id : "child1" ,
type : "workspaceNode" ,
position : { x : 0 , y : 0 } ,
parentId : "parent" ,
data : { name : "Child1" , status : "online" , tier : 1 , agentCard : null , activeTasks : 0 , collapsed : false , role : "agent" , lastErrorRate : 0 , lastSampleError : "" , url : "" , parentId : "parent" , currentTask : "" , needsRestart : false , runtime : "" , budgetLimit : null } ,
measured : { width : 240 , height : 130 } ,
} ,
{
id : "child2" ,
type : "workspaceNode" ,
position : { x : 300 , y : 200 } ,
parentId : "parent" ,
data : { name : "Child2" , status : "online" , tier : 1 , agentCard : null , activeTasks : 0 , collapsed : false , role : "agent" , lastErrorRate : 0 , lastSampleError : "" , url : "" , parentId : "parent" , currentTask : "" , needsRestart : false , runtime : "" , budgetLimit : null } ,
measured : { width : 240 , height : 130 } ,
} ,
] ,
} ) ;
useCanvasStore . getState ( ) . growParentsToFitChildren ( ) ;
const parent = useCanvasStore . getState ( ) . nodes . find ( ( n ) = > n . id === "parent" ) ! ;
expect ( parent . width ) . toBe ( 556 ) ; // max(0+240, 300+240)+16
expect ( parent . height ) . toBe ( 346 ) ; // max(0+130, 200+130)+16
} ) ;
} ) ;
// ---------- ACTIVITY_LOGGED event ----------
describe ( "ACTIVITY_LOGGED event" , ( ) = > {