11import React from 'react' ;
22
3+ import { AnimatedTokenTrain } from './AnimatedTokenFlow' ;
34import { EmojiImage } from './ActorNodes' ;
45import { EMOJI , type EmojiAsset } from './emojiAssets' ;
56import {
89 ModelCallFrame ,
910} from './ModelCallFrame' ;
1011import { TILE_GRID } from './diagramTileLayout' ;
12+ import { seededTokenTrain } from './TokenTrainSequence' ;
1113import styles from './AgentWorkStabilityDiagram.module.css' ;
1214
1315type Tone = 'violet' | 'indigo' | 'cyan' | 'warning' | 'success' ;
@@ -34,6 +36,25 @@ const MOBILE_FRAME = { x: 16, y: 160, width: 308, height: 352 };
3436const DESKTOP_TAB_WIDTH = 216 ;
3537const MOBILE_TAB_WIDTH = 212 ;
3638const PRESSURE_ARROW_GRID = TILE_GRID ;
39+ const STABILIZER_HEIGHT = 24 ;
40+ const WORKING_CONTEXT_OFFSET = 80 ;
41+ const EVIDENCE_OFFSET = { desktop : 240 , mobile : 272 } as const ;
42+ const LOOP_STAGE_HEIGHT = 32 ;
43+ const LOOP_STAGE_STEP = 40 ;
44+ const LOOP_VISUAL_HEIGHT = LOOP_STAGE_STEP * 2 + LOOP_STAGE_HEIGHT ;
45+ const CONTROLLED_RUN_DURATION = '9.6s' ;
46+ const CONTROLLED_RUN_TIMING = {
47+ cycleMs : 9600 ,
48+ travelMs : 7600 ,
49+ fadeMs : 900 ,
50+ repeat : 'loop' ,
51+ } as const ;
52+ const CONTROLLED_RUN_STAGGER = { mode : 'pathSpacing' , spacingPx : 32 } as const ;
53+ const CONTROLLED_RUN_TOKENS = seededTokenTrain ( 'agent-work-stability-loop' , 4 ) ;
54+
55+ // Motion spec — A token train repeats Plan → Act → Observe → Verify on the actual loop
56+ // connectors. Stabilizer pings mark their control points; all moving elements disappear
57+ // under reduced motion, where this complete static diagram is canonical.
3758
3859type WorkFrameGeometry = {
3960 frame : ModelCallFrameBounds ;
@@ -294,54 +315,160 @@ function Stabilizers({
294315 width = { cardWidth }
295316 label = "EXPLICIT BOUNDARIES"
296317 tone = "cyan"
318+ motion = "boundaries"
297319 icon = { EMOJI . compass }
298320 />
299321 < Stabilizer
300322 x = { cardX }
301- y = { y + 80 }
323+ y = { y + WORKING_CONTEXT_OFFSET }
302324 width = { cardWidth }
303325 label = "WORKING CONTEXT"
304326 tone = "indigo"
327+ motion = "context"
305328 icon = { EMOJI . books }
306329 />
307330 < Stabilizer
308331 x = { cardX }
309- y = { compact ? y + 272 : y + 240 }
332+ y = { evidenceStabilizerY ( y , compact ) }
310333 width = { cardWidth }
311334 label = "INDEPENDENT EVIDENCE"
312335 tone = "success"
336+ motion = "evidence"
313337 icon = { EMOJI . check }
314338 />
315339 </ >
316340 ) ;
317341}
318342
343+ type StabilizerMotion = 'boundaries' | 'context' | 'evidence' ;
344+
345+ type StabilizerMotionTiming = {
346+ outlineTimes : string ;
347+ outlineValues : string ;
348+ pingTimes : string ;
349+ } ;
350+
351+ const STABILIZER_MOTION : Record < StabilizerMotion , StabilizerMotionTiming > = {
352+ boundaries : {
353+ outlineTimes : '0;0.02;0.12;0.16;1' ,
354+ outlineValues : '0;1;1;0;0' ,
355+ pingTimes : '0;0.02;0.06;1' ,
356+ } ,
357+ context : {
358+ outlineTimes : '0;0.22;0.27;0.61;0.66;1' ,
359+ outlineValues : '0;0;1;1;0;0' ,
360+ pingTimes : '0;0.22;0.26;1' ,
361+ } ,
362+ evidence : {
363+ outlineTimes : '0;0.72;0.77;0.85;0.90;1' ,
364+ outlineValues : '0;0;1;1;0;0' ,
365+ pingTimes : '0;0.72;0.76;1' ,
366+ } ,
367+ } ;
368+
319369function Stabilizer ( {
320370 x,
321371 y,
322372 width,
323373 label,
324374 tone,
375+ motion,
325376 icon,
326377} : {
327378 x : number ;
328379 y : number ;
329380 width : number ;
330381 label : string ;
331382 tone : Tone ;
383+ motion : StabilizerMotion ;
332384 icon : EmojiAsset ;
333385} ) {
334386 return (
335387 < g className = { styles . stabilizer } data-tone = { tone } >
336- < rect x = { x } y = { y } width = { width } height = { 24 } rx = { 0 } />
388+ < rect x = { x } y = { y } width = { width } height = { STABILIZER_HEIGHT } rx = { 0 } />
337389 < EmojiImage asset = { icon } x = { x + 8 } y = { y + 4 } size = { 16 } />
338390 < text x = { x + 32 } y = { y + 16 } textAnchor = "start" >
339391 { label }
340392 </ text >
393+ < StabilizerSignal x = { x } y = { y } width = { width } tone = { tone } motion = { motion } />
394+ </ g >
395+ ) ;
396+ }
397+
398+ function StabilizerSignal ( {
399+ x,
400+ y,
401+ width,
402+ tone,
403+ motion,
404+ } : {
405+ x : number ;
406+ y : number ;
407+ width : number ;
408+ tone : Tone ;
409+ motion : StabilizerMotion ;
410+ } ) {
411+ const timing = STABILIZER_MOTION [ motion ] ;
412+ return (
413+ < g
414+ className = { styles . stabilizerMotion }
415+ data-tone = { tone }
416+ transform = { `translate(${ x + width / 2 } ${ y + 12 } )` }
417+ >
418+ < rect
419+ className = { styles . stabilizerOutline }
420+ x = { - width / 2 - 4 }
421+ y = { - 16 }
422+ width = { width + 8 }
423+ height = { 32 }
424+ opacity = "0"
425+ >
426+ < animate
427+ attributeName = "opacity"
428+ dur = { CONTROLLED_RUN_DURATION }
429+ repeatCount = "indefinite"
430+ values = { timing . outlineValues }
431+ keyTimes = { timing . outlineTimes }
432+ />
433+ </ rect >
434+ < rect
435+ className = { styles . stabilizerPing }
436+ x = { - width / 2 - 4 }
437+ y = { - 16 }
438+ width = { width + 8 }
439+ height = { 32 }
440+ opacity = "0"
441+ >
442+ < animate
443+ attributeName = "opacity"
444+ dur = { CONTROLLED_RUN_DURATION }
445+ repeatCount = "indefinite"
446+ values = "0;0.8;0;0"
447+ keyTimes = { timing . pingTimes }
448+ />
449+ < animateTransform
450+ attributeName = "transform"
451+ type = "scale"
452+ dur = { CONTROLLED_RUN_DURATION }
453+ repeatCount = "indefinite"
454+ values = "1;1;1.12;1.12"
455+ keyTimes = { timing . pingTimes }
456+ />
457+ </ rect >
341458 </ g >
342459 ) ;
343460}
344461
462+ function evidenceStabilizerY ( y : number , compact : boolean ) {
463+ return y + EVIDENCE_OFFSET [ compact ? 'mobile' : 'desktop' ] ;
464+ }
465+
466+ function centeredLoopTop ( y : number , compact : boolean ) {
467+ const contextBottom = y + WORKING_CONTEXT_OFFSET + STABILIZER_HEIGHT ;
468+ const evidenceTop = evidenceStabilizerY ( y , compact ) ;
469+ return contextBottom + ( evidenceTop - contextBottom - LOOP_VISUAL_HEIGHT ) / 2 ;
470+ }
471+
345472function AgentLoop ( {
346473 x,
347474 y,
@@ -354,12 +481,21 @@ function AgentLoop({
354481 compact : boolean ;
355482} ) {
356483 const center = x + width / 2 ;
357- const top = compact ? y + 136 : y + 120 ;
358- const side = top + 40 ;
359- const bottom = top + 80 ;
484+ const top = centeredLoopTop ( y , compact ) ;
485+ const side = top + LOOP_STAGE_STEP ;
486+ const bottom = top + LOOP_STAGE_STEP * 2 ;
360487 const sideWidth = compact ? 80 : 96 ;
361488 const left = x + 24 ;
362489 const right = x + width - sideWidth - 24 ;
490+ const trainPath = loopTrainPath (
491+ center ,
492+ top ,
493+ side ,
494+ bottom ,
495+ left ,
496+ right ,
497+ sideWidth
498+ ) ;
363499 return (
364500 < g className = { styles . loop } >
365501 < LoopPath
@@ -374,6 +510,14 @@ function AgentLoop({
374510 < LoopPath
375511 d = { `M ${ left } ${ side + 16 } H ${ left + 16 } V ${ top + 16 } H ${ center - 48 } ` }
376512 />
513+ < AnimatedTokenTrain
514+ pathD = { trainPath }
515+ tokens = { CONTROLLED_RUN_TOKENS }
516+ timing = { CONTROLLED_RUN_TIMING }
517+ stagger = { CONTROLLED_RUN_STAGGER }
518+ size = { 16 }
519+ tone = "violet"
520+ />
377521 < LoopStage
378522 x = { center - 48 }
379523 y = { top }
@@ -404,6 +548,26 @@ function AgentLoop({
404548 ) ;
405549}
406550
551+ function loopTrainPath (
552+ center : number ,
553+ top : number ,
554+ side : number ,
555+ bottom : number ,
556+ left : number ,
557+ right : number ,
558+ sideWidth : number
559+ ) {
560+ const planY = top + 16 ;
561+ const sideY = side + 16 ;
562+ const observeY = bottom + 16 ;
563+ return [
564+ `M ${ center } ${ planY } H ${ right - 16 } V ${ sideY } H ${ right + sideWidth } ` ,
565+ `H ${ right + sideWidth - 16 } V ${ observeY } H ${ center - 48 } ` ,
566+ `H ${ left + sideWidth + 16 } V ${ sideY } H ${ left } ` ,
567+ `H ${ left + 16 } V ${ planY } H ${ center } ` ,
568+ ] . join ( ' ' ) ;
569+ }
570+
407571function LoopPath ( { d } : { d : string } ) {
408572 return < path d = { d } markerEnd = "url(#loop-arrow)" /> ;
409573}
@@ -423,7 +587,7 @@ function LoopStage({
423587} ) {
424588 return (
425589 < g className = { styles . loopStage } >
426- < rect x = { x } y = { y } width = { width } height = { 32 } rx = { 0 } />
590+ < rect x = { x } y = { y } width = { width } height = { LOOP_STAGE_HEIGHT } rx = { 0 } />
427591 < EmojiImage asset = { icon } x = { x + 8 } y = { y + 8 } size = { 16 } />
428592 < text x = { x + 30 } y = { y + 21 } textAnchor = "start" >
429593 { label }
0 commit comments