@@ -166,7 +166,14 @@ export function parseSpecialTags(content: string): ParsedTags {
166166 * PlanSteps component renders the workflow plan steps from the plan subagent
167167 * Only renders the title, not the full plan details
168168 */
169- function PlanSteps ( { steps } : { steps : Record < string , PlanStep > } ) {
169+ function PlanSteps ( {
170+ steps,
171+ streaming = false ,
172+ } : {
173+ steps : Record < string , PlanStep >
174+ /** When true, uses smooth streaming animation for step titles */
175+ streaming ?: boolean
176+ } ) {
170177 const sortedSteps = useMemo ( ( ) => {
171178 return Object . entries ( steps )
172179 . sort ( ( [ a ] , [ b ] ) => {
@@ -192,16 +199,23 @@ function PlanSteps({ steps }: { steps: Record<string, PlanStep> }) {
192199 </ span >
193200 </ div >
194201 < div className = 'divide-y divide-[var(--border-1)]' >
195- { sortedSteps . map ( ( [ num , title ] ) => (
196- < div key = { num } className = 'flex items-start gap-2.5 px-2.5 py-2' >
197- < div className = 'flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full bg-[var(--surface-3)] font-medium font-mono text-[11px] text-[var(--text-secondary)]' >
198- { num }
199- </ div >
200- < div className = 'min-w-0 flex-1 text-[12px] text-[var(--text-secondary)] leading-5 [&_code]:px-1 [&_code]:py-0.5 [&_code]:text-[11px] [&_p]:m-0 [&_p]:leading-5' >
201- < CopilotMarkdownRenderer content = { title } />
202+ { sortedSteps . map ( ( [ num , title ] , index ) => {
203+ const isLastStep = index === sortedSteps . length - 1
204+ return (
205+ < div key = { num } className = 'flex items-start gap-2.5 px-2.5 py-2' >
206+ < div className = 'flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full bg-[var(--surface-3)] font-medium font-mono text-[11px] text-[var(--text-secondary)]' >
207+ { num }
208+ </ div >
209+ < div className = 'min-w-0 flex-1 text-[12px] text-[var(--text-secondary)] leading-5 [&_code]:px-1 [&_code]:py-0.5 [&_code]:text-[11px] [&_p]:m-0 [&_p]:leading-5' >
210+ { streaming && isLastStep ? (
211+ < SmoothStreamingText content = { title } isStreaming = { true } />
212+ ) : (
213+ < CopilotMarkdownRenderer content = { title } />
214+ ) }
215+ </ div >
202216 </ div >
203- </ div >
204- ) ) }
217+ )
218+ } ) }
205219 </ div >
206220 </ div >
207221 )
@@ -983,7 +997,7 @@ function SubAgentContent({
983997 . join ( '' )
984998 const parsed = parseSpecialTags ( allText )
985999 if ( parsed . plan && Object . keys ( parsed . plan ) . length > 0 ) {
986- return < PlanSteps steps = { parsed . plan } />
1000+ return < PlanSteps steps = { parsed . plan } streaming = { ! isThinkingDone } />
9871001 }
9881002 return null
9891003 } ) ( ) }
@@ -1032,7 +1046,7 @@ function SubAgentThinkingContent({
10321046 />
10331047 ) }
10341048 { allParsed . plan && Object . keys ( allParsed . plan ) . length > 0 && (
1035- < PlanSteps steps = { allParsed . plan } />
1049+ < PlanSteps steps = { allParsed . plan } streaming = { isStreaming } />
10361050 ) }
10371051 </ div >
10381052 )
@@ -1168,7 +1182,7 @@ function SubagentContentRenderer({
11681182 return (
11691183 < div className = 'w-full' >
11701184 { renderCollapsibleContent ( ) }
1171- { hasPlan && < PlanSteps steps = { allParsed . plan ! } /> }
1185+ { hasPlan && < PlanSteps steps = { allParsed . plan ! } streaming = { isStreaming } /> }
11721186 </ div >
11731187 )
11741188 }
@@ -1364,7 +1378,7 @@ function WorkflowEditSummary({ toolCall }: { toolCall: CopilotToolCall }) {
13641378 subBlocks . push ( { id : 'else' , title : 'else' , value : '' , isPassword : false } )
13651379 }
13661380 } else {
1367- // Filter visible subblocks from config (same logic as canvas)
1381+ // Filter visible subblocks from config (same logic as canvas preview )
13681382 const visibleSubBlocks =
13691383 blockConfig ?. subBlocks ?. filter ( ( sb ) => {
13701384 // Skip hidden subblocks
@@ -1377,12 +1391,19 @@ function WorkflowEditSummary({ toolCall }: { toolCall: CopilotToolCall }) {
13771391 return true
13781392 } ) ?? [ ]
13791393
1380- // Add subblocks that are visible in config, in config order
1394+ // Track seen ids to dedupe (same pattern as canvas preview using id as key)
1395+ const seenIds = new Set < string > ( )
1396+
1397+ // Add subblocks that are visible in config, in config order (first config per id wins)
13811398 for ( const subBlockConfig of visibleSubBlocks ) {
1399+ // Skip if we've already added this id (handles configs with same id but different conditions)
1400+ if ( seenIds . has ( subBlockConfig . id ) ) continue
1401+
13821402 if ( subBlockConfig . id in inputs ) {
13831403 const value = inputs [ subBlockConfig . id ]
13841404 // Skip empty values and connections
13851405 if ( value === null || value === undefined || value === '' ) continue
1406+ seenIds . add ( subBlockConfig . id )
13861407 subBlocks . push ( {
13871408 id : subBlockConfig . id ,
13881409 title : subBlockConfig . title ?? subBlockConfig . id ,
0 commit comments