@@ -989,6 +989,53 @@ describe("MessagesTimeline", () => {
989989 expect ( markup ) . not . toContain ( 'aria-label="Tool call failed"' ) ;
990990 } ) ;
991991
992+ it ( "keeps the collapsed summary icon neutral when the group ends in a failure" , ( ) => {
993+ const markup = renderToStaticMarkup (
994+ < MessagesTimeline
995+ { ...buildProps ( ) }
996+ timelineEntries = { [
997+ {
998+ id : "entry-completed" ,
999+ kind : "work" ,
1000+ createdAt : "2026-03-17T19:12:28.000Z" ,
1001+ entry : {
1002+ id : "work-completed" ,
1003+ createdAt : "2026-03-17T19:12:28.000Z" ,
1004+ label : "Run tests" ,
1005+ tone : "tool" ,
1006+ itemType : "command_execution" ,
1007+ toolLifecycleStatus : "completed" ,
1008+ } ,
1009+ } ,
1010+ {
1011+ id : "entry-failed" ,
1012+ kind : "work" ,
1013+ createdAt : "2026-03-17T19:12:29.000Z" ,
1014+ entry : {
1015+ id : "work-failed" ,
1016+ createdAt : "2026-03-17T19:12:29.000Z" ,
1017+ label : "Run lint" ,
1018+ tone : "tool" ,
1019+ itemType : "command_execution" ,
1020+ toolLifecycleStatus : "failed" ,
1021+ } ,
1022+ } ,
1023+ ] }
1024+ /> ,
1025+ ) ;
1026+
1027+ expect ( markup ) . toContain ( "Ran 2 commands" ) ;
1028+ // Neutral: the group keeps its command glyph instead of swapping to a
1029+ // failure glyph, and neither the heading nor the icon wrapper goes red once
1030+ // the group ends in an ordinary command failure. Pylon renders the command
1031+ // summary as a DotMatrix, so assert its state rather than a lucide class.
1032+ expect ( markup ) . toContain ( 'data-state="terminal"' ) ;
1033+ expect ( markup ) . not . toMatch ( / f o n t - m e d i u m t e x t - d e s t r u c t i v e / ) ;
1034+ expect ( markup ) . not . toMatch ( / s i z e - 6 s h r i n k - 0 i t e m s - c e n t e r j u s t i f y - c e n t e r t e x t - d e s t r u c t i v e / ) ;
1035+ // The failure stays discoverable for screen readers.
1036+ expect ( markup ) . toContain ( "tool call failed" ) ;
1037+ } ) ;
1038+
9921039 it ( "keeps mixed work logs neutral after a later tool call succeeds" , ( ) => {
9931040 const markup = renderToStaticMarkup (
9941041 < MessagesTimeline
@@ -1402,11 +1449,22 @@ describe("MessagesTimeline", () => {
14021449 expect ( markup ) . not . toContain ( 'data-testid="file-diff"' ) ;
14031450 } ) ;
14041451
1405- it ( "renders a failure marker for failed tool lifecycle entries" , ( ) => {
1452+ it ( "renders a muted failure marker for failed tool lifecycle entries" , ( ) => {
14061453 const markup = renderToStaticMarkup (
14071454 < MessagesTimeline
14081455 { ...buildProps ( ) }
14091456 timelineEntries = { [
1457+ {
1458+ id : "entry-info" ,
1459+ kind : "work" ,
1460+ createdAt : "2026-03-17T19:12:27.000Z" ,
1461+ entry : {
1462+ id : "work-info" ,
1463+ createdAt : "2026-03-17T19:12:27.000Z" ,
1464+ label : "Status updated" ,
1465+ tone : "info" ,
1466+ } ,
1467+ } ,
14101468 {
14111469 id : "entry-1" ,
14121470 kind : "work" ,
@@ -1426,5 +1484,48 @@ describe("MessagesTimeline", () => {
14261484
14271485 expect ( markup ) . toContain ( 'data-state="error"' ) ;
14281486 expect ( markup ) . toContain ( 'aria-label="Tool call failed"' ) ;
1487+ // Ordinary tool failures render muted, not red. Pylon's marker is a small
1488+ // DotMatrix status dot that is itself destructive-coloured, so assert on the
1489+ // row treatment rather than on the absence of the class anywhere in the tree.
1490+ expect ( markup ) . not . toMatch ( / f o n t - m e d i u m t e x t - d e s t r u c t i v e / ) ;
1491+ expect ( markup ) . not . toMatch ( / s i z e - 6 s h r i n k - 0 i t e m s - c e n t e r j u s t i f y - c e n t e r t e x t - d e s t r u c t i v e / ) ;
1492+ } ) ;
1493+
1494+ it ( "keeps the red treatment for severe orchestration failures" , ( ) => {
1495+ const markup = renderToStaticMarkup (
1496+ < MessagesTimeline
1497+ { ...buildProps ( ) }
1498+ timelineEntries = { [
1499+ {
1500+ id : "entry-info" ,
1501+ kind : "work" ,
1502+ createdAt : "2026-03-17T19:12:27.000Z" ,
1503+ entry : {
1504+ id : "work-info" ,
1505+ createdAt : "2026-03-17T19:12:27.000Z" ,
1506+ label : "Status updated" ,
1507+ tone : "info" ,
1508+ } ,
1509+ } ,
1510+ {
1511+ id : "entry-turn-failed" ,
1512+ kind : "work" ,
1513+ createdAt : "2026-03-17T19:12:28.000Z" ,
1514+ entry : {
1515+ id : "work-turn-failed" ,
1516+ createdAt : "2026-03-17T19:12:28.000Z" ,
1517+ label : "Provider turn start failed" ,
1518+ tone : "error" ,
1519+ sourceActivityKind : "provider.turn.start.failed" ,
1520+ } ,
1521+ } ,
1522+ ] }
1523+ /> ,
1524+ ) ;
1525+
1526+ // Pylon renders severe failures with circle-alert where upstream uses an x.
1527+ expect ( markup ) . toContain ( "lucide-circle-alert" ) ;
1528+ expect ( markup ) . toMatch ( / f o n t - m e d i u m t e x t - d e s t r u c t i v e / ) ;
1529+ expect ( markup ) . toMatch ( / s i z e - 6 s h r i n k - 0 i t e m s - c e n t e r j u s t i f y - c e n t e r t e x t - d e s t r u c t i v e / ) ;
14291530 } ) ;
14301531} ) ;
0 commit comments