|
6 | 6 | const n = d.querySelectorAll(':scope > .op').length; |
7 | 7 | d.querySelector('summary').textContent = '⚙ ' + n + ' operation' + (n === 1 ? '' : 's'); |
8 | 8 | } |
| 9 | + // Activity renders through the original append path while work is running. Only the explicit |
| 10 | + // completion signal wraps finished activity, so live operation cards look and behave exactly as |
| 11 | + // they did before completed-turn collapsing existed. |
| 12 | + function activitySummary(d) { |
| 13 | + const ops = d.querySelectorAll('.op').length; |
| 14 | + const tools = d.querySelectorAll('.tool-pill').length; |
| 15 | + const status = d.querySelectorAll('.notice-card.activity-item').length; |
| 16 | + const parts = []; |
| 17 | + if (ops) parts.push(ops + ' operation' + (ops === 1 ? '' : 's')); |
| 18 | + if (tools) parts.push(tools + ' tool call' + (tools === 1 ? '' : 's')); |
| 19 | + if (status) parts.push(status + ' update' + (status === 1 ? '' : 's')); |
| 20 | + d.querySelector('summary').textContent = 'Activity' + (parts.length ? ' · ' + parts.join(' · ') : ''); |
| 21 | + } |
| 22 | + function completeActivity() { |
| 23 | + const items = Array.from(log.querySelectorAll('.activity-item:not([data-activity-completed])')); |
| 24 | + const tops = []; |
| 25 | + items.forEach(function (item) { |
| 26 | + item.setAttribute('data-activity-completed', '1'); |
| 27 | + let top = item; |
| 28 | + while (top.parentElement && top.parentElement !== log) top = top.parentElement; |
| 29 | + if (top.parentElement === log && tops.indexOf(top) < 0) tops.push(top); |
| 30 | + }); |
| 31 | + |
| 32 | + let run = []; |
| 33 | + function flush() { |
| 34 | + if (!run.length) return; |
| 35 | + const d = document.createElement('details'); |
| 36 | + d.className = 'activity-group'; |
| 37 | + d.appendChild(document.createElement('summary')); |
| 38 | + log.insertBefore(d, run[0]); |
| 39 | + run.forEach(function (node) { d.appendChild(node); }); |
| 40 | + activitySummary(d); |
| 41 | + run = []; |
| 42 | + } |
| 43 | + |
| 44 | + tops.forEach(function (top) { |
| 45 | + if (run.length && run[run.length - 1].nextElementSibling !== top) flush(); |
| 46 | + run.push(top); |
| 47 | + }); |
| 48 | + flush(); |
| 49 | + } |
| 50 | + function approvalActivitySummary(d) { |
| 51 | + const icons = []; |
| 52 | + d.querySelectorAll('.approval-activity-item[data-activity-icon]').forEach(function (item) { |
| 53 | + const icon = item.getAttribute('data-activity-icon'); |
| 54 | + if (icon && icons.indexOf(icon) < 0) icons.push(icon); |
| 55 | + }); |
| 56 | + const summary = d.querySelector('summary'); |
| 57 | + summary.textContent = icons.join(' '); |
| 58 | + summary.title = 'Approval activity'; |
| 59 | + summary.setAttribute('aria-label', 'Approval activity: ' + icons.join(' ')); |
| 60 | + } |
| 61 | + function completeApprovalActivity() { |
| 62 | + const items = Array.from(log.querySelectorAll( |
| 63 | + '.approval-activity-item:not([data-approval-activity-completed])')); |
| 64 | + const tops = []; |
| 65 | + items.forEach(function (item) { |
| 66 | + item.setAttribute('data-approval-activity-completed', '1'); |
| 67 | + let top = item; |
| 68 | + while (top.parentElement && top.parentElement !== log) top = top.parentElement; |
| 69 | + if (top.parentElement === log && tops.indexOf(top) < 0) tops.push(top); |
| 70 | + }); |
| 71 | + |
| 72 | + let run = []; |
| 73 | + function flush() { |
| 74 | + if (!run.length) return; |
| 75 | + const d = document.createElement('details'); |
| 76 | + d.className = 'approval-activity-group'; |
| 77 | + d.appendChild(document.createElement('summary')); |
| 78 | + log.insertBefore(d, run[0]); |
| 79 | + run.forEach(function (node) { d.appendChild(node); }); |
| 80 | + approvalActivitySummary(d); |
| 81 | + run = []; |
| 82 | + } |
| 83 | + |
| 84 | + tops.forEach(function (top) { |
| 85 | + if (run.length && run[run.length - 1].nextElementSibling !== top) flush(); |
| 86 | + run.push(top); |
| 87 | + }); |
| 88 | + flush(); |
| 89 | + } |
| 90 | + // Finished work commonly alternates between a tool/activity card, its resulting diff or |
| 91 | + // command, and an approval result. Keep that complete sequence together once the turn is |
| 92 | + // over, without changing the individual cards or how they stream while the work is active. |
| 93 | + function isCompletedWork(node) { |
| 94 | + return !node.hasAttribute('data-work-completed') && |
| 95 | + (node.matches('details.activity-group, details.approval-activity-group') || |
| 96 | + node.matches('.panel[data-work-kind]')); |
| 97 | + } |
| 98 | + function workSummary(d) { |
| 99 | + const files = d.querySelectorAll('.panel[data-work-kind="diff"]').length; |
| 100 | + const additions = d.querySelectorAll('.d-add').length; |
| 101 | + const deletions = d.querySelectorAll('.d-rem').length; |
| 102 | + const commands = d.querySelectorAll('.panel[data-work-kind="command"]').length; |
| 103 | + const icons = []; |
| 104 | + d.querySelectorAll('.approval-activity-item[data-activity-icon]').forEach(function (item) { |
| 105 | + const icon = item.getAttribute('data-activity-icon'); |
| 106 | + if (icon && icons.indexOf(icon) < 0) icons.push(icon); |
| 107 | + }); |
| 108 | + |
| 109 | + const parts = []; |
| 110 | + if (files) parts.push(files + ' file' + (files === 1 ? '' : 's') + ' changed'); |
| 111 | + if (additions || deletions) parts.push('+' + additions + ' / −' + deletions); |
| 112 | + if (icons.length) parts.push(icons.join(' ')); |
| 113 | + if (commands) parts.push(commands + ' command' + (commands === 1 ? '' : 's')); |
| 114 | + d.querySelector('summary').textContent = 'Work completed' + |
| 115 | + (parts.length ? ' · ' + parts.join(' · ') : ''); |
| 116 | + } |
| 117 | + function completeWorkRollups() { |
| 118 | + let run = []; |
| 119 | + function flush() { |
| 120 | + if (run.length < 2) return run = []; |
| 121 | + const d = document.createElement('details'); |
| 122 | + d.className = 'work-group'; |
| 123 | + d.appendChild(document.createElement('summary')); |
| 124 | + log.insertBefore(d, run[0]); |
| 125 | + run.forEach(function (node) { |
| 126 | + node.setAttribute('data-work-completed', '1'); |
| 127 | + d.appendChild(node); |
| 128 | + }); |
| 129 | + workSummary(d); |
| 130 | + run = []; |
| 131 | + } |
| 132 | + Array.from(log.children).forEach(function (node) { |
| 133 | + if (isCompletedWork(node)) run.push(node); |
| 134 | + else flush(); |
| 135 | + }); |
| 136 | + flush(); |
| 137 | + } |
9 | 138 | function placeChild(c) { |
10 | 139 | if (c.nodeType !== 1) { log.appendChild(c); return; } |
11 | 140 | if (c.classList.contains('op')) { |
|
387 | 516 | if (nearBottom) window.scrollTo(0, document.body.scrollHeight); |
388 | 517 | updatePill(); |
389 | 518 | }; |
| 519 | + window.__completeActivity = function () { |
| 520 | + completeActivity(); |
| 521 | + completeApprovalActivity(); |
| 522 | + completeWorkRollups(); |
| 523 | + }; |
390 | 524 | window.__clear = function () { log.innerHTML = ''; updatePill(); }; |
391 | 525 |
|
392 | 526 | document.addEventListener('click', function (e) { |
|
0 commit comments