Render dashboard live progress markdown (#67)
* review: harden readonly git checks and lint verification * test: satisfy readonly reviewer sandbox typing * test: fix readonly reviewer sandbox assertions * test: remove impossible readonly sandbox guards * test: relax readonly sandbox assertions * test: cast readonly sandbox expectation shape * test: cast readonly sandbox expectation through unknown * Phase 0 — STEP_DONE/TASK_DONE split + owner-follow-up integration smoke * Add STEP_DONE guards, verdict storage, and stale delivery suppression * style: format paired stepdone telemetry files * Route STEP_DONE through reviewer * Add structured Discord attachments * style: format structured attachment test * Fix room thread bot output parity * Remove duplicate work item attachment migration from owner branch * Remove legacy dashboard rooms renderer * Extract dashboard parsed body renderer * Extract dashboard redaction helpers * Extract dashboard RoomCardV2 component * Include RoomCardV2 test in vitest suite * Extract dashboard RoomBoardV2 component * Extract dashboard EmptyState component * Extract dashboard InboxPanel component * Split dashboard InboxPanel card renderer * Extract dashboard TaskPanel component * Extract dashboard UsagePanel component * Fix dashboard room thread chunk rendering * Extract dashboard ServicePanel component * Render dashboard live progress markdown
This commit is contained in:
@@ -109,4 +109,56 @@ describe('RoomCardV2', () => {
|
|||||||
expect(html).toContain('TASK_DONE');
|
expect(html).toContain('TASK_DONE');
|
||||||
expect(html).toContain('prod 배포 완료');
|
expect(html).toContain('prod 배포 완료');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('renders live progress as markdown', () => {
|
||||||
|
const html = renderToStaticMarkup(
|
||||||
|
createElement(RoomCardV2, {
|
||||||
|
activity: activity({
|
||||||
|
pairedTask: {
|
||||||
|
id: 'task-1',
|
||||||
|
title: 'OpenAPI sync',
|
||||||
|
status: 'running',
|
||||||
|
roundTripCount: 1,
|
||||||
|
updatedAt: '2026-04-28T02:01:00.000Z',
|
||||||
|
currentTurn: {
|
||||||
|
turnId: 'turn-1',
|
||||||
|
role: 'owner',
|
||||||
|
intentKind: 'implementation',
|
||||||
|
state: 'running',
|
||||||
|
attemptNo: 1,
|
||||||
|
executorServiceId: 'svc-1',
|
||||||
|
executorAgentType: 'codex',
|
||||||
|
activeRunId: null,
|
||||||
|
createdAt: '2026-04-28T02:00:00.000Z',
|
||||||
|
updatedAt: '2026-04-28T02:01:00.000Z',
|
||||||
|
completedAt: null,
|
||||||
|
lastError: null,
|
||||||
|
progressText:
|
||||||
|
'최신 `origin/dev`로 fast-forward한 뒤 `pnpm openapi:sync`를 실행합니다.',
|
||||||
|
progressUpdatedAt: '2026-04-28T02:01:00.000Z',
|
||||||
|
},
|
||||||
|
outputs: [],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
activityLoading: false,
|
||||||
|
busy: false,
|
||||||
|
draft: '',
|
||||||
|
entry: { ...entry, status: 'processing' },
|
||||||
|
expanded: false,
|
||||||
|
inboxItems: [],
|
||||||
|
locale: 'ko',
|
||||||
|
onDraftChange: () => {},
|
||||||
|
onSendMessage: () => {},
|
||||||
|
onToggle: () => {},
|
||||||
|
pinned: false,
|
||||||
|
t,
|
||||||
|
...formatters,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(html).toContain('class="live-progress"');
|
||||||
|
expect(html).toContain('<code>origin/dev</code>');
|
||||||
|
expect(html).toContain('<code>pnpm openapi:sync</code>');
|
||||||
|
expect(html).not.toContain('`origin/dev`');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -519,7 +519,7 @@ function CollapsedLiveTurn({
|
|||||||
</time>
|
</time>
|
||||||
</header>
|
</header>
|
||||||
{turn.progressText && turn.progressText.trim() ? (
|
{turn.progressText && turn.progressText.trim() ? (
|
||||||
<pre className="live-progress">{turn.progressText}</pre>
|
<LiveProgressBody text={turn.progressText} />
|
||||||
) : turn.activeRunId ? (
|
) : turn.activeRunId ? (
|
||||||
<code className="live-run">{turn.activeRunId}</code>
|
<code className="live-run">{turn.activeRunId}</code>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -527,6 +527,14 @@ function CollapsedLiveTurn({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function LiveProgressBody({ text }: { text: string }) {
|
||||||
|
return (
|
||||||
|
<div className="live-progress">
|
||||||
|
<ParsedBody text={text} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function CollapsedOutputPreview({
|
function CollapsedOutputPreview({
|
||||||
expanded,
|
expanded,
|
||||||
isProcessing,
|
isProcessing,
|
||||||
@@ -824,7 +832,7 @@ function RoomLiveTimelineEntry({
|
|||||||
</header>
|
</header>
|
||||||
<div className="room-timeline-body">
|
<div className="room-timeline-body">
|
||||||
{liveProgressDisplay ? (
|
{liveProgressDisplay ? (
|
||||||
<pre className="live-progress">{liveProgressDisplay}</pre>
|
<LiveProgressBody text={liveProgressDisplay} />
|
||||||
) : (
|
) : (
|
||||||
<p className="room-empty">{t.rooms.loadingActivity}</p>
|
<p className="room-empty">{t.rooms.loadingActivity}</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user