| ... | ... | @@ -1470,57 +1470,54 @@ describe("deriveMessagesTimelineRows", () => { |
| 1470 | 1470 | }); |
| 1471 | 1471 | }); |
| 1472 | 1472 | |
| 1473 | | it("keeps a failure visible when other hidden entries succeeded", () => { |
| 1474 | | const rows = deriveMessagesTimelineRows({ |
| 1475 | | timelineEntries: [ |
| 1476 | | { |
| 1477 | | id: "failed-work-entry", |
| 1478 | | kind: "work", |
| 1479 | | createdAt: "2026-01-01T00:00:01Z", |
| 1480 | | entry: { |
| 1481 | | id: "failed-work", |
| 1482 | | createdAt: "2026-01-01T00:00:01Z", |
| 1483 | | label: "Ran command", |
| 1484 | | tone: "tool", |
| 1485 | | toolLifecycleStatus: "failed", |
| 1486 | | }, |
| 1487 | | }, |
| 1488 | | { |
| 1489 | | id: "completed-work-entry", |
| 1490 | | kind: "work", |
| 1491 | | createdAt: "2026-01-01T00:00:02Z", |
| 1492 | | entry: { |
| 1493 | | id: "completed-work", |
| 1494 | | createdAt: "2026-01-01T00:00:02Z", |
| 1495 | | label: "Ran command", |
| 1496 | | tone: "tool", |
| 1497 | | toolLifecycleStatus: "completed", |
| 1498 | | }, |
| 1499 | | }, |
| 1500 | | { |
| 1501 | | id: "visible-info-entry", |
| 1502 | | kind: "work", |
| 1503 | | createdAt: "2026-01-01T00:00:03Z", |
| 1504 | | entry: { |
| 1505 | | id: "visible-info", |
| 1506 | | createdAt: "2026-01-01T00:00:03Z", |
| 1507 | | label: "Status updated", |
| 1508 | | tone: "info", |
| 1509 | | }, |
| 1510 | | }, |
| 1511 | | ], |
| 1512 | | isWorking: false, |
| 1513 | | activeTurnStartedAt: null, |
| 1514 | | turnDiffSummaryByAssistantMessageId: new Map(), |
| 1515 | | revertTurnCountByUserMessageId: new Map(), |
| 1516 | | }); |
| 1473 | it.each([ |
| 1474 | ["the later success is hidden", ["failed", "completed", "info"], false], |
| 1475 | ["the later success is visible", ["failed", "info", "completed"], false], |
| 1476 | ["an error-toned entry recovers", ["error", "info", "completed"], false], |
| 1477 | ["the final failure is hidden", ["completed", "failed", "info"], true], |
| 1478 | ["the final failure is visible", ["failed", "info", "failed"], true], |
| 1479 | ["the only failure is visible", ["completed", "info", "failed"], false], |
| 1480 | ] as const)( |
| 1481 | "uses the final tool call for mixed work groups when %s", |
| 1482 | (_, statuses, hasFailure) => { |
| 1483 | const timelineEntries = statuses.map((status, index) => { |
| 1484 | const id = `work-${index}`; |
| 1485 | const createdAt = `2026-01-01T00:00:0${index}Z`; |
| 1486 | |
| 1487 | return { |
| 1488 | id: `work-entry-${index}`, |
| 1489 | kind: "work" as const, |
| 1490 | createdAt, |
| 1491 | entry: |
| 1492 | status === "info" |
| 1493 | ? { id, createdAt, label: "Status updated", tone: "info" as const } |
| 1494 | : status === "error" |
| 1495 | ? { id, createdAt, label: "Command failed", tone: "error" as const } |
| 1496 | : { |
| 1497 | id, |
| 1498 | createdAt, |
| 1499 | label: "Ran command", |
| 1500 | tone: "tool" as const, |
| 1501 | toolLifecycleStatus: status, |
| 1502 | }, |
| 1503 | }; |
| 1504 | }); |
| 1517 | 1505 | |
| 1518 | | expect(rows.find((row) => row.kind === "work-toggle")).toMatchObject({ |
| 1519 | | hiddenCount: 2, |
| 1520 | | summary: null, |
| 1521 | | hasFailure: true, |
| 1522 | | }); |
| 1523 | | }); |
| 1506 | const rows = deriveMessagesTimelineRows({ |
| 1507 | timelineEntries, |
| 1508 | isWorking: false, |
| 1509 | activeTurnStartedAt: null, |
| 1510 | turnDiffSummaryByAssistantMessageId: new Map(), |
| 1511 | revertTurnCountByUserMessageId: new Map(), |
| 1512 | }); |
| 1513 | |
| 1514 | expect(rows.find((row) => row.kind === "work-toggle")).toMatchObject({ |
| 1515 | hiddenCount: 2, |
| 1516 | summary: null, |
| 1517 | hasFailure, |
| 1518 | }); |
| 1519 | }, |
| 1520 | ); |
| 1524 | 1521 | }); |
| 1525 | 1522 | |
| 1526 | 1523 | describe("computeStableMessagesTimelineRows", () => { |