From ab3eca14f10f21239d88931c36e34b8006496c19 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 29 May 2026 01:17:11 +0100 Subject: [PATCH] fix(workboard): tighten controls and track card events --- docs/plugins/workboard.md | 9 ++ extensions/workboard/src/store.test.ts | 18 +++ extensions/workboard/src/store.ts | 126 +++++++++++++++- extensions/workboard/src/types.ts | 19 +++ ui/src/i18n/.i18n/ar.meta.json | 8 +- ui/src/i18n/.i18n/de.meta.json | 8 +- ui/src/i18n/.i18n/es.meta.json | 8 +- ui/src/i18n/.i18n/fa.meta.json | 8 +- ui/src/i18n/.i18n/fr.meta.json | 8 +- ui/src/i18n/.i18n/id.meta.json | 8 +- ui/src/i18n/.i18n/it.meta.json | 8 +- ui/src/i18n/.i18n/ja-JP.meta.json | 8 +- ui/src/i18n/.i18n/ko.meta.json | 8 +- ui/src/i18n/.i18n/nl.meta.json | 8 +- ui/src/i18n/.i18n/pl.meta.json | 8 +- ui/src/i18n/.i18n/pt-BR.meta.json | 8 +- ui/src/i18n/.i18n/raw-copy-baseline.json | 77 ---------- ui/src/i18n/.i18n/th.meta.json | 8 +- ui/src/i18n/.i18n/tr.meta.json | 8 +- ui/src/i18n/.i18n/uk.meta.json | 8 +- ui/src/i18n/.i18n/vi.meta.json | 8 +- ui/src/i18n/.i18n/zh-CN.meta.json | 8 +- ui/src/i18n/.i18n/zh-TW.meta.json | 8 +- ui/src/i18n/locales/ar.ts | 26 +++- ui/src/i18n/locales/de.ts | 30 +++- ui/src/i18n/locales/en.ts | 24 +++ ui/src/i18n/locales/es.ts | 24 +++ ui/src/i18n/locales/fa.ts | 58 ++++++- ui/src/i18n/locales/fr.ts | 24 +++ ui/src/i18n/locales/id.ts | 24 +++ ui/src/i18n/locales/it.ts | 24 +++ ui/src/i18n/locales/ja-JP.ts | 24 +++ ui/src/i18n/locales/ko.ts | 26 +++- ui/src/i18n/locales/nl.ts | 52 +++++++ ui/src/i18n/locales/pl.ts | 56 ++++++- ui/src/i18n/locales/pt-BR.ts | 26 +++- ui/src/i18n/locales/th.ts | 52 +++++++ ui/src/i18n/locales/tr.ts | 26 +++- ui/src/i18n/locales/uk.ts | 26 +++- ui/src/i18n/locales/vi.ts | 52 +++++++ ui/src/i18n/locales/zh-CN.ts | 26 +++- ui/src/i18n/locales/zh-TW.ts | 26 +++- ui/src/styles/workboard.css | 31 ++++ ui/src/ui/app-render.ts | 29 ++-- ui/src/ui/controllers/workboard.test.ts | 25 +++- ui/src/ui/controllers/workboard.ts | 63 +++++++- ui/src/ui/views/workboard.test.ts | 115 ++++++++++++++ ui/src/ui/views/workboard.ts | 183 ++++++++++++++++------- 48 files changed, 1224 insertions(+), 241 deletions(-) diff --git a/docs/plugins/workboard.md b/docs/plugins/workboard.md index ceeea25de6a..22d6e348748 100644 --- a/docs/plugins/workboard.md +++ b/docs/plugins/workboard.md @@ -48,10 +48,16 @@ Each card stores: - optional agent id - optional linked session, run, task, or source URL - optional execution metadata for a Codex or Claude session started from the card +- recent card events such as created, moved, linked, or agent-updated changes Cards are stored in the plugin's Gateway state. They are local to the Gateway state directory and move with the rest of that Gateway's OpenClaw state. +Workboard keeps a compact per-card event history so operators can see how a +card moved through the board without opening the linked session. The event trail +is intentionally local metadata; it does not replace session transcripts or +GitHub issue history. + ## Card executions Unlinked cards can start work from the card. Start uses the Gateway's configured @@ -74,6 +80,9 @@ Cards can be linked to existing dashboard sessions or to the session created when you start work from a card. Linked cards show the session lifecycle inline: running, linked idle, done, failed, or missing. +If the linked session is missing, the card stays linked for context and still +offers start controls so you can restart work into a fresh dashboard session. + You can also capture an existing dashboard session from the Sessions tab with Add to Workboard. The card is linked to that session, uses the session label or recent user prompt as the title, and seeds notes from the recent user prompt plus diff --git a/extensions/workboard/src/store.test.ts b/extensions/workboard/src/store.test.ts index 749f1c9c8d0..ce34418b9ab 100644 --- a/extensions/workboard/src/store.test.ts +++ b/extensions/workboard/src/store.test.ts @@ -34,6 +34,15 @@ describe("WorkboardStore", () => { expect((await store.list()).map((card) => card.id)).toEqual([todo.id, review.id]); expect(review.labels).toEqual(["release", "docs"]); expect(review.priority).toBe("high"); + expect(review.events?.[0]).toMatchObject({ kind: "created", toStatus: "review" }); + }); + + it("preserves explicit zero positions", async () => { + const store = new WorkboardStore(createMemoryStore()); + + const card = await store.create({ title: "Top card", status: "todo", position: 0 }); + + expect(card.position).toBe(0); }); it("keeps initial session, run, and task links when creating cards", async () => { @@ -77,6 +86,11 @@ describe("WorkboardStore", () => { expect(running.status).toBe("running"); expect(running.position).toBe(500); expect(running.startedAt).toBeGreaterThanOrEqual(card.createdAt); + expect(running.events?.at(-1)).toMatchObject({ + kind: "moved", + fromStatus: "todo", + toStatus: "running", + }); const done = await store.update(card.id, { status: "done" }); expect(done.completedAt).toBeGreaterThanOrEqual(done.startedAt ?? 0); @@ -103,6 +117,10 @@ describe("WorkboardStore", () => { const relinked = await store.update(card.id, { sessionKey: "agent:main:dashboard:2" }); expect(relinked.sessionKey).toBe("agent:main:dashboard:2"); expect(relinked.execution?.sessionKey).toBe("agent:main:dashboard:2"); + expect(relinked.events?.at(-1)).toMatchObject({ + kind: "linked", + sessionKey: "agent:main:dashboard:2", + }); const unlinked = await store.update(card.id, { sessionKey: "" }); expect(unlinked.sessionKey).toBeUndefined(); diff --git a/extensions/workboard/src/store.ts b/extensions/workboard/src/store.ts index 63ad5b07b83..5af198472ce 100644 --- a/extensions/workboard/src/store.ts +++ b/extensions/workboard/src/store.ts @@ -3,9 +3,12 @@ import { WORKBOARD_EXECUTION_ENGINES, WORKBOARD_EXECUTION_MODES, WORKBOARD_EXECUTION_STATUSES, + WORKBOARD_EVENT_KINDS, WORKBOARD_PRIORITIES, WORKBOARD_STATUSES, type WorkboardCard, + type WorkboardEvent, + type WorkboardEventKind, type WorkboardExecution, type WorkboardExecutionEngine, type WorkboardExecutionMode, @@ -16,6 +19,7 @@ import { const POSITION_STEP = 1000; const MAX_CARDS = 2000; +const MAX_CARD_EVENTS = 50; export type PersistedWorkboardCard = { version: 1; @@ -170,6 +174,52 @@ function normalizeTimestamp(value: unknown, fallback: number): number { : fallback; } +function normalizeEvent(value: unknown): WorkboardEvent | null { + if (!value || typeof value !== "object" || Array.isArray(value)) { + return null; + } + const record = value as Record; + const id = normalizeOptionalString(record.id); + const kind = WORKBOARD_EVENT_KINDS.includes(record.kind as WorkboardEventKind) + ? (record.kind as WorkboardEventKind) + : null; + const at = normalizeTimestamp(record.at, 0); + if (!id || !kind || !at) { + return null; + } + const fromStatus = + typeof record.fromStatus === "string" && + WORKBOARD_STATUSES.includes(record.fromStatus as WorkboardStatus) + ? (record.fromStatus as WorkboardStatus) + : undefined; + const toStatus = + typeof record.toStatus === "string" && + WORKBOARD_STATUSES.includes(record.toStatus as WorkboardStatus) + ? (record.toStatus as WorkboardStatus) + : undefined; + const sessionKey = normalizeOptionalString(record.sessionKey); + const runId = normalizeOptionalString(record.runId); + return { + id, + kind, + at, + ...(fromStatus ? { fromStatus } : {}), + ...(toStatus ? { toStatus } : {}), + ...(sessionKey ? { sessionKey } : {}), + ...(runId ? { runId } : {}), + }; +} + +function normalizeEvents(value: unknown): WorkboardEvent[] { + if (!Array.isArray(value)) { + return []; + } + return value + .map(normalizeEvent) + .filter((event): event is WorkboardEvent => event !== null) + .slice(-MAX_CARD_EVENTS); +} + function normalizeExecution(value: unknown): WorkboardExecution | undefined { if (!value || typeof value !== "object" || Array.isArray(value)) { return undefined; @@ -234,6 +284,60 @@ function compareCards(left: WorkboardCard, right: WorkboardCard): number { return left.createdAt - right.createdAt; } +function cardSessionKey(card: WorkboardCard): string | undefined { + return card.sessionKey ?? card.execution?.sessionKey; +} + +function cardRunId(card: WorkboardCard): string | undefined { + return card.runId ?? card.execution?.runId; +} + +function appendEvent( + card: WorkboardCard, + event: Omit, + at = Date.now(), +): WorkboardEvent[] { + return [ + ...normalizeEvents(card.events), + { + id: randomUUID(), + at, + ...event, + }, + ].slice(-MAX_CARD_EVENTS); +} + +function updateEvent( + existing: WorkboardCard, + next: WorkboardCard, +): Omit { + if (existing.status !== next.status || existing.position !== next.position) { + return { + kind: "moved", + fromStatus: existing.status, + toStatus: next.status, + }; + } + if (cardSessionKey(existing) !== cardSessionKey(next)) { + return { + kind: "linked", + ...(cardSessionKey(next) ? { sessionKey: cardSessionKey(next) } : {}), + }; + } + if ( + existing.execution?.status !== next.execution?.status || + existing.execution?.engine !== next.execution?.engine || + cardRunId(existing) !== cardRunId(next) + ) { + return { + kind: "execution_updated", + ...(cardSessionKey(next) ? { sessionKey: cardSessionKey(next) } : {}), + ...(cardRunId(next) ? { runId: cardRunId(next) } : {}), + }; + } + return { kind: "edited" }; +} + function removeUndefinedCardFields(card: WorkboardCard): WorkboardCard { const next = { ...card }; for (const key of [ @@ -277,10 +381,13 @@ export class WorkboardStore { const now = Date.now(); const status = normalizeStatus(input.status, "todo"); const cards = await this.list(); - const position = - normalizePosition(input.position, 0) || - Math.max(0, ...cards.filter((card) => card.status === status).map((card) => card.position)) + - POSITION_STEP; + const normalizedPosition = normalizePosition(input.position, Number.NaN); + const position = Number.isFinite(normalizedPosition) + ? normalizedPosition + : Math.max( + 0, + ...cards.filter((card) => card.status === status).map((card) => card.position), + ) + POSITION_STEP; const notes = normalizeNotes(input.notes); const agentId = normalizeOptionalString(input.agentId); const sessionKey = normalizeOptionalString(input.sessionKey); @@ -297,6 +404,16 @@ export class WorkboardStore { position, createdAt: now, updatedAt: now, + events: [ + { + id: randomUUID(), + kind: "created", + at: now, + toStatus: status, + ...(sessionKey ? { sessionKey } : {}), + ...(runId ? { runId } : {}), + }, + ], ...(notes ? { notes } : {}), ...(agentId ? { agentId } : {}), ...(sessionKey ? { sessionKey } : {}), @@ -356,6 +473,7 @@ export class WorkboardStore { ...(startedAt ? { startedAt } : {}), ...(completedAt ? { completedAt } : {}), }); + next.events = appendEvent(next, updateEvent(existing, next), now); if (status !== "done") { delete next.completedAt; } diff --git a/extensions/workboard/src/types.ts b/extensions/workboard/src/types.ts index 37da6598b78..4e47395de93 100644 --- a/extensions/workboard/src/types.ts +++ b/extensions/workboard/src/types.ts @@ -17,12 +17,20 @@ export const WORKBOARD_EXECUTION_STATUSES = [ "blocked", "done", ] as const; +export const WORKBOARD_EVENT_KINDS = [ + "created", + "edited", + "moved", + "linked", + "execution_updated", +] as const; export type WorkboardStatus = (typeof WORKBOARD_STATUSES)[number]; export type WorkboardPriority = (typeof WORKBOARD_PRIORITIES)[number]; export type WorkboardExecutionEngine = (typeof WORKBOARD_EXECUTION_ENGINES)[number]; export type WorkboardExecutionMode = (typeof WORKBOARD_EXECUTION_MODES)[number]; export type WorkboardExecutionStatus = (typeof WORKBOARD_EXECUTION_STATUSES)[number]; +export type WorkboardEventKind = (typeof WORKBOARD_EVENT_KINDS)[number]; export type WorkboardExecution = { id: string; @@ -37,6 +45,16 @@ export type WorkboardExecution = { updatedAt: number; }; +export type WorkboardEvent = { + id: string; + kind: WorkboardEventKind; + at: number; + fromStatus?: WorkboardStatus; + toStatus?: WorkboardStatus; + sessionKey?: string; + runId?: string; +}; + export type WorkboardCard = { id: string; title: string; @@ -55,6 +73,7 @@ export type WorkboardCard = { updatedAt: number; startedAt?: number; completedAt?: number; + events?: WorkboardEvent[]; }; export type WorkboardListResult = { diff --git a/ui/src/i18n/.i18n/ar.meta.json b/ui/src/i18n/.i18n/ar.meta.json index b452873031b..0c308484744 100644 --- a/ui/src/i18n/.i18n/ar.meta.json +++ b/ui/src/i18n/.i18n/ar.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-05-28T18:48:31.545Z", + "generatedAt": "2026-05-28T23:49:35.013Z", "locale": "ar", "model": "gpt-5.5", "provider": "openai", - "sourceHash": "7c867296be27a09ab0e35f76d2518f479e24ab667179c5b3fabf83d6c57f3ef9", - "totalKeys": 1158, - "translatedKeys": 1158, + "sourceHash": "b966847cbbaca64097b1e105dcc26ad803434cfb2d3b39cae3edc2be3874cfda", + "totalKeys": 1238, + "translatedKeys": 1238, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/de.meta.json b/ui/src/i18n/.i18n/de.meta.json index fd3aee35db0..16f07f27514 100644 --- a/ui/src/i18n/.i18n/de.meta.json +++ b/ui/src/i18n/.i18n/de.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-05-28T18:48:21.334Z", + "generatedAt": "2026-05-28T23:49:02.619Z", "locale": "de", "model": "gpt-5.5", "provider": "openai", - "sourceHash": "7c867296be27a09ab0e35f76d2518f479e24ab667179c5b3fabf83d6c57f3ef9", - "totalKeys": 1158, - "translatedKeys": 1158, + "sourceHash": "b966847cbbaca64097b1e105dcc26ad803434cfb2d3b39cae3edc2be3874cfda", + "totalKeys": 1238, + "translatedKeys": 1238, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/es.meta.json b/ui/src/i18n/.i18n/es.meta.json index 03fb6a9d038..0dc996019d3 100644 --- a/ui/src/i18n/.i18n/es.meta.json +++ b/ui/src/i18n/.i18n/es.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-05-28T18:48:23.802Z", + "generatedAt": "2026-05-28T23:49:07.283Z", "locale": "es", "model": "gpt-5.5", "provider": "openai", - "sourceHash": "7c867296be27a09ab0e35f76d2518f479e24ab667179c5b3fabf83d6c57f3ef9", - "totalKeys": 1158, - "translatedKeys": 1158, + "sourceHash": "b966847cbbaca64097b1e105dcc26ad803434cfb2d3b39cae3edc2be3874cfda", + "totalKeys": 1238, + "translatedKeys": 1238, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/fa.meta.json b/ui/src/i18n/.i18n/fa.meta.json index 49fd54d9561..f9e48a4bf30 100644 --- a/ui/src/i18n/.i18n/fa.meta.json +++ b/ui/src/i18n/.i18n/fa.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-05-28T18:48:52.280Z", + "generatedAt": "2026-05-28T23:51:04.444Z", "locale": "fa", "model": "gpt-5.5", "provider": "openai", - "sourceHash": "7c867296be27a09ab0e35f76d2518f479e24ab667179c5b3fabf83d6c57f3ef9", - "totalKeys": 1158, - "translatedKeys": 1158, + "sourceHash": "b966847cbbaca64097b1e105dcc26ad803434cfb2d3b39cae3edc2be3874cfda", + "totalKeys": 1238, + "translatedKeys": 1238, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/fr.meta.json b/ui/src/i18n/.i18n/fr.meta.json index b0bd2932033..de7de045aac 100644 --- a/ui/src/i18n/.i18n/fr.meta.json +++ b/ui/src/i18n/.i18n/fr.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-05-28T18:48:29.028Z", + "generatedAt": "2026-05-28T23:49:27.030Z", "locale": "fr", "model": "gpt-5.5", "provider": "openai", - "sourceHash": "7c867296be27a09ab0e35f76d2518f479e24ab667179c5b3fabf83d6c57f3ef9", - "totalKeys": 1158, - "translatedKeys": 1158, + "sourceHash": "b966847cbbaca64097b1e105dcc26ad803434cfb2d3b39cae3edc2be3874cfda", + "totalKeys": 1238, + "translatedKeys": 1238, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/id.meta.json b/ui/src/i18n/.i18n/id.meta.json index 4dcf40c0db0..ad68b5a06b7 100644 --- a/ui/src/i18n/.i18n/id.meta.json +++ b/ui/src/i18n/.i18n/id.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-05-28T18:48:42.075Z", + "generatedAt": "2026-05-28T23:50:00.392Z", "locale": "id", "model": "gpt-5.5", "provider": "openai", - "sourceHash": "7c867296be27a09ab0e35f76d2518f479e24ab667179c5b3fabf83d6c57f3ef9", - "totalKeys": 1158, - "translatedKeys": 1158, + "sourceHash": "b966847cbbaca64097b1e105dcc26ad803434cfb2d3b39cae3edc2be3874cfda", + "totalKeys": 1238, + "translatedKeys": 1238, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/it.meta.json b/ui/src/i18n/.i18n/it.meta.json index 1aa8e0f52ce..141e15cb789 100644 --- a/ui/src/i18n/.i18n/it.meta.json +++ b/ui/src/i18n/.i18n/it.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-05-28T18:48:33.241Z", + "generatedAt": "2026-05-28T23:49:40.467Z", "locale": "it", "model": "gpt-5.5", "provider": "openai", - "sourceHash": "7c867296be27a09ab0e35f76d2518f479e24ab667179c5b3fabf83d6c57f3ef9", - "totalKeys": 1158, - "translatedKeys": 1158, + "sourceHash": "b966847cbbaca64097b1e105dcc26ad803434cfb2d3b39cae3edc2be3874cfda", + "totalKeys": 1238, + "translatedKeys": 1238, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/ja-JP.meta.json b/ui/src/i18n/.i18n/ja-JP.meta.json index 0d423e3f4a2..8d5bb802f2f 100644 --- a/ui/src/i18n/.i18n/ja-JP.meta.json +++ b/ui/src/i18n/.i18n/ja-JP.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-05-28T18:48:25.623Z", + "generatedAt": "2026-05-28T23:49:14.097Z", "locale": "ja-JP", "model": "gpt-5.5", "provider": "openai", - "sourceHash": "7c867296be27a09ab0e35f76d2518f479e24ab667179c5b3fabf83d6c57f3ef9", - "totalKeys": 1158, - "translatedKeys": 1158, + "sourceHash": "b966847cbbaca64097b1e105dcc26ad803434cfb2d3b39cae3edc2be3874cfda", + "totalKeys": 1238, + "translatedKeys": 1238, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/ko.meta.json b/ui/src/i18n/.i18n/ko.meta.json index 2279c85cc01..77251874f9d 100644 --- a/ui/src/i18n/.i18n/ko.meta.json +++ b/ui/src/i18n/.i18n/ko.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-05-28T18:48:27.331Z", + "generatedAt": "2026-05-28T23:49:19.581Z", "locale": "ko", "model": "gpt-5.5", "provider": "openai", - "sourceHash": "7c867296be27a09ab0e35f76d2518f479e24ab667179c5b3fabf83d6c57f3ef9", - "totalKeys": 1158, - "translatedKeys": 1158, + "sourceHash": "b966847cbbaca64097b1e105dcc26ad803434cfb2d3b39cae3edc2be3874cfda", + "totalKeys": 1238, + "translatedKeys": 1238, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/nl.meta.json b/ui/src/i18n/.i18n/nl.meta.json index 46d09dcbf09..bf1fc7fb7c9 100644 --- a/ui/src/i18n/.i18n/nl.meta.json +++ b/ui/src/i18n/.i18n/nl.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-05-28T18:48:49.875Z", + "generatedAt": "2026-05-28T23:50:48.437Z", "locale": "nl", "model": "gpt-5.5", "provider": "openai", - "sourceHash": "7c867296be27a09ab0e35f76d2518f479e24ab667179c5b3fabf83d6c57f3ef9", - "totalKeys": 1158, - "translatedKeys": 1158, + "sourceHash": "b966847cbbaca64097b1e105dcc26ad803434cfb2d3b39cae3edc2be3874cfda", + "totalKeys": 1238, + "translatedKeys": 1238, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/pl.meta.json b/ui/src/i18n/.i18n/pl.meta.json index f7d48f93d81..82b21aec81a 100644 --- a/ui/src/i18n/.i18n/pl.meta.json +++ b/ui/src/i18n/.i18n/pl.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-05-28T18:48:43.834Z", + "generatedAt": "2026-05-28T23:50:12.160Z", "locale": "pl", "model": "gpt-5.5", "provider": "openai", - "sourceHash": "7c867296be27a09ab0e35f76d2518f479e24ab667179c5b3fabf83d6c57f3ef9", - "totalKeys": 1158, - "translatedKeys": 1158, + "sourceHash": "b966847cbbaca64097b1e105dcc26ad803434cfb2d3b39cae3edc2be3874cfda", + "totalKeys": 1238, + "translatedKeys": 1238, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/pt-BR.meta.json b/ui/src/i18n/.i18n/pt-BR.meta.json index 261b49e1c5d..0ce1c40fd72 100644 --- a/ui/src/i18n/.i18n/pt-BR.meta.json +++ b/ui/src/i18n/.i18n/pt-BR.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-05-28T18:48:18.990Z", + "generatedAt": "2026-05-28T23:48:56.314Z", "locale": "pt-BR", "model": "gpt-5.5", "provider": "openai", - "sourceHash": "7c867296be27a09ab0e35f76d2518f479e24ab667179c5b3fabf83d6c57f3ef9", - "totalKeys": 1158, - "translatedKeys": 1158, + "sourceHash": "b966847cbbaca64097b1e105dcc26ad803434cfb2d3b39cae3edc2be3874cfda", + "totalKeys": 1238, + "translatedKeys": 1238, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/raw-copy-baseline.json b/ui/src/i18n/.i18n/raw-copy-baseline.json index da5654d201c..4d5adfcf128 100644 --- a/ui/src/i18n/.i18n/raw-copy-baseline.json +++ b/ui/src/i18n/.i18n/raw-copy-baseline.json @@ -4655,83 +4655,6 @@ "name": "aria-label", "path": "ui/src/ui/views/usage-render-overview.ts", "text": "Remove session filter" - }, - { - "count": 1, - "kind": "html-attribute", - "name": "placeholder", - "path": "ui/src/ui/views/workboard.ts", - "text": "Card title" - }, - { - "count": 1, - "kind": "html-attribute", - "name": "placeholder", - "path": "ui/src/ui/views/workboard.ts", - "text": "Notes, acceptance criteria, links" - }, - { - "count": 1, - "kind": "html-attribute", - "name": "placeholder", - "path": "ui/src/ui/views/workboard.ts", - "text": "Search cards" - }, - { - "count": 1, - "kind": "html-attribute", - "name": "title", - "path": "ui/src/ui/views/workboard.ts", - "text": "Delete card" - }, - { - "count": 1, - "kind": "html-attribute", - "name": "title", - "path": "ui/src/ui/views/workboard.ts", - "text": "Open session" - }, - { - "count": 1, - "kind": "html-attribute", - "name": "title", - "path": "ui/src/ui/views/workboard.ts", - "text": "Start session" - }, - { - "count": 1, - "kind": "html-text", - "name": "text", - "path": "ui/src/ui/views/workboard.ts", - "text": "All priorities" - }, - { - "count": 1, - "kind": "html-text", - "name": "text", - "path": "ui/src/ui/views/workboard.ts", - "text": "default agent" - }, - { - "count": 1, - "kind": "html-text", - "name": "text", - "path": "ui/src/ui/views/workboard.ts", - "text": "Default agent" - }, - { - "count": 1, - "kind": "html-text", - "name": "text", - "path": "ui/src/ui/views/workboard.ts", - "text": "Drop work here" - }, - { - "count": 1, - "kind": "html-text", - "name": "text", - "path": "ui/src/ui/views/workboard.ts", - "text": "live" } ] } diff --git a/ui/src/i18n/.i18n/th.meta.json b/ui/src/i18n/.i18n/th.meta.json index 3d70f326457..b48f69834a6 100644 --- a/ui/src/i18n/.i18n/th.meta.json +++ b/ui/src/i18n/.i18n/th.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-05-28T18:48:45.871Z", + "generatedAt": "2026-05-28T23:50:26.593Z", "locale": "th", "model": "gpt-5.5", "provider": "openai", - "sourceHash": "7c867296be27a09ab0e35f76d2518f479e24ab667179c5b3fabf83d6c57f3ef9", - "totalKeys": 1158, - "translatedKeys": 1158, + "sourceHash": "b966847cbbaca64097b1e105dcc26ad803434cfb2d3b39cae3edc2be3874cfda", + "totalKeys": 1238, + "translatedKeys": 1238, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/tr.meta.json b/ui/src/i18n/.i18n/tr.meta.json index 584576748f9..7cfa4d9fda0 100644 --- a/ui/src/i18n/.i18n/tr.meta.json +++ b/ui/src/i18n/.i18n/tr.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-05-28T18:48:35.897Z", + "generatedAt": "2026-05-28T23:49:47.972Z", "locale": "tr", "model": "gpt-5.5", "provider": "openai", - "sourceHash": "7c867296be27a09ab0e35f76d2518f479e24ab667179c5b3fabf83d6c57f3ef9", - "totalKeys": 1158, - "translatedKeys": 1158, + "sourceHash": "b966847cbbaca64097b1e105dcc26ad803434cfb2d3b39cae3edc2be3874cfda", + "totalKeys": 1238, + "translatedKeys": 1238, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/uk.meta.json b/ui/src/i18n/.i18n/uk.meta.json index c8c308b8b74..97bb3f53283 100644 --- a/ui/src/i18n/.i18n/uk.meta.json +++ b/ui/src/i18n/.i18n/uk.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-05-28T18:48:39.972Z", + "generatedAt": "2026-05-28T23:49:54.214Z", "locale": "uk", "model": "gpt-5.5", "provider": "openai", - "sourceHash": "7c867296be27a09ab0e35f76d2518f479e24ab667179c5b3fabf83d6c57f3ef9", - "totalKeys": 1158, - "translatedKeys": 1158, + "sourceHash": "b966847cbbaca64097b1e105dcc26ad803434cfb2d3b39cae3edc2be3874cfda", + "totalKeys": 1238, + "translatedKeys": 1238, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/vi.meta.json b/ui/src/i18n/.i18n/vi.meta.json index f139e0b176a..8329bd20fa6 100644 --- a/ui/src/i18n/.i18n/vi.meta.json +++ b/ui/src/i18n/.i18n/vi.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-05-28T18:48:47.804Z", + "generatedAt": "2026-05-28T23:50:37.343Z", "locale": "vi", "model": "gpt-5.5", "provider": "openai", - "sourceHash": "7c867296be27a09ab0e35f76d2518f479e24ab667179c5b3fabf83d6c57f3ef9", - "totalKeys": 1158, - "translatedKeys": 1158, + "sourceHash": "b966847cbbaca64097b1e105dcc26ad803434cfb2d3b39cae3edc2be3874cfda", + "totalKeys": 1238, + "translatedKeys": 1238, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/zh-CN.meta.json b/ui/src/i18n/.i18n/zh-CN.meta.json index a183b16a9ea..c018ca54202 100644 --- a/ui/src/i18n/.i18n/zh-CN.meta.json +++ b/ui/src/i18n/.i18n/zh-CN.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-05-28T18:48:15.047Z", + "generatedAt": "2026-05-28T23:48:43.195Z", "locale": "zh-CN", "model": "gpt-5.5", "provider": "openai", - "sourceHash": "7c867296be27a09ab0e35f76d2518f479e24ab667179c5b3fabf83d6c57f3ef9", - "totalKeys": 1158, - "translatedKeys": 1158, + "sourceHash": "b966847cbbaca64097b1e105dcc26ad803434cfb2d3b39cae3edc2be3874cfda", + "totalKeys": 1238, + "translatedKeys": 1238, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/zh-TW.meta.json b/ui/src/i18n/.i18n/zh-TW.meta.json index 643dd295946..9d1c18834c9 100644 --- a/ui/src/i18n/.i18n/zh-TW.meta.json +++ b/ui/src/i18n/.i18n/zh-TW.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-05-28T18:48:17.187Z", + "generatedAt": "2026-05-28T23:48:49.571Z", "locale": "zh-TW", "model": "gpt-5.5", "provider": "openai", - "sourceHash": "7c867296be27a09ab0e35f76d2518f479e24ab667179c5b3fabf83d6c57f3ef9", - "totalKeys": 1158, - "translatedKeys": 1158, + "sourceHash": "b966847cbbaca64097b1e105dcc26ad803434cfb2d3b39cae3edc2be3874cfda", + "totalKeys": 1238, + "translatedKeys": 1238, "workflow": 1 } diff --git a/ui/src/i18n/locales/ar.ts b/ui/src/i18n/locales/ar.ts index e624a6c89de..dcc97a22f42 100644 --- a/ui/src/i18n/locales/ar.ts +++ b/ui/src/i18n/locales/ar.ts @@ -492,6 +492,18 @@ export const ar: TranslationMap = { noLinkedSession: "لا توجد جلسة مرتبطة", stopSession: "إيقاف الجلسة", editCard: "تعديل البطاقة", + editCardHelp: "حدّث بيانات تعريف قائمة الانتظار وتسليم الجلسة.", + newCard: "بطاقة جديدة", + newCardHelp: "أضف العمل إلى قائمة الانتظار لجلسة وكيل.", + deleteCard: "حذف البطاقة", + openSession: "فتح الجلسة", + openLinkedSession: "فتح الجلسة المرتبطة", + defaultAgent: "الوكيل الافتراضي", + runEngine: "تشغيل {engine}", + openEngine: "فتح {engine}", + runDefaultAgent: "تشغيل الوكيل الافتراضي", + start: "بدء", + live: "مباشر", fieldTitle: "العنوان", fieldNotes: "ملاحظات", fieldStatus: "الحالة", @@ -499,7 +511,12 @@ export const ar: TranslationMap = { fieldAgent: "الوكيل", fieldSession: "الجلسة", fieldLabels: "التصنيفات", + titlePlaceholder: "عنوان البطاقة", + notesPlaceholder: "ملاحظات، معايير القبول، روابط", labelsPlaceholder: "ui, docs", + searchPlaceholder: "البحث في البطاقات", + allPriorities: "كل الأولويات", + emptyColumn: "أفلِت العمل هنا", lifecycleUnlinked: "لا توجد جلسة", lifecycleUnlinkedDetail: "ابدأ جلسة أو اربطها", lifecycleMissing: "الجلسة مفقودة", @@ -512,6 +529,13 @@ export const ar: TranslationMap = { lifecycleDoneDetail: "تم النقل إلى المراجعة", lifecycleNeedsReview: "تحتاج إلى مراجعة", lifecycleNeedsReviewDetail: "توقف التشغيل أو فشل", + eventsLabel: "أحداث البطاقة", + eventCreated: "تم الإنشاء", + eventEdited: "تم التعديل", + eventMoved: "تم النقل", + eventMovedTo: "تم النقل إلى {status}", + eventLinked: "تم ربط الجلسة", + eventExecutionUpdated: "تم تحديث الوكيل", gameButton: "لعبة مصغرة", gameTitle: "مطاردة البطاقات", gameStart: "صِل إلى مربع الإطلاق.", @@ -1172,7 +1196,7 @@ export const ar: TranslationMap = { }, queue: { retry: "إعادة المحاولة", - retrySend: "إعادة الإرسال", + retrySend: "إعادة محاولة الإرسال", retryQueuedMessage: "إعادة محاولة الرسالة في قائمة الانتظار", }, composer: { diff --git a/ui/src/i18n/locales/de.ts b/ui/src/i18n/locales/de.ts index 1e33f0f0af5..2feeccb3438 100644 --- a/ui/src/i18n/locales/de.ts +++ b/ui/src/i18n/locales/de.ts @@ -496,6 +496,18 @@ export const de: TranslationMap = { noLinkedSession: "Keine verknüpfte Sitzung", stopSession: "Sitzung stoppen", editCard: "Karte bearbeiten", + editCardHelp: "Warteschlangen-Metadaten und Sitzungsübergabe aktualisieren.", + newCard: "Neue Karte", + newCardHelp: "Arbeit für eine Agentensitzung in die Warteschlange einreihen.", + deleteCard: "Karte löschen", + openSession: "Sitzung öffnen", + openLinkedSession: "Verknüpfte Sitzung öffnen", + defaultAgent: "Standard-Agent", + runEngine: "{engine} ausführen", + openEngine: "{engine} öffnen", + runDefaultAgent: "Standard-Agent ausführen", + start: "Starten", + live: "live", fieldTitle: "Titel", fieldNotes: "Notizen", fieldStatus: "Status", @@ -503,7 +515,12 @@ export const de: TranslationMap = { fieldAgent: "Agent", fieldSession: "Sitzung", fieldLabels: "Labels", + titlePlaceholder: "Kartentitel", + notesPlaceholder: "Notizen, Akzeptanzkriterien, Links", labelsPlaceholder: "ui, docs", + searchPlaceholder: "Karten suchen", + allPriorities: "Alle Prioritäten", + emptyColumn: "Arbeit hier ablegen", lifecycleUnlinked: "Keine Sitzung", lifecycleUnlinkedDetail: "Sitzung starten oder verknüpfen", lifecycleMissing: "Sitzung fehlt", @@ -516,6 +533,13 @@ export const de: TranslationMap = { lifecycleDoneDetail: "Zur Überprüfung verschoben", lifecycleNeedsReview: "Überprüfung erforderlich", lifecycleNeedsReviewDetail: "Lauf gestoppt oder fehlgeschlagen", + eventsLabel: "Kartenereignisse", + eventCreated: "Erstellt", + eventEdited: "Bearbeitet", + eventMoved: "Verschoben", + eventMovedTo: "Verschoben nach {status}", + eventLinked: "Sitzung verknüpft", + eventExecutionUpdated: "Agent aktualisiert", gameButton: "Minispiel", gameTitle: "Card Chase", gameStart: "Erreiche das Startfeld.", @@ -1195,9 +1219,9 @@ export const de: TranslationMap = { sendMessage: "Send message", }, queue: { - retry: "Erneut versuchen", - retrySend: "Senden erneut versuchen", - retryQueuedMessage: "Nachricht in der Warteschlange erneut versuchen", + retry: "Wiederholen", + retrySend: "Senden wiederholen", + retryQueuedMessage: "Nachricht in der Warteschlange erneut senden", }, composer: { placeholder: "Message {name} (Enter to send)", diff --git a/ui/src/i18n/locales/en.ts b/ui/src/i18n/locales/en.ts index 1e393ddd133..0ed2273f425 100644 --- a/ui/src/i18n/locales/en.ts +++ b/ui/src/i18n/locales/en.ts @@ -491,6 +491,18 @@ export const en: TranslationMap = { noLinkedSession: "No linked session", stopSession: "Stop session", editCard: "Edit card", + editCardHelp: "Update queue metadata and session handoff.", + newCard: "New card", + newCardHelp: "Queue work for an agent session.", + deleteCard: "Delete card", + openSession: "Open session", + openLinkedSession: "Open linked session", + defaultAgent: "Default agent", + runEngine: "Run {engine}", + openEngine: "Open {engine}", + runDefaultAgent: "Run default agent", + start: "Start", + live: "live", fieldTitle: "Title", fieldNotes: "Notes", fieldStatus: "Status", @@ -498,7 +510,12 @@ export const en: TranslationMap = { fieldAgent: "Agent", fieldSession: "Session", fieldLabels: "Labels", + titlePlaceholder: "Card title", + notesPlaceholder: "Notes, acceptance criteria, links", labelsPlaceholder: "ui, docs", + searchPlaceholder: "Search cards", + allPriorities: "All priorities", + emptyColumn: "Drop work here", lifecycleUnlinked: "No session", lifecycleUnlinkedDetail: "Start or link a session", lifecycleMissing: "Session missing", @@ -511,6 +528,13 @@ export const en: TranslationMap = { lifecycleDoneDetail: "Moved to review", lifecycleNeedsReview: "Needs review", lifecycleNeedsReviewDetail: "Run stopped or failed", + eventsLabel: "Card events", + eventCreated: "Created", + eventEdited: "Edited", + eventMoved: "Moved", + eventMovedTo: "Moved to {status}", + eventLinked: "Linked session", + eventExecutionUpdated: "Agent updated", gameButton: "Mini game", gameTitle: "Card Chase", gameStart: "Reach the launch tile.", diff --git a/ui/src/i18n/locales/es.ts b/ui/src/i18n/locales/es.ts index 15622275a26..0e8816d83a2 100644 --- a/ui/src/i18n/locales/es.ts +++ b/ui/src/i18n/locales/es.ts @@ -493,6 +493,18 @@ export const es: TranslationMap = { noLinkedSession: "Sin sesión vinculada", stopSession: "Detener sesión", editCard: "Editar tarjeta", + editCardHelp: "Actualiza los metadatos de la cola y la transferencia de sesión.", + newCard: "Nueva tarjeta", + newCardHelp: "Pon trabajo en cola para una sesión de agente.", + deleteCard: "Eliminar tarjeta", + openSession: "Abrir sesión", + openLinkedSession: "Abrir sesión vinculada", + defaultAgent: "Agente predeterminado", + runEngine: "Ejecutar {engine}", + openEngine: "Abrir {engine}", + runDefaultAgent: "Ejecutar agente predeterminado", + start: "Iniciar", + live: "en vivo", fieldTitle: "Título", fieldNotes: "Notas", fieldStatus: "Estado", @@ -500,7 +512,12 @@ export const es: TranslationMap = { fieldAgent: "Agente", fieldSession: "Sesión", fieldLabels: "Etiquetas", + titlePlaceholder: "Título de la tarjeta", + notesPlaceholder: "Notas, criterios de aceptación, enlaces", labelsPlaceholder: "ui, docs", + searchPlaceholder: "Buscar tarjetas", + allPriorities: "Todas las prioridades", + emptyColumn: "Suelta el trabajo aquí", lifecycleUnlinked: "Sin sesión", lifecycleUnlinkedDetail: "Inicia o vincula una sesión", lifecycleMissing: "Falta la sesión", @@ -513,6 +530,13 @@ export const es: TranslationMap = { lifecycleDoneDetail: "Movido a revisión", lifecycleNeedsReview: "Necesita revisión", lifecycleNeedsReviewDetail: "La ejecución se detuvo o falló", + eventsLabel: "Eventos de la tarjeta", + eventCreated: "Creada", + eventEdited: "Editada", + eventMoved: "Movido", + eventMovedTo: "Movido a {status}", + eventLinked: "Sesión vinculada", + eventExecutionUpdated: "Agente actualizado", gameButton: "Minijuego", gameTitle: "Card Chase", gameStart: "Alcanza la casilla de lanzamiento.", diff --git a/ui/src/i18n/locales/fa.ts b/ui/src/i18n/locales/fa.ts index 9f2eb15bd88..f4c3fc38afe 100644 --- a/ui/src/i18n/locales/fa.ts +++ b/ui/src/i18n/locales/fa.ts @@ -493,6 +493,32 @@ export const fa: TranslationMap = { }, noLinkedSession: "جلسه‌ای پیوند نشده است", stopSession: "توقف جلسه", + editCard: "ویرایش کارت", + editCardHelp: "به‌روزرسانی فرادادهٔ صف و واگذاری نشست.", + newCard: "کارت جدید", + newCardHelp: "کار را برای یک نشست عامل در صف قرار دهید.", + deleteCard: "حذف کارت", + openSession: "باز کردن نشست", + openLinkedSession: "باز کردن نشست پیوندشده", + defaultAgent: "عامل پیش‌فرض", + runEngine: "اجرای {engine}", + openEngine: "باز کردن {engine}", + runDefaultAgent: "اجرای عامل پیش‌فرض", + start: "شروع", + live: "زنده", + fieldTitle: "عنوان", + fieldNotes: "یادداشت‌ها", + fieldStatus: "وضعیت", + fieldPriority: "اولویت", + fieldAgent: "عامل", + fieldSession: "نشست", + fieldLabels: "برچسب‌ها", + titlePlaceholder: "عنوان کارت", + notesPlaceholder: "یادداشت‌ها، معیارهای پذیرش، پیوندها", + labelsPlaceholder: "ui, docs", + searchPlaceholder: "جستجوی کارت‌ها", + allPriorities: "همه اولویت‌ها", + emptyColumn: "کار را اینجا رها کنید", lifecycleUnlinked: "بدون جلسه", lifecycleUnlinkedDetail: "یک جلسه را شروع یا پیوند کنید", lifecycleMissing: "جلسه پیدا نشد", @@ -505,6 +531,32 @@ export const fa: TranslationMap = { lifecycleDoneDetail: "به بازبینی منتقل شد", lifecycleNeedsReview: "نیازمند بازبینی", lifecycleNeedsReviewDetail: "اجرا متوقف شد یا ناموفق بود", + eventsLabel: "رویدادهای کارت", + eventCreated: "ایجاد شد", + eventEdited: "ویرایش شد", + eventMoved: "منتقل شد", + eventMovedTo: "به {status} منتقل شد", + eventLinked: "نشست پیوند داده شد", + eventExecutionUpdated: "Agent به‌روزرسانی شد", + gameButton: "بازی کوچک", + gameTitle: "تعقیب کارت", + gameStart: "به کاشی راه‌اندازی برسید.", + gameBoundary: "به مرز رسیدید.", + gameBlocked: "مسدود شده است.", + gameContinue: "ادامه دهید.", + gameWin: "راه‌اندازی با موفقیت انجام شد.", + gameMoves: "حرکت‌ها {count}", + gameWins: "بردها {count}", + gameBoard: "صفحه Card Chase", + gameControls: "کنترل‌های Card Chase", + gameAgent: "عامل", + gameLaunch: "راه‌اندازی", + gameBlockedCell: "مسدود", + gameOpenCell: "باز", + gameMoveUp: "حرکت به بالا", + gameMoveLeft: "حرکت به چپ", + gameMoveDown: "حرکت به پایین", + gameMoveRight: "حرکت به راست", }, overview: { access: { @@ -1160,9 +1212,9 @@ export const fa: TranslationMap = { sendMessage: "Send message", }, queue: { - retry: "تلاش مجدد", - retrySend: "ارسال مجدد", - retryQueuedMessage: "تلاش مجدد برای پیام در صف", + retry: "تلاش دوباره", + retrySend: "تلاش دوباره برای ارسال", + retryQueuedMessage: "تلاش دوباره برای پیام در صف", }, composer: { placeholder: "Message {name} (Enter to send)", diff --git a/ui/src/i18n/locales/fr.ts b/ui/src/i18n/locales/fr.ts index a914402fb86..6add4d16bcf 100644 --- a/ui/src/i18n/locales/fr.ts +++ b/ui/src/i18n/locales/fr.ts @@ -495,6 +495,18 @@ export const fr: TranslationMap = { noLinkedSession: "Aucune session liée", stopSession: "Arrêter la session", editCard: "Modifier la carte", + editCardHelp: "Mettez à jour les métadonnées de la file d’attente et le transfert de session.", + newCard: "Nouvelle carte", + newCardHelp: "Mettez du travail en file d’attente pour une session d’agent.", + deleteCard: "Supprimer la carte", + openSession: "Ouvrir la session", + openLinkedSession: "Ouvrir la session liée", + defaultAgent: "Agent par défaut", + runEngine: "Exécuter {engine}", + openEngine: "Ouvrir {engine}", + runDefaultAgent: "Exécuter l’agent par défaut", + start: "Démarrer", + live: "en direct", fieldTitle: "Titre", fieldNotes: "Notes", fieldStatus: "Statut", @@ -502,7 +514,12 @@ export const fr: TranslationMap = { fieldAgent: "Agent", fieldSession: "Session", fieldLabels: "Étiquettes", + titlePlaceholder: "Titre de la carte", + notesPlaceholder: "Notes, critères d’acceptation, liens", labelsPlaceholder: "ui, docs", + searchPlaceholder: "Rechercher des cartes", + allPriorities: "Toutes les priorités", + emptyColumn: "Déposez le travail ici", lifecycleUnlinked: "Aucune session", lifecycleUnlinkedDetail: "Démarrer ou lier une session", lifecycleMissing: "Session manquante", @@ -515,6 +532,13 @@ export const fr: TranslationMap = { lifecycleDoneDetail: "Déplacé vers la révision", lifecycleNeedsReview: "Nécessite une révision", lifecycleNeedsReviewDetail: "Exécution arrêtée ou échouée", + eventsLabel: "Événements de la carte", + eventCreated: "Créé", + eventEdited: "Modifié", + eventMoved: "Déplacé", + eventMovedTo: "Déplacé vers {status}", + eventLinked: "Session liée", + eventExecutionUpdated: "Agent mis à jour", gameButton: "Mini-jeu", gameTitle: "Card Chase", gameStart: "Atteignez la tuile de lancement.", diff --git a/ui/src/i18n/locales/id.ts b/ui/src/i18n/locales/id.ts index a1603c13a1b..70209bb4e18 100644 --- a/ui/src/i18n/locales/id.ts +++ b/ui/src/i18n/locales/id.ts @@ -493,6 +493,18 @@ export const id: TranslationMap = { noLinkedSession: "Tidak ada sesi tertaut", stopSession: "Hentikan sesi", editCard: "Edit kartu", + editCardHelp: "Perbarui metadata antrean dan serah terima sesi.", + newCard: "Kartu baru", + newCardHelp: "Antrekan pekerjaan untuk sesi agen.", + deleteCard: "Hapus kartu", + openSession: "Buka sesi", + openLinkedSession: "Buka sesi tertaut", + defaultAgent: "Agen default", + runEngine: "Jalankan {engine}", + openEngine: "Buka {engine}", + runDefaultAgent: "Jalankan agen default", + start: "Mulai", + live: "langsung", fieldTitle: "Judul", fieldNotes: "Catatan", fieldStatus: "Status", @@ -500,7 +512,12 @@ export const id: TranslationMap = { fieldAgent: "Agen", fieldSession: "Sesi", fieldLabels: "Label", + titlePlaceholder: "Judul kartu", + notesPlaceholder: "Catatan, kriteria penerimaan, tautan", labelsPlaceholder: "ui, docs", + searchPlaceholder: "Cari kartu", + allPriorities: "Semua prioritas", + emptyColumn: "Letakkan pekerjaan di sini", lifecycleUnlinked: "Tidak ada sesi", lifecycleUnlinkedDetail: "Mulai atau tautkan sesi", lifecycleMissing: "Sesi hilang", @@ -513,6 +530,13 @@ export const id: TranslationMap = { lifecycleDoneDetail: "Dipindahkan ke peninjauan", lifecycleNeedsReview: "Perlu ditinjau", lifecycleNeedsReviewDetail: "Run dihentikan atau gagal", + eventsLabel: "Peristiwa kartu", + eventCreated: "Dibuat", + eventEdited: "Diedit", + eventMoved: "Dipindahkan", + eventMovedTo: "Dipindahkan ke {status}", + eventLinked: "Sesi ditautkan", + eventExecutionUpdated: "Agen diperbarui", gameButton: "Mini game", gameTitle: "Card Chase", gameStart: "Capai petak peluncuran.", diff --git a/ui/src/i18n/locales/it.ts b/ui/src/i18n/locales/it.ts index 72c5f8e45e9..9d4494a4a1e 100644 --- a/ui/src/i18n/locales/it.ts +++ b/ui/src/i18n/locales/it.ts @@ -495,6 +495,18 @@ export const it: TranslationMap = { noLinkedSession: "Nessuna sessione collegata", stopSession: "Interrompi sessione", editCard: "Modifica scheda", + editCardHelp: "Aggiorna i metadati della coda e il passaggio di sessione.", + newCard: "Nuova scheda", + newCardHelp: "Metti in coda il lavoro per una sessione dell'agente.", + deleteCard: "Elimina scheda", + openSession: "Apri sessione", + openLinkedSession: "Apri sessione collegata", + defaultAgent: "Agente predefinito", + runEngine: "Esegui {engine}", + openEngine: "Apri {engine}", + runDefaultAgent: "Esegui agente predefinito", + start: "Avvia", + live: "live", fieldTitle: "Titolo", fieldNotes: "Note", fieldStatus: "Stato", @@ -502,7 +514,12 @@ export const it: TranslationMap = { fieldAgent: "Agente", fieldSession: "Sessione", fieldLabels: "Etichette", + titlePlaceholder: "Titolo scheda", + notesPlaceholder: "Note, criteri di accettazione, link", labelsPlaceholder: "ui, docs", + searchPlaceholder: "Cerca schede", + allPriorities: "Tutte le priorità", + emptyColumn: "Rilascia qui il lavoro", lifecycleUnlinked: "Nessuna sessione", lifecycleUnlinkedDetail: "Avvia o collega una sessione", lifecycleMissing: "Sessione mancante", @@ -515,6 +532,13 @@ export const it: TranslationMap = { lifecycleDoneDetail: "Spostata in revisione", lifecycleNeedsReview: "Richiede revisione", lifecycleNeedsReviewDetail: "Esecuzione interrotta o non riuscita", + eventsLabel: "Eventi della scheda", + eventCreated: "Creato", + eventEdited: "Modificato", + eventMoved: "Spostato", + eventMovedTo: "Spostato in {status}", + eventLinked: "Sessione collegata", + eventExecutionUpdated: "Agente aggiornato", gameButton: "Mini gioco", gameTitle: "Card Chase", gameStart: "Raggiungi la casella di lancio.", diff --git a/ui/src/i18n/locales/ja-JP.ts b/ui/src/i18n/locales/ja-JP.ts index 798e0b39281..c0d968163fa 100644 --- a/ui/src/i18n/locales/ja-JP.ts +++ b/ui/src/i18n/locales/ja-JP.ts @@ -496,6 +496,18 @@ export const ja_JP: TranslationMap = { noLinkedSession: "リンクされたセッションがありません", stopSession: "セッションを停止", editCard: "カードを編集", + editCardHelp: "キューのメタデータとセッションの引き継ぎを更新します。", + newCard: "新規カード", + newCardHelp: "エージェントセッションの作業をキューに追加します。", + deleteCard: "カードを削除", + openSession: "セッションを開く", + openLinkedSession: "リンクされたセッションを開く", + defaultAgent: "デフォルトエージェント", + runEngine: "{engine} を実行", + openEngine: "{engine} を開く", + runDefaultAgent: "デフォルトエージェントを実行", + start: "開始", + live: "ライブ", fieldTitle: "タイトル", fieldNotes: "メモ", fieldStatus: "ステータス", @@ -503,7 +515,12 @@ export const ja_JP: TranslationMap = { fieldAgent: "エージェント", fieldSession: "セッション", fieldLabels: "ラベル", + titlePlaceholder: "カードのタイトル", + notesPlaceholder: "メモ、受け入れ条件、リンク", labelsPlaceholder: "ui, docs", + searchPlaceholder: "カードを検索", + allPriorities: "すべての優先度", + emptyColumn: "ここに作業をドロップ", lifecycleUnlinked: "セッションなし", lifecycleUnlinkedDetail: "セッションを開始またはリンク", lifecycleMissing: "セッションが見つかりません", @@ -516,6 +533,13 @@ export const ja_JP: TranslationMap = { lifecycleDoneDetail: "レビューに移動しました", lifecycleNeedsReview: "レビューが必要", lifecycleNeedsReviewDetail: "実行が停止または失敗しました", + eventsLabel: "カードイベント", + eventCreated: "作成済み", + eventEdited: "編集済み", + eventMoved: "移動しました", + eventMovedTo: "{status} に移動しました", + eventLinked: "セッションをリンクしました", + eventExecutionUpdated: "Agent が更新されました", gameButton: "ミニゲーム", gameTitle: "Card Chase", gameStart: "ローンチタイルに到達してください。", diff --git a/ui/src/i18n/locales/ko.ts b/ui/src/i18n/locales/ko.ts index faaa4077ca7..30d2e3f56f6 100644 --- a/ui/src/i18n/locales/ko.ts +++ b/ui/src/i18n/locales/ko.ts @@ -492,6 +492,18 @@ export const ko: TranslationMap = { noLinkedSession: "연결된 세션 없음", stopSession: "세션 중지", editCard: "카드 편집", + editCardHelp: "대기열 메타데이터와 세션 인계를 업데이트합니다.", + newCard: "새 카드", + newCardHelp: "에이전트 세션을 위한 작업을 대기열에 추가합니다.", + deleteCard: "카드 삭제", + openSession: "세션 열기", + openLinkedSession: "연결된 세션 열기", + defaultAgent: "기본 에이전트", + runEngine: "{engine} 실행", + openEngine: "{engine} 열기", + runDefaultAgent: "기본 에이전트 실행", + start: "시작", + live: "라이브", fieldTitle: "제목", fieldNotes: "메모", fieldStatus: "상태", @@ -499,7 +511,12 @@ export const ko: TranslationMap = { fieldAgent: "에이전트", fieldSession: "세션", fieldLabels: "레이블", + titlePlaceholder: "카드 제목", + notesPlaceholder: "메모, 승인 기준, 링크", labelsPlaceholder: "ui, docs", + searchPlaceholder: "카드 검색", + allPriorities: "모든 우선순위", + emptyColumn: "여기에 작업을 놓으세요", lifecycleUnlinked: "세션 없음", lifecycleUnlinkedDetail: "세션을 시작하거나 연결하세요", lifecycleMissing: "세션 누락", @@ -512,6 +529,13 @@ export const ko: TranslationMap = { lifecycleDoneDetail: "검토로 이동됨", lifecycleNeedsReview: "검토 필요", lifecycleNeedsReviewDetail: "실행이 중지되었거나 실패했습니다", + eventsLabel: "카드 이벤트", + eventCreated: "생성됨", + eventEdited: "편집됨", + eventMoved: "이동됨", + eventMovedTo: "{status}(으)로 이동됨", + eventLinked: "연결된 세션", + eventExecutionUpdated: "Agent 업데이트됨", gameButton: "미니 게임", gameTitle: "Card Chase", gameStart: "출발 타일에 도달하세요.", @@ -1180,7 +1204,7 @@ export const ko: TranslationMap = { }, queue: { retry: "다시 시도", - retrySend: "전송 다시 시도", + retrySend: "보내기 다시 시도", retryQueuedMessage: "대기 중인 메시지 다시 시도", }, composer: { diff --git a/ui/src/i18n/locales/nl.ts b/ui/src/i18n/locales/nl.ts index 9431579a051..8e6dbf98574 100644 --- a/ui/src/i18n/locales/nl.ts +++ b/ui/src/i18n/locales/nl.ts @@ -494,6 +494,32 @@ export const nl: TranslationMap = { }, noLinkedSession: "Geen gekoppelde sessie", stopSession: "Sessie stoppen", + editCard: "Kaart bewerken", + editCardHelp: "Werk wachtrijmetadata en sessieoverdracht bij.", + newCard: "Nieuwe kaart", + newCardHelp: "Zet werk in de wachtrij voor een agentsessie.", + deleteCard: "Kaart verwijderen", + openSession: "Sessie openen", + openLinkedSession: "Gekoppelde sessie openen", + defaultAgent: "Standaardagent", + runEngine: "{engine} uitvoeren", + openEngine: "{engine} openen", + runDefaultAgent: "Standaardagent uitvoeren", + start: "Starten", + live: "live", + fieldTitle: "Titel", + fieldNotes: "Notities", + fieldStatus: "Status", + fieldPriority: "Prioriteit", + fieldAgent: "Agent", + fieldSession: "Sessie", + fieldLabels: "Labels", + titlePlaceholder: "Kaarttitel", + notesPlaceholder: "Notities, acceptatiecriteria, links", + labelsPlaceholder: "ui, docs", + searchPlaceholder: "Kaarten zoeken", + allPriorities: "Alle prioriteiten", + emptyColumn: "Sleep werk hierheen", lifecycleUnlinked: "Geen sessie", lifecycleUnlinkedDetail: "Start of koppel een sessie", lifecycleMissing: "Sessie ontbreekt", @@ -506,6 +532,32 @@ export const nl: TranslationMap = { lifecycleDoneDetail: "Verplaatst naar review", lifecycleNeedsReview: "Review nodig", lifecycleNeedsReviewDetail: "Run gestopt of mislukt", + eventsLabel: "Kaartgebeurtenissen", + eventCreated: "Gemaakt", + eventEdited: "Bewerkt", + eventMoved: "Verplaatst", + eventMovedTo: "Verplaatst naar {status}", + eventLinked: "Gekoppelde sessie", + eventExecutionUpdated: "Agent bijgewerkt", + gameButton: "Minigame", + gameTitle: "Card Chase", + gameStart: "Bereik de lanceringstegel.", + gameBoundary: "Grens bereikt.", + gameBlocked: "Geblokkeerd.", + gameContinue: "Ga door.", + gameWin: "Lancering voltooid.", + gameMoves: "Zetten {count}", + gameWins: "Overwinningen {count}", + gameBoard: "Card Chase-bord", + gameControls: "Card Chase-bediening", + gameAgent: "Agent", + gameLaunch: "Starten", + gameBlockedCell: "Geblokkeerd", + gameOpenCell: "Open", + gameMoveUp: "Omhoog verplaatsen", + gameMoveLeft: "Naar links verplaatsen", + gameMoveDown: "Omlaag verplaatsen", + gameMoveRight: "Naar rechts verplaatsen", }, overview: { access: { diff --git a/ui/src/i18n/locales/pl.ts b/ui/src/i18n/locales/pl.ts index 17acc1b61e2..70ac0b579ca 100644 --- a/ui/src/i18n/locales/pl.ts +++ b/ui/src/i18n/locales/pl.ts @@ -493,6 +493,32 @@ export const pl: TranslationMap = { }, noLinkedSession: "Brak połączonej sesji", stopSession: "Zatrzymaj sesję", + editCard: "Edytuj kartę", + editCardHelp: "Zaktualizuj metadane kolejki i przekazanie sesji.", + newCard: "Nowa karta", + newCardHelp: "Dodaj zadanie do kolejki dla sesji agenta.", + deleteCard: "Usuń kartę", + openSession: "Otwórz sesję", + openLinkedSession: "Otwórz powiązaną sesję", + defaultAgent: "Domyślny agent", + runEngine: "Uruchom {engine}", + openEngine: "Otwórz {engine}", + runDefaultAgent: "Uruchom domyślnego agenta", + start: "Rozpocznij", + live: "na żywo", + fieldTitle: "Tytuł", + fieldNotes: "Notatki", + fieldStatus: "Status", + fieldPriority: "Priorytet", + fieldAgent: "Agent", + fieldSession: "Sesja", + fieldLabels: "Etykiety", + titlePlaceholder: "Tytuł karty", + notesPlaceholder: "Notatki, kryteria akceptacji, linki", + labelsPlaceholder: "ui, docs", + searchPlaceholder: "Szukaj kart", + allPriorities: "Wszystkie priorytety", + emptyColumn: "Upuść pracę tutaj", lifecycleUnlinked: "Brak sesji", lifecycleUnlinkedDetail: "Rozpocznij lub połącz sesję", lifecycleMissing: "Brak sesji", @@ -505,6 +531,32 @@ export const pl: TranslationMap = { lifecycleDoneDetail: "Przeniesiono do przeglądu", lifecycleNeedsReview: "Wymaga przeglądu", lifecycleNeedsReviewDetail: "Uruchomienie zatrzymane lub nieudane", + eventsLabel: "Zdarzenia karty", + eventCreated: "Utworzono", + eventEdited: "Edytowano", + eventMoved: "Przeniesiono", + eventMovedTo: "Przeniesiono do {status}", + eventLinked: "Połączona sesja", + eventExecutionUpdated: "Agent zaktualizowany", + gameButton: "Minigra", + gameTitle: "Pościg za kartą", + gameStart: "Dotrzyj do pola uruchomienia.", + gameBoundary: "Osiągnięto granicę.", + gameBlocked: "Zablokowane.", + gameContinue: "Kontynuuj.", + gameWin: "Uruchomienie zakończone.", + gameMoves: "Ruchy {count}", + gameWins: "Wygrane {count}", + gameBoard: "Plansza Card Chase", + gameControls: "Sterowanie Card Chase", + gameAgent: "Agent", + gameLaunch: "Uruchom", + gameBlockedCell: "Zablokowane", + gameOpenCell: "Otwarte", + gameMoveUp: "Przesuń w górę", + gameMoveLeft: "Przesuń w lewo", + gameMoveDown: "Przesuń w dół", + gameMoveRight: "Przesuń w prawo", }, overview: { access: { @@ -1164,8 +1216,8 @@ export const pl: TranslationMap = { }, queue: { retry: "Ponów", - retrySend: "Ponów wysyłanie", - retryQueuedMessage: "Ponów wysłanie wiadomości w kolejce", + retrySend: "Ponów wysłanie", + retryQueuedMessage: "Ponów wiadomość w kolejce", }, composer: { placeholder: "Message {name} (Enter to send)", diff --git a/ui/src/i18n/locales/pt-BR.ts b/ui/src/i18n/locales/pt-BR.ts index f330f07a498..57d92d41fb9 100644 --- a/ui/src/i18n/locales/pt-BR.ts +++ b/ui/src/i18n/locales/pt-BR.ts @@ -493,6 +493,18 @@ export const pt_BR: TranslationMap = { noLinkedSession: "Nenhuma sessão vinculada", stopSession: "Parar sessão", editCard: "Editar cartão", + editCardHelp: "Atualize os metadados da fila e a transferência de sessão.", + newCard: "Novo cartão", + newCardHelp: "Enfileire trabalho para uma sessão de agente.", + deleteCard: "Excluir cartão", + openSession: "Abrir sessão", + openLinkedSession: "Abrir sessão vinculada", + defaultAgent: "Agente padrão", + runEngine: "Executar {engine}", + openEngine: "Abrir {engine}", + runDefaultAgent: "Executar agente padrão", + start: "Iniciar", + live: "ao vivo", fieldTitle: "Título", fieldNotes: "Notas", fieldStatus: "Status", @@ -500,7 +512,12 @@ export const pt_BR: TranslationMap = { fieldAgent: "Agente", fieldSession: "Sessão", fieldLabels: "Etiquetas", + titlePlaceholder: "Título do cartão", + notesPlaceholder: "Notas, critérios de aceitação, links", labelsPlaceholder: "ui, docs", + searchPlaceholder: "Pesquisar cartões", + allPriorities: "Todas as prioridades", + emptyColumn: "Solte o trabalho aqui", lifecycleUnlinked: "Nenhuma sessão", lifecycleUnlinkedDetail: "Inicie ou vincule uma sessão", lifecycleMissing: "Sessão ausente", @@ -513,6 +530,13 @@ export const pt_BR: TranslationMap = { lifecycleDoneDetail: "Movido para revisão", lifecycleNeedsReview: "Precisa de revisão", lifecycleNeedsReviewDetail: "A execução foi interrompida ou falhou", + eventsLabel: "Eventos do cartão", + eventCreated: "Criado", + eventEdited: "Editado", + eventMoved: "Movido", + eventMovedTo: "Movido para {status}", + eventLinked: "Sessão vinculada", + eventExecutionUpdated: "Agente atualizado", gameButton: "Mini game", gameTitle: "Card Chase", gameStart: "Alcance o bloco de lançamento.", @@ -1189,7 +1213,7 @@ export const pt_BR: TranslationMap = { queue: { retry: "Tentar novamente", retrySend: "Tentar enviar novamente", - retryQueuedMessage: "Tentar novamente a mensagem na fila", + retryQueuedMessage: "Tentar novamente mensagem na fila", }, composer: { placeholder: "Message {name} (Enter to send)", diff --git a/ui/src/i18n/locales/th.ts b/ui/src/i18n/locales/th.ts index 7921eb8c70a..aa7c0c076b4 100644 --- a/ui/src/i18n/locales/th.ts +++ b/ui/src/i18n/locales/th.ts @@ -490,6 +490,32 @@ export const th: TranslationMap = { }, noLinkedSession: "ไม่มีเซสชันที่เชื่อมโยง", stopSession: "หยุดเซสชัน", + editCard: "แก้ไขการ์ด", + editCardHelp: "อัปเดตข้อมูลเมตาของคิวและการส่งต่อเซสชัน", + newCard: "การ์ดใหม่", + newCardHelp: "จัดคิวงานสำหรับเซสชันของเอเจนต์", + deleteCard: "ลบการ์ด", + openSession: "เปิดเซสชัน", + openLinkedSession: "เปิดเซสชันที่ลิงก์ไว้", + defaultAgent: "เอเจนต์เริ่มต้น", + runEngine: "เรียกใช้ {engine}", + openEngine: "เปิด {engine}", + runDefaultAgent: "เรียกใช้เอเจนต์เริ่มต้น", + start: "เริ่ม", + live: "สด", + fieldTitle: "ชื่อเรื่อง", + fieldNotes: "บันทึก", + fieldStatus: "สถานะ", + fieldPriority: "ลำดับความสำคัญ", + fieldAgent: "เอเจนต์", + fieldSession: "เซสชัน", + fieldLabels: "ป้ายกำกับ", + titlePlaceholder: "ชื่อการ์ด", + notesPlaceholder: "บันทึก, เกณฑ์การยอมรับ, ลิงก์", + labelsPlaceholder: "ui, docs", + searchPlaceholder: "ค้นหาการ์ด", + allPriorities: "ทุกลำดับความสำคัญ", + emptyColumn: "วางงานที่นี่", lifecycleUnlinked: "ไม่มีเซสชัน", lifecycleUnlinkedDetail: "เริ่มหรือเชื่อมโยงเซสชัน", lifecycleMissing: "ไม่พบเซสชัน", @@ -502,6 +528,32 @@ export const th: TranslationMap = { lifecycleDoneDetail: "ย้ายไปยังการตรวจทานแล้ว", lifecycleNeedsReview: "ต้องตรวจทาน", lifecycleNeedsReviewDetail: "การรันหยุดหรือไม่สำเร็จ", + eventsLabel: "เหตุการณ์ของการ์ด", + eventCreated: "สร้างแล้ว", + eventEdited: "แก้ไขแล้ว", + eventMoved: "ย้ายแล้ว", + eventMovedTo: "ย้ายไปยัง {status}", + eventLinked: "เซสชันที่ลิงก์แล้ว", + eventExecutionUpdated: "เอเจนต์อัปเดตแล้ว", + gameButton: "มินิเกม", + gameTitle: "Card Chase", + gameStart: "ไปให้ถึงช่องเปิดตัว", + gameBoundary: "ถึงขอบเขตแล้ว", + gameBlocked: "ถูกบล็อก", + gameContinue: "ไปต่อ", + gameWin: "เปิดตัวสำเร็จ", + gameMoves: "การเดิน {count}", + gameWins: "ชนะ {count}", + gameBoard: "กระดาน Card Chase", + gameControls: "ตัวควบคุม Card Chase", + gameAgent: "Agent", + gameLaunch: "เปิดใช้งาน", + gameBlockedCell: "ถูกบล็อก", + gameOpenCell: "เปิด", + gameMoveUp: "เลื่อนขึ้น", + gameMoveLeft: "เลื่อนไปทางซ้าย", + gameMoveDown: "เลื่อนลง", + gameMoveRight: "เลื่อนไปทางขวา", }, overview: { access: { diff --git a/ui/src/i18n/locales/tr.ts b/ui/src/i18n/locales/tr.ts index 8a4085387e9..bdf69b29087 100644 --- a/ui/src/i18n/locales/tr.ts +++ b/ui/src/i18n/locales/tr.ts @@ -495,6 +495,18 @@ export const tr: TranslationMap = { noLinkedSession: "Bağlı oturum yok", stopSession: "Oturumu durdur", editCard: "Kartı düzenle", + editCardHelp: "Kuyruk meta verilerini ve oturum devrini güncelleyin.", + newCard: "Yeni kart", + newCardHelp: "Bir ajan oturumu için işi kuyruğa alın.", + deleteCard: "Kartı sil", + openSession: "Oturumu aç", + openLinkedSession: "Bağlantılı oturumu aç", + defaultAgent: "Varsayılan ajan", + runEngine: "{engine} çalıştır", + openEngine: "{engine} aç", + runDefaultAgent: "Varsayılan ajanı çalıştır", + start: "Başlat", + live: "canlı", fieldTitle: "Başlık", fieldNotes: "Notlar", fieldStatus: "Durum", @@ -502,7 +514,12 @@ export const tr: TranslationMap = { fieldAgent: "Aracı", fieldSession: "Oturum", fieldLabels: "Etiketler", + titlePlaceholder: "Kart başlığı", + notesPlaceholder: "Notlar, kabul kriterleri, bağlantılar", labelsPlaceholder: "ui, docs", + searchPlaceholder: "Kartlarda ara", + allPriorities: "Tüm öncelikler", + emptyColumn: "İşi buraya bırakın", lifecycleUnlinked: "Oturum yok", lifecycleUnlinkedDetail: "Bir oturum başlatın veya bağlayın", lifecycleMissing: "Oturum eksik", @@ -515,6 +532,13 @@ export const tr: TranslationMap = { lifecycleDoneDetail: "İncelemeye taşındı", lifecycleNeedsReview: "İnceleme gerekli", lifecycleNeedsReviewDetail: "Çalışma durduruldu veya başarısız oldu", + eventsLabel: "Kart olayları", + eventCreated: "Oluşturuldu", + eventEdited: "Düzenlendi", + eventMoved: "Taşındı", + eventMovedTo: "{status} durumuna taşındı", + eventLinked: "Oturum bağlandı", + eventExecutionUpdated: "Aracı güncellendi", gameButton: "Mini oyun", gameTitle: "Kart Takibi", gameStart: "Başlatma karesine ulaşın.", @@ -1194,7 +1218,7 @@ export const tr: TranslationMap = { queue: { retry: "Yeniden dene", retrySend: "Göndermeyi yeniden dene", - retryQueuedMessage: "Kuyruktaki mesajı yeniden dene", + retryQueuedMessage: "Kuyruğa alınan iletiyi yeniden dene", }, composer: { placeholder: "Message {name} (Enter to send)", diff --git a/ui/src/i18n/locales/uk.ts b/ui/src/i18n/locales/uk.ts index bf39e586183..f2cf004fcce 100644 --- a/ui/src/i18n/locales/uk.ts +++ b/ui/src/i18n/locales/uk.ts @@ -494,6 +494,18 @@ export const uk: TranslationMap = { noLinkedSession: "Немає пов’язаної сесії", stopSession: "Зупинити сесію", editCard: "Редагувати картку", + editCardHelp: "Оновіть метадані черги та передавання сесії.", + newCard: "Нова картка", + newCardHelp: "Поставте роботу в чергу для сесії агента.", + deleteCard: "Видалити картку", + openSession: "Відкрити сесію", + openLinkedSession: "Відкрити пов’язану сесію", + defaultAgent: "Агент за замовчуванням", + runEngine: "Запустити {engine}", + openEngine: "Відкрити {engine}", + runDefaultAgent: "Запустити агента за замовчуванням", + start: "Почати", + live: "наживо", fieldTitle: "Заголовок", fieldNotes: "Нотатки", fieldStatus: "Статус", @@ -501,7 +513,12 @@ export const uk: TranslationMap = { fieldAgent: "Агент", fieldSession: "Сеанс", fieldLabels: "Мітки", + titlePlaceholder: "Заголовок картки", + notesPlaceholder: "Нотатки, критерії приймання, посилання", labelsPlaceholder: "ui, docs", + searchPlaceholder: "Шукати картки", + allPriorities: "Усі пріоритети", + emptyColumn: "Перетягніть роботу сюди", lifecycleUnlinked: "Немає сесії", lifecycleUnlinkedDetail: "Запустіть або пов’яжіть сесію", lifecycleMissing: "Сесію не знайдено", @@ -514,6 +531,13 @@ export const uk: TranslationMap = { lifecycleDoneDetail: "Переміщено на перевірку", lifecycleNeedsReview: "Потребує перевірки", lifecycleNeedsReviewDetail: "Запуск зупинено або він завершився помилкою", + eventsLabel: "Події картки", + eventCreated: "Створено", + eventEdited: "Змінено", + eventMoved: "Переміщено", + eventMovedTo: "Переміщено до {status}", + eventLinked: "Пов’язаний сеанс", + eventExecutionUpdated: "Агент оновлено", gameButton: "Мінігра", gameTitle: "Card Chase", gameStart: "Дістаньтеся клітинки запуску.", @@ -1191,7 +1215,7 @@ export const uk: TranslationMap = { queue: { retry: "Повторити", retrySend: "Повторити надсилання", - retryQueuedMessage: "Повторити надсилання повідомлення в черзі", + retryQueuedMessage: "Повторити повідомлення в черзі", }, composer: { placeholder: "Message {name} (Enter to send)", diff --git a/ui/src/i18n/locales/vi.ts b/ui/src/i18n/locales/vi.ts index 0d76a719bbc..c64149256da 100644 --- a/ui/src/i18n/locales/vi.ts +++ b/ui/src/i18n/locales/vi.ts @@ -492,6 +492,32 @@ export const vi: TranslationMap = { }, noLinkedSession: "Không có phiên được liên kết", stopSession: "Dừng phiên", + editCard: "Chỉnh sửa thẻ", + editCardHelp: "Cập nhật siêu dữ liệu hàng đợi và bàn giao phiên.", + newCard: "Thẻ mới", + newCardHelp: "Đưa công việc vào hàng đợi cho một phiên agent.", + deleteCard: "Xóa thẻ", + openSession: "Mở phiên", + openLinkedSession: "Mở phiên được liên kết", + defaultAgent: "Agent mặc định", + runEngine: "Chạy {engine}", + openEngine: "Mở {engine}", + runDefaultAgent: "Chạy agent mặc định", + start: "Bắt đầu", + live: "trực tiếp", + fieldTitle: "Tiêu đề", + fieldNotes: "Ghi chú", + fieldStatus: "Trạng thái", + fieldPriority: "Mức ưu tiên", + fieldAgent: "Agent", + fieldSession: "Phiên", + fieldLabels: "Nhãn", + titlePlaceholder: "Tiêu đề thẻ", + notesPlaceholder: "Ghi chú, tiêu chí chấp nhận, liên kết", + labelsPlaceholder: "ui, docs", + searchPlaceholder: "Tìm kiếm thẻ", + allPriorities: "Tất cả mức ưu tiên", + emptyColumn: "Thả công việc vào đây", lifecycleUnlinked: "Không có phiên", lifecycleUnlinkedDetail: "Bắt đầu hoặc liên kết một phiên", lifecycleMissing: "Thiếu phiên", @@ -504,6 +530,32 @@ export const vi: TranslationMap = { lifecycleDoneDetail: "Đã chuyển sang xem xét", lifecycleNeedsReview: "Cần xem xét", lifecycleNeedsReviewDetail: "Lượt chạy đã dừng hoặc thất bại", + eventsLabel: "Sự kiện thẻ", + eventCreated: "Đã tạo", + eventEdited: "Đã chỉnh sửa", + eventMoved: "Đã di chuyển", + eventMovedTo: "Đã di chuyển đến {status}", + eventLinked: "Phiên đã liên kết", + eventExecutionUpdated: "Agent đã cập nhật", + gameButton: "Trò chơi nhỏ", + gameTitle: "Đuổi bắt thẻ", + gameStart: "Đến ô khởi chạy.", + gameBoundary: "Đã đến ranh giới.", + gameBlocked: "Bị chặn.", + gameContinue: "Tiếp tục.", + gameWin: "Đã vượt qua khởi chạy.", + gameMoves: "Lượt đi {count}", + gameWins: "Thắng {count}", + gameBoard: "Bảng Card Chase", + gameControls: "Điều khiển Card Chase", + gameAgent: "Tác nhân", + gameLaunch: "Khởi chạy", + gameBlockedCell: "Bị chặn", + gameOpenCell: "Mở", + gameMoveUp: "Di chuyển lên", + gameMoveLeft: "Di chuyển sang trái", + gameMoveDown: "Di chuyển xuống", + gameMoveRight: "Di chuyển sang phải", }, overview: { access: { diff --git a/ui/src/i18n/locales/zh-CN.ts b/ui/src/i18n/locales/zh-CN.ts index 3056d6790a1..c65766db2ee 100644 --- a/ui/src/i18n/locales/zh-CN.ts +++ b/ui/src/i18n/locales/zh-CN.ts @@ -490,6 +490,18 @@ export const zh_CN: TranslationMap = { noLinkedSession: "没有已关联的会话", stopSession: "停止会话", editCard: "编辑卡片", + editCardHelp: "更新队列元数据和会话交接。", + newCard: "新建卡片", + newCardHelp: "为代理会话排队工作。", + deleteCard: "删除卡片", + openSession: "打开会话", + openLinkedSession: "打开关联会话", + defaultAgent: "默认代理", + runEngine: "运行 {engine}", + openEngine: "打开 {engine}", + runDefaultAgent: "运行默认代理", + start: "开始", + live: "实时", fieldTitle: "标题", fieldNotes: "备注", fieldStatus: "状态", @@ -497,7 +509,12 @@ export const zh_CN: TranslationMap = { fieldAgent: "代理", fieldSession: "会话", fieldLabels: "标签", + titlePlaceholder: "卡片标题", + notesPlaceholder: "备注、验收标准、链接", labelsPlaceholder: "ui, docs", + searchPlaceholder: "搜索卡片", + allPriorities: "所有优先级", + emptyColumn: "将工作拖放到此处", lifecycleUnlinked: "无会话", lifecycleUnlinkedDetail: "启动或关联会话", lifecycleMissing: "会话缺失", @@ -510,6 +527,13 @@ export const zh_CN: TranslationMap = { lifecycleDoneDetail: "已移至审核", lifecycleNeedsReview: "需要审核", lifecycleNeedsReviewDetail: "运行已停止或失败", + eventsLabel: "卡片事件", + eventCreated: "已创建", + eventEdited: "已编辑", + eventMoved: "已移动", + eventMovedTo: "已移至 {status}", + eventLinked: "已关联会话", + eventExecutionUpdated: "Agent 已更新", gameButton: "迷你游戏", gameTitle: "卡片追逐", gameStart: "到达发布图块。", @@ -1153,7 +1177,7 @@ export const zh_CN: TranslationMap = { queue: { retry: "重试", retrySend: "重试发送", - retryQueuedMessage: "重试队列中的消息", + retryQueuedMessage: "重试排队消息", }, composer: { placeholder: "给 {name} 发消息(Enter 发送)", diff --git a/ui/src/i18n/locales/zh-TW.ts b/ui/src/i18n/locales/zh-TW.ts index 3aae516c7f9..ee9c47f0ddc 100644 --- a/ui/src/i18n/locales/zh-TW.ts +++ b/ui/src/i18n/locales/zh-TW.ts @@ -490,6 +490,18 @@ export const zh_TW: TranslationMap = { noLinkedSession: "沒有已連結的工作階段", stopSession: "停止工作階段", editCard: "編輯卡片", + editCardHelp: "更新佇列中繼資料與工作階段交接。", + newCard: "新增卡片", + newCardHelp: "為代理程式工作階段排入工作。", + deleteCard: "刪除卡片", + openSession: "開啟工作階段", + openLinkedSession: "開啟連結的工作階段", + defaultAgent: "預設代理程式", + runEngine: "執行 {engine}", + openEngine: "開啟 {engine}", + runDefaultAgent: "執行預設代理程式", + start: "開始", + live: "即時", fieldTitle: "標題", fieldNotes: "備註", fieldStatus: "狀態", @@ -497,7 +509,12 @@ export const zh_TW: TranslationMap = { fieldAgent: "代理", fieldSession: "工作階段", fieldLabels: "標籤", + titlePlaceholder: "卡片標題", + notesPlaceholder: "備註、驗收標準、連結", labelsPlaceholder: "ui, docs", + searchPlaceholder: "搜尋卡片", + allPriorities: "所有優先順序", + emptyColumn: "將工作拖放到這裡", lifecycleUnlinked: "沒有工作階段", lifecycleUnlinkedDetail: "開始或連結工作階段", lifecycleMissing: "找不到工作階段", @@ -510,6 +527,13 @@ export const zh_TW: TranslationMap = { lifecycleDoneDetail: "已移至審查", lifecycleNeedsReview: "需要審查", lifecycleNeedsReviewDetail: "執行已停止或失敗", + eventsLabel: "卡片事件", + eventCreated: "已建立", + eventEdited: "已編輯", + eventMoved: "已移動", + eventMovedTo: "已移動至 {status}", + eventLinked: "已連結工作階段", + eventExecutionUpdated: "代理程式已更新", gameButton: "小遊戲", gameTitle: "卡片追逐", gameStart: "到達啟動方格。", @@ -1154,7 +1178,7 @@ export const zh_TW: TranslationMap = { }, queue: { retry: "重試", - retrySend: "重試傳送", + retrySend: "重新傳送", retryQueuedMessage: "重試佇列中的訊息", }, composer: { diff --git a/ui/src/styles/workboard.css b/ui/src/styles/workboard.css index 1f1915680e4..06f148682a5 100644 --- a/ui/src/styles/workboard.css +++ b/ui/src/styles/workboard.css @@ -533,6 +533,37 @@ white-space: nowrap; } +.workboard-events { + display: grid; + gap: 4px; + margin: 0; + padding: 7px 0 0; + border-top: 1px solid color-mix(in srgb, var(--border) 62%, transparent); + list-style: none; +} + +.workboard-events li { + display: flex; + justify-content: space-between; + gap: 8px; + min-width: 0; + color: var(--muted); + font-size: 0.72rem; + line-height: 1.25; +} + +.workboard-events span { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.workboard-events time { + flex: 0 0 auto; + color: color-mix(in srgb, var(--muted) 72%, transparent); +} + .workboard-lifecycle { flex: 0 0 auto; border-radius: 6px; diff --git a/ui/src/ui/app-render.ts b/ui/src/ui/app-render.ts index 26ffa9d18b5..7ab3d1fa1fe 100644 --- a/ui/src/ui/app-render.ts +++ b/ui/src/ui/app-render.ts @@ -2044,6 +2044,10 @@ export function renderApp(state: AppViewState) { enabledByDefault: false, }, ); + const operatorCanWrite = hasOperatorWriteAccess( + (state.hello as { auth?: { role?: string; scopes?: string[] } } | null)?.auth ?? + null, + ); return m.renderSessions({ loading: state.sessionsLoading, result: state.sessionsResult, @@ -2064,7 +2068,7 @@ export function renderApp(state: AppViewState) { selectedKeys: state.sessionsSelectedKeys, workboardSessionKeys: new Set( workboardState.cards - .map((card) => card.sessionKey) + .flatMap((card) => [card.sessionKey, card.execution?.sessionKey]) .filter((key): key is string => typeof key === "string" && key.length > 0), ), workboardBusySessionKey: [...workboardState.capturingSessionKeys][0] ?? null, @@ -2168,17 +2172,18 @@ export function renderApp(state: AppViewState) { switchChatSession(state, sessionKey); state.setTab("chat" as import("./navigation.ts").Tab); }, - onAddToWorkboard: workboardEnabled - ? async (session) => { - await captureSessionToWorkboard({ - host: state, - client: state.client, - session, - requestUpdate: requestHostUpdate, - }); - state.setTab("workboard" as import("./navigation.ts").Tab); - } - : undefined, + onAddToWorkboard: + workboardEnabled && operatorCanWrite + ? async (session) => { + await captureSessionToWorkboard({ + host: state, + client: state.client, + session, + requestUpdate: requestHostUpdate, + }); + state.setTab("workboard" as import("./navigation.ts").Tab); + } + : undefined, onToggleCheckpointDetails: (sessionKey) => toggleSessionCompactionCheckpoints(state, sessionKey), onBranchFromCheckpoint: async (sessionKey, checkpointId) => { diff --git a/ui/src/ui/controllers/workboard.test.ts b/ui/src/ui/controllers/workboard.test.ts index 1b7d9f1c828..159eb2ed743 100644 --- a/ui/src/ui/controllers/workboard.test.ts +++ b/ui/src/ui/controllers/workboard.test.ts @@ -204,7 +204,20 @@ describe("workboard controller", () => { it("does not duplicate existing captured sessions", async () => { const host = {}; const state = getWorkboardState(host); - const existing = { ...sampleCard, sessionKey: sampleSession.key }; + const existing = { + ...sampleCard, + execution: { + id: "exec-1", + kind: "agent-session", + engine: "codex", + mode: "autonomous", + status: "running", + model: "openai/gpt-5.5", + sessionKey: sampleSession.key, + startedAt: 1, + updatedAt: 1, + }, + } satisfies WorkboardCard; state.loaded = true; state.cards = [existing]; const client = createClient({}); @@ -223,6 +236,8 @@ describe("workboard controller", () => { const host = {}; const state = getWorkboardState(host); state.capturingSessionKeys.add(sampleSession.key); + const existing = { ...sampleCard, sessionKey: sampleSession.key }; + state.cards = [existing]; const client = createClient({}); const card = await captureSessionToWorkboard({ @@ -231,7 +246,7 @@ describe("workboard controller", () => { session: sampleSession, }); - expect(card).toBeNull(); + expect(card).toBe(existing); expect(client.request).not.toHaveBeenCalled(); }); @@ -537,6 +552,12 @@ describe("workboard controller", () => { state: "running", targetStatus: "running", }); + expect( + getWorkboardLifecycle(linked, [{ ...sampleSession, hasActiveRun: false, status: "running" }]), + ).toMatchObject({ + state: "running", + targetStatus: "running", + }); expect( getWorkboardLifecycle(linked, [{ ...sampleSession, hasActiveRun: false, status: "done" }]), ).toMatchObject({ diff --git a/ui/src/ui/controllers/workboard.ts b/ui/src/ui/controllers/workboard.ts index aaacc44f8b0..0ca26b72cd2 100644 --- a/ui/src/ui/controllers/workboard.ts +++ b/ui/src/ui/controllers/workboard.ts @@ -20,6 +20,13 @@ export const WORKBOARD_EXECUTION_STATUSES = [ "blocked", "done", ] as const; +export const WORKBOARD_EVENT_KINDS = [ + "created", + "edited", + "moved", + "linked", + "execution_updated", +] as const; export const WORKBOARD_ENGINE_MODELS = { codex: "openai/gpt-5.5", @@ -31,6 +38,7 @@ export type WorkboardPriority = (typeof WORKBOARD_PRIORITIES)[number]; export type WorkboardExecutionEngine = (typeof WORKBOARD_EXECUTION_ENGINES)[number]; export type WorkboardExecutionMode = (typeof WORKBOARD_EXECUTION_MODES)[number]; export type WorkboardExecutionStatus = (typeof WORKBOARD_EXECUTION_STATUSES)[number]; +export type WorkboardEventKind = (typeof WORKBOARD_EVENT_KINDS)[number]; export type WorkboardExecution = { id: string; @@ -45,6 +53,16 @@ export type WorkboardExecution = { updatedAt: number; }; +export type WorkboardEvent = { + id: string; + kind: WorkboardEventKind; + at: number; + fromStatus?: WorkboardStatus; + toStatus?: WorkboardStatus; + sessionKey?: string; + runId?: string; +}; + export type WorkboardCard = { id: string; title: string; @@ -63,6 +81,7 @@ export type WorkboardCard = { updatedAt: number; startedAt?: number; completedAt?: number; + events?: WorkboardEvent[]; }; export type WorkboardLifecycleState = @@ -209,6 +228,41 @@ function normalizeExecution(value: unknown): WorkboardExecution | undefined { }; } +function normalizeEvent(value: unknown): WorkboardEvent | null { + if (!isRecord(value)) { + return null; + } + const id = typeof value.id === "string" && value.id.trim() ? value.id.trim() : ""; + const kind = WORKBOARD_EVENT_KINDS.includes(value.kind as WorkboardEventKind) + ? (value.kind as WorkboardEventKind) + : null; + const at = typeof value.at === "number" && Number.isFinite(value.at) ? value.at : 0; + if (!id || !kind || !at) { + return null; + } + const fromStatus = WORKBOARD_STATUSES.includes(value.fromStatus as WorkboardStatus) + ? (value.fromStatus as WorkboardStatus) + : undefined; + const toStatus = WORKBOARD_STATUSES.includes(value.toStatus as WorkboardStatus) + ? (value.toStatus as WorkboardStatus) + : undefined; + return { + id, + kind, + at, + ...(fromStatus ? { fromStatus } : {}), + ...(toStatus ? { toStatus } : {}), + ...(typeof value.sessionKey === "string" ? { sessionKey: value.sessionKey } : {}), + ...(typeof value.runId === "string" ? { runId: value.runId } : {}), + }; +} + +function normalizeEvents(value: unknown): WorkboardEvent[] { + return Array.isArray(value) + ? value.map(normalizeEvent).filter((event): event is WorkboardEvent => event !== null) + : []; +} + function normalizeCard(value: unknown): WorkboardCard | null { if (!isRecord(value)) { return null; @@ -225,6 +279,7 @@ function normalizeCard(value: unknown): WorkboardCard | null { return null; } const execution = normalizeExecution(value.execution); + const events = normalizeEvents(value.events); return { id, title, @@ -245,6 +300,7 @@ function normalizeCard(value: unknown): WorkboardCard | null { ...(execution ? { execution } : {}), ...(typeof value.startedAt === "number" ? { startedAt: value.startedAt } : {}), ...(typeof value.completedAt === "number" ? { completedAt: value.completedAt } : {}), + ...(events.length ? { events } : {}), }; } @@ -422,6 +478,7 @@ function executionStatusForLifecycle( case "unlinked": return undefined; } + return undefined; } function shouldSyncExecutionStatus( @@ -584,7 +641,7 @@ export async function captureSessionToWorkboard(params: { return null; } if (state.capturingSessionKeys.has(params.session.key)) { - return state.cards.find((card) => card.sessionKey === params.session.key) ?? null; + return state.cards.find((card) => workboardCardSessionKey(card) === params.session.key) ?? null; } state.error = null; state.capturingSessionKeys.add(params.session.key); @@ -601,7 +658,9 @@ export async function captureSessionToWorkboard(params: { if (!state.loaded) { return null; } - const existing = state.cards.find((card) => card.sessionKey === params.session.key); + const existing = state.cards.find( + (card) => workboardCardSessionKey(card) === params.session.key, + ); if (existing) { return existing; } diff --git a/ui/src/ui/views/workboard.test.ts b/ui/src/ui/views/workboard.test.ts index 4177e64f7dc..9690665caca 100644 --- a/ui/src/ui/views/workboard.test.ts +++ b/ui/src/ui/views/workboard.test.ts @@ -160,6 +160,83 @@ describe("renderWorkboard", () => { expect(container.querySelector(".workboard-card")?.getAttribute("role")).toBeNull(); }); + it("hides write controls for read-only operators", () => { + const host = {}; + const state = getWorkboardState(host); + state.loaded = true; + state.cards = [ + { + id: "card-1", + title: "Inspect only", + status: "todo", + priority: "normal", + labels: [], + position: 1000, + createdAt: 1, + updatedAt: 1, + }, + ]; + const container = document.createElement("div"); + + render( + renderWorkboard({ + host, + client: null, + connected: true, + canWrite: false, + pluginEnabled: true, + agentsList: null, + sessions: [], + onOpenSession: () => undefined, + }), + container, + ); + + expect(container.querySelector('button[title="Edit card"]')).toBeNull(); + expect(container.querySelector('button[title="Delete card"]')).toBeNull(); + expect(container.querySelectorAll(".workboard-card__start")).toHaveLength(0); + expect( + container.querySelector(".workboard-toolbar__actions .btn.primary"), + ).toBeNull(); + expect(container.querySelector(".workboard-card")?.getAttribute("draggable")).toBe("false"); + }); + + it("offers start controls when a linked session no longer exists", () => { + const host = {}; + const state = getWorkboardState(host); + state.loaded = true; + state.cards = [ + { + id: "card-1", + title: "Restart this", + status: "blocked", + priority: "normal", + labels: [], + position: 1000, + createdAt: 1, + updatedAt: 1, + sessionKey: "agent:main:missing:1", + }, + ]; + const container = document.createElement("div"); + + render( + renderWorkboard({ + host, + client: null, + connected: true, + pluginEnabled: true, + agentsList: null, + sessions: [], + onOpenSession: () => undefined, + }), + container, + ); + + expect(container.textContent).toContain("Session missing"); + expect(container.querySelectorAll(".workboard-card__start")).toHaveLength(5); + }); + it("opens a modal for new cards", () => { const host = {}; getWorkboardState(host).loaded = true; @@ -231,6 +308,44 @@ describe("renderWorkboard", () => { expect(container.querySelector(".workboard-game__stats")?.textContent).toContain("Moves 1"); }); + it("renders card event history", () => { + const host = {}; + const state = getWorkboardState(host); + state.loaded = true; + state.cards = [ + { + id: "card-1", + title: "Tracked task", + status: "review", + priority: "normal", + labels: [], + position: 1000, + createdAt: 1, + updatedAt: 2, + events: [ + { id: "event-1", kind: "created", at: 1, toStatus: "todo" }, + { id: "event-2", kind: "moved", at: 2, fromStatus: "todo", toStatus: "review" }, + ], + }, + ]; + const container = document.createElement("div"); + + render( + renderWorkboard({ + host, + client: null, + connected: true, + pluginEnabled: true, + agentsList: null, + sessions: [], + onOpenSession: () => undefined, + }), + container, + ); + + expect(container.querySelector(".workboard-events")?.textContent).toContain("Moved to Review"); + }); + it("opens an edit modal and submits card updates", async () => { const host = {}; const state = getWorkboardState(host); diff --git a/ui/src/ui/views/workboard.ts b/ui/src/ui/views/workboard.ts index f99ba9de66c..17f9a4211bb 100644 --- a/ui/src/ui/views/workboard.ts +++ b/ui/src/ui/views/workboard.ts @@ -15,6 +15,7 @@ import { type WorkboardExecutionEngine, type WorkboardExecutionMode, type WorkboardCard, + type WorkboardEvent, type WorkboardLifecycle, type WorkboardPriority, type WorkboardStatus, @@ -54,6 +55,47 @@ function formatTime(value: number | undefined): string { }); } +function canMutate(props: WorkboardProps): boolean { + return props.canWrite !== false; +} + +function formatEventLabel(event: WorkboardEvent): string { + switch (event.kind) { + case "created": + return t("workboard.eventCreated"); + case "edited": + return t("workboard.eventEdited"); + case "moved": + return event.toStatus + ? t("workboard.eventMovedTo", { status: formatStatusLabel(event.toStatus) }) + : t("workboard.eventMoved"); + case "linked": + return t("workboard.eventLinked"); + case "execution_updated": + return t("workboard.eventExecutionUpdated"); + } + return ""; +} + +function renderEvents(card: WorkboardCard) { + const events = (card.events ?? []).toReversed().slice(0, 4); + if (events.length === 0) { + return nothing; + } + return html` +
    + ${events.map( + (event) => html` +
  1. + ${formatEventLabel(event)} + +
  2. + `, + )} +
+ `; +} + function matchesFilter( card: WorkboardCard, options: { query: string; priority: "all" | WorkboardPriority }, @@ -347,12 +389,10 @@ function renderCardModal(props: WorkboardProps) { >
-

${editing ? "Edit card" : "New card"}

-

- ${editing - ? "Update queue metadata and session handoff." - : "Queue work for an agent session."} -

+

+ ${editing ? t("workboard.editCard") : t("workboard.newCard")} +

+

${editing ? t("workboard.editCardHelp") : t("workboard.newCardHelp")}