diff --git a/apps/dashboard/src/RoomCardV2.test.ts b/apps/dashboard/src/RoomCardV2.test.ts
index 16cbb4c..89643ec 100644
--- a/apps/dashboard/src/RoomCardV2.test.ts
+++ b/apps/dashboard/src/RoomCardV2.test.ts
@@ -24,8 +24,13 @@ const formatters = {
formatDate: (value: string | null | undefined) => value ?? '-',
formatDuration: (value: number | null) => (value === null ? '-' : `${value}`),
formatLiveElapsed: (value: number) => `${value}`,
- senderRoleClass: (value: string | null | undefined) =>
- (value ?? '').toLowerCase().includes('owner') ? 'role-owner' : 'role-human',
+ senderRoleClass: (value: string | null | undefined) => {
+ const role = (value ?? '').toLowerCase();
+ if (role.includes('owner')) return 'role-owner';
+ if (role.includes('reviewer')) return 'role-reviewer';
+ if (role.includes('arbiter')) return 'role-arbiter';
+ return 'role-human';
+ },
statusLabel: (status: string) => status,
};
@@ -110,6 +115,51 @@ describe('RoomCardV2', () => {
expect(html).toContain('prod 배포 완료');
});
+ it('localizes protocol role and verdict labels in Korean rooms', () => {
+ const html = renderToStaticMarkup(
+ createElement(RoomCardV2, {
+ activity: activity({
+ pairedTask: {
+ id: 'task-1',
+ title: 'Review pending',
+ status: 'in_review',
+ roundTripCount: 2,
+ updatedAt: '2026-04-28T02:03:00.000Z',
+ currentTurn: null,
+ outputs: [
+ {
+ id: 1,
+ turnNumber: 2,
+ role: 'reviewer',
+ verdict: 'continue',
+ createdAt: '2026-04-28T02:02:00.000Z',
+ outputText: '검증 완료. 다음 단계로 진행 가능합니다.',
+ },
+ ],
+ },
+ }),
+ activityLoading: false,
+ busy: false,
+ draft: '',
+ entry,
+ expanded: true,
+ inboxItems: [],
+ locale: 'ko',
+ onDraftChange: () => {},
+ onSendMessage: () => {},
+ onToggle: () => {},
+ pinned: true,
+ t,
+ ...formatters,
+ }),
+ );
+
+ expect(html).toContain('>리뷰어');
+ expect(html).toContain('>계속');
+ expect(html).not.toContain('>reviewer');
+ expect(html).not.toContain('>continue');
+ });
+
it('renders live progress as markdown', () => {
const html = renderToStaticMarkup(
createElement(RoomCardV2, {
@@ -244,6 +294,7 @@ describe('RoomCardV2', () => {
);
expect(html).toContain('class="room-watcher-fold"');
+ expect(html).toContain('>리뷰어');
expect(html).toContain(watcherTail);
});
});
diff --git a/apps/dashboard/src/RoomCardV2.tsx b/apps/dashboard/src/RoomCardV2.tsx
index 8caad08..6fe7da4 100644
--- a/apps/dashboard/src/RoomCardV2.tsx
+++ b/apps/dashboard/src/RoomCardV2.tsx
@@ -5,6 +5,7 @@ import type { DashboardOverview, DashboardRoomActivity } from './api';
import type { Locale, Messages } from './i18n';
import { ParsedBody } from './ParsedBody';
import { RoomAttachmentGallery } from './RoomAttachmentGallery';
+import { displayRole, displayVerdict } from './roomDisplayLabels';
import {
buildRoomThreadEntries,
isWatcherRoomMessage,
@@ -421,7 +422,9 @@ function RoomCurrentTurnSummary({
return (
- {turn.role}
+
+ {displayRole(turn.role, locale)}
+
{turn.intentKind}
{turn.attemptNo > 1 ? (
@@ -504,7 +507,9 @@ function CollapsedLiveTurn({
LIVE
- {turn.role}
+
+ {displayRole(turn.role, locale)}
+
{turn.intentKind}
{turn.state}
{turn.executorServiceId ? (
@@ -574,7 +579,7 @@ function CollapsedWatcherStrip({
워쳐 {watcherCount}
- {lastWatcher.senderName}
+ {displayRole(lastWatcher.senderName, locale)}
{lastWatcher.content.slice(0, 90)}
@@ -764,7 +769,7 @@ function RoomTimelineEntry({
- {entry.senderName}
+ {displayRole(entry.senderName, locale)}
{entry.turnNumber !== undefined ? (
@@ -774,7 +779,7 @@ function RoomTimelineEntry({
) : null}
{verdict ? (
- {verdict}
+ {displayVerdict(verdict, locale)}
) : null}
@@ -818,7 +823,7 @@ function RoomLiveTimelineEntry({
)}
- {turn.role}
+ {displayRole(turn.role, locale)}