Skip to content

Commit db53a94

Browse files
joewinkeclaude
andcommitted
bug(jat-q8r49): surface accept/reject events in activity timeline
- history endpoint now includes status_change comments (e.g. from feedback widget respond) in the timeline as task_accepted / task_rejected events - TaskDetailDrawer renders them on the left side with green/red check/x icons - Tasks tab filter includes status_change events alongside jat_events - count.status_changes added to the history response Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 30b6fb7 commit db53a94

2 files changed

Lines changed: 63 additions & 8 deletions

File tree

ide/src/lib/components/TaskDetailDrawer.svelte

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@
191191
192192
// Task history state
193193
interface TimelineEvent {
194-
type: 'jat_event' | 'agent_mail';
194+
type: 'jat_event' | 'agent_mail' | 'status_change';
195195
event: string;
196196
timestamp: string;
197197
description: string;
@@ -205,6 +205,7 @@
205205
count: {
206206
total: number;
207207
jat_events: number;
208+
status_changes: number;
208209
agent_mail: number;
209210
};
210211
timestamp: string;
@@ -863,7 +864,7 @@
863864
const filteredTimeline = $derived((): TimelineEvent[] => {
864865
if (!taskHistory?.timeline) return [];
865866
if (timelineFilter === 'all') return taskHistory.timeline;
866-
if (timelineFilter === 'tasks') return taskHistory.timeline.filter(e => e.type === 'jat_event');
867+
if (timelineFilter === 'tasks') return taskHistory.timeline.filter(e => e.type === 'jat_event' || e.type === 'status_change');
867868
return taskHistory.timeline.filter(e => e.type === 'agent_mail');
868869
});
869870
@@ -4969,7 +4970,7 @@
49694970
class="tab {timelineFilter === 'tasks' ? 'tab-active' : ''}"
49704971
onclick={() => timelineFilter = 'tasks'}
49714972
>
4972-
Tasks ({taskHistory?.count?.jat_events || 0})
4973+
Tasks ({(taskHistory?.count?.jat_events || 0) + (taskHistory?.count?.status_changes || 0)})
49734974
</button>
49744975
<button
49754976
class="tab {timelineFilter === 'messages' ? 'tab-active' : ''}"
@@ -5011,10 +5012,34 @@
50115012
<li style="grid-template-columns: 30% min-content 1fr;">
50125013
<!-- Top connector (skip for first item) -->
50135014
{#if i > 0}
5014-
<hr class="{event.type === 'jat_event' ? 'bg-info' : 'bg-warning'}" />
5015+
<hr class="{event.type === 'jat_event' || event.type === 'status_change' ? (event.event === 'task_accepted' ? 'bg-success' : event.type === 'status_change' ? 'bg-error' : 'bg-info') : 'bg-warning'}" />
50155016
{/if}
50165017

5017-
{#if event.type === 'jat_event'}
5018+
{#if event.type === 'status_change'}
5019+
<!-- Accept/reject decision on LEFT side -->
5020+
{@const isAccepted = event.event === 'task_accepted'}
5021+
<div class="timeline-start timeline-box text-xs mb-4 {isAccepted ? 'border-success/30 bg-success/5' : 'border-error/30 bg-error/5'}">
5022+
<div class="font-semibold text-base-content">
5023+
{event.description}
5024+
</div>
5025+
<div class="text-base-content/60 mt-1">
5026+
{formatDate(event.timestamp)}
5027+
</div>
5028+
{#if event.metadata.author}
5029+
<div class="text-base-content/50 mt-1 text-xs">by {event.metadata.author}</div>
5030+
{/if}
5031+
</div>
5032+
<div class="timeline-middle">
5033+
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 {isAccepted ? 'text-success' : 'text-error'}" fill="none" viewBox="0 0 24 24" stroke="currentColor">
5034+
{#if isAccepted}
5035+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
5036+
{:else}
5037+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
5038+
{/if}
5039+
</svg>
5040+
</div>
5041+
<div class="timeline-end"></div>
5042+
{:else if event.type === 'jat_event'}
50185043
<!-- Task events on LEFT side (timeline-start) -->
50195044
<div class="timeline-start timeline-box text-xs mb-4 border-info/30 bg-info/5">
50205045
<!-- Event description -->
@@ -5134,7 +5159,8 @@
51345159

51355160
<!-- Bottom connector (skip for last item) -->
51365161
{#if i < filteredTimeline().length - 1}
5137-
<hr class="{filteredTimeline()[i + 1]?.type === 'jat_event' ? 'bg-info' : 'bg-warning'}" />
5162+
{@const nextEvent = filteredTimeline()[i + 1]}
5163+
<hr class="{nextEvent?.type === 'status_change' ? (nextEvent.event === 'task_accepted' ? 'bg-success' : 'bg-error') : nextEvent?.type === 'jat_event' ? 'bg-info' : 'bg-warning'}" />
51385164
{/if}
51395165
</li>
51405166
{/each}

ide/src/routes/api/tasks/[id]/history/+server.js

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* Combines:
77
* - JAT task state changes (created, status updates, assignee changes, etc.)
88
* - Agent Mail coordination messages (filtered by thread_id = task.id)
9+
* - status_change comments (accept/reject decisions from feedback widget)
910
*
1011
* Returns chronological timeline with visual distinction between event types.
1112
*/
@@ -117,7 +118,28 @@ export async function GET({ params }) {
117118
});
118119
}
119120

120-
// 2. Add Agent Mail messages
121+
// 2. Add status_change comments (e.g. accept/reject from feedback widget)
122+
const comments = /** @type {Array<any>} */ (task.comments ?? []);
123+
comments
124+
.filter(c => c.comment_type === 'status_change')
125+
.forEach(c => {
126+
const meta = typeof c.metadata === 'string' ? safeParse(c.metadata) : (c.metadata ?? {});
127+
const isAccepted = meta?.response === 'accepted';
128+
timeline.push({
129+
type: 'status_change',
130+
event: isAccepted ? 'task_accepted' : 'task_rejected',
131+
timestamp: c.created_at,
132+
description: c.text || (isAccepted ? 'Accepted' : 'Rejected'),
133+
metadata: {
134+
author: c.author,
135+
response: meta?.response,
136+
reason: meta?.reason,
137+
source: meta?.source
138+
}
139+
});
140+
});
141+
142+
// 3. Add Agent Mail messages
121143
mailMessages.forEach(msg => {
122144
timeline.push({
123145
type: 'agent_mail',
@@ -135,7 +157,7 @@ export async function GET({ params }) {
135157
});
136158
});
137159

138-
// 3. Sort timeline by timestamp (newest first)
160+
// 4. Sort timeline by timestamp (newest first)
139161
timeline.sort((a, b) => {
140162
const timeA = new Date(a.timestamp).getTime();
141163
const timeB = new Date(b.timestamp).getTime();
@@ -149,6 +171,7 @@ export async function GET({ params }) {
149171
count: {
150172
total: timeline.length,
151173
jat_events: timeline.filter(e => e.type === 'jat_event').length,
174+
status_changes: timeline.filter(e => e.type === 'status_change').length,
152175
agent_mail: timeline.filter(e => e.type === 'agent_mail').length
153176
},
154177
timestamp: new Date().toISOString()
@@ -166,3 +189,9 @@ export async function GET({ params }) {
166189
}, { status: 500 });
167190
}
168191
}
192+
193+
/** @param {unknown} s */
194+
function safeParse(s) {
195+
if (typeof s !== 'string') return s;
196+
try { return JSON.parse(s); } catch { return {}; }
197+
}

0 commit comments

Comments
 (0)