| ... | @@ -123,16 +123,28 @@ export function AgentActivity( | ... | @@ -123,16 +123,28 @@ export function AgentActivity( |
| 123 | ? phaseTint(failedRow.phase) | 123 | ? phaseTint(failedRow.phase) |
| 124 | : tint; | 124 | : tint; |
| 125 | | 125 | |
| | 126 | // With nothing active the aggregate only carries recently finished work, so |
| | 127 | // "0 active agents" (and a lone "0" in the expanded island) read as broken. |
| | 128 | // Lead with the outcome instead. The outcome is derived here from the rows |
| | 129 | // rather than taken from the server subtitle (which keys off the newest |
| | 130 | // terminal row): every presentation — header text, tint, count slots, |
| | 131 | // minimal glyph — must agree, and a failure anywhere should dominate a |
| | 132 | // newer success. |
| | 133 | const allDone = props.activeCount === 0; |
| | 134 | const doneLabel = failedRow ? "Failed" : "Done"; |
| | 135 | const outcomeLabel = failedRow ? "Agent work failed" : "Agent work completed"; |
| | 136 | |
| 126 | // Header copy: "5 active agents" + (", 1 needs attention"). The banner renders | 137 | // Header copy: "5 active agents" + (", 1 needs attention"). The banner renders |
| 127 | // the two parts in-line so the attention half can carry the accent color; | 138 | // the two parts in-line so the attention half can carry the accent color; |
| 128 | // `summary` is the short form for tight spots (expanded center, watch card). | 139 | // `summary` is the short form for tight spots (expanded center, watch card). |
| 129 | const agentWord = props.activeCount === 1 ? "agent" : "agents"; | 140 | const agentWord = props.activeCount === 1 ? "agent" : "agents"; |
| 130 | const agentsLabel = `${props.activeCount} active ${agentWord}`; | 141 | const agentsLabel = allDone ? outcomeLabel : `${props.activeCount} active ${agentWord}`; |
| 131 | const attentionSuffix = | 142 | const attentionSuffix = |
| 132 | attentionRows.length > 0 | 143 | attentionRows.length > 0 |
| 133 | ? `${attentionRows.length} need${attentionRows.length === 1 ? "s" : ""} attention` | 144 | ? `${attentionRows.length} need${attentionRows.length === 1 ? "s" : ""} attention` |
| 134 | : ""; | 145 | : ""; |
| 135 | const summary = attentionSuffix || `${props.activeCount} active`; | 146 | const activeLabel = allDone ? doneLabel : `${props.activeCount} active`; |
| | 147 | const summary = attentionSuffix || activeLabel; |
| 136 | | 148 | |
| 137 | // Any registered scheme variant routes back to this app; taps are delivered | 149 | // Any registered scheme variant routes back to this app; taps are delivered |
| 138 | // to the widget's containing app, so the prod scheme is safe for all builds. | 150 | // to the widget's containing app, so the prod scheme is safe for all builds. |
| ... | @@ -142,8 +154,6 @@ export function AgentActivity( | ... | @@ -142,8 +154,6 @@ export function AgentActivity( |
| 142 | ? `t3code://${deepLinkRow.deepLink.slice(1)}` | 154 | ? `t3code://${deepLinkRow.deepLink.slice(1)}` |
| 143 | : null; | 155 | : null; |
| 144 | | 156 | |
| 145 | const activeLabel = `${props.activeCount} active`; | | |
| 146 | | | |
| 147 | // A scannable status glyph per phase — reads faster than colored words and | 157 | // A scannable status glyph per phase — reads faster than colored words and |
| 148 | // ties the compact / expanded / banner / watch presentations together. | 158 | // ties the compact / expanded / banner / watch presentations together. |
| 149 | type SFName = NonNullable<ComponentProps<typeof Image>["systemName"]>; | 159 | type SFName = NonNullable<ComponentProps<typeof Image>["systemName"]>; |
| ... | @@ -245,7 +255,9 @@ export function AgentActivity( | ... | @@ -245,7 +255,9 @@ export function AgentActivity( |
| 245 | <Text | 255 | <Text |
| 246 | modifiers={[ | 256 | modifiers={[ |
| 247 | font({ weight: "semibold", size: 13 }), | 257 | font({ weight: "semibold", size: 13 }), |
| 248 | foregroundStyle(primaryForeground), | 258 | // The all-done header carries the outcome tint (emerald / |
| | 259 | // red) the way the Done/Failed status labels do. |
| | 260 | foregroundStyle(allDone ? headerTint : primaryForeground), |
| 249 | lineLimit(1), | 261 | lineLimit(1), |
| 250 | ]} | 262 | ]} |
| 251 | > | 263 | > |
| ... | @@ -322,16 +334,17 @@ export function AgentActivity( | ... | @@ -322,16 +334,17 @@ export function AgentActivity( |
| 322 | </Text> | 334 | </Text> |
| 323 | ), | 335 | ), |
| 324 | // The shared/minimal form is a ~22pt circle — a single signal reads there, | 336 | // The shared/minimal form is a ~22pt circle — a single signal reads there, |
| 325 | // the wordmark does not. Show the blocking phase glyph, else the mark. | 337 | // the wordmark does not. Show the blocking/outcome phase glyph, else the |
| | 338 | // mark (all-done shows the hero row's checkmark/cross). |
| 326 | minimal: | 339 | minimal: |
| 327 | attentionRow || failedRow | 340 | (attentionRow || failedRow || allDone) && heroRow |
| 328 | ? renderGlyph(phaseSymbol(heroRow.phase), 13, phaseTint(heroRow.phase)) | 341 | ? renderGlyph(phaseSymbol(heroRow.phase), 13, phaseTint(heroRow.phase)) |
| 329 | : renderLogo(11, tint), | 342 | : renderLogo(11, tint), |
| 330 | expandedLeading: ( | 343 | expandedLeading: ( |
| 331 | <HStack spacing={5} alignment="center" modifiers={[padding({ leading: 4, vertical: 4 })]}> | 344 | <HStack spacing={5} alignment="center" modifiers={[padding({ leading: 4, vertical: 4 })]}> |
| 332 | {renderLogo(15, tint)} | 345 | {renderLogo(15, tint)} |
| 333 | <Text modifiers={[font({ weight: "bold", size: 13 }), foregroundStyle(tint)]}> | 346 | <Text modifiers={[font({ weight: "bold", size: 13 }), foregroundStyle(tint)]}> |
| 334 | {`${props.activeCount}`} | 347 | {allDone ? doneLabel : `${props.activeCount}`} |
| 335 | </Text> | 348 | </Text> |
| 336 | </HStack> | 349 | </HStack> |
| 337 | ), | 350 | ), |