From 49b50f5632f2af16e2b5ec94b0d2cb0fd655e893 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Tue, 2 Jun 2026 08:18:13 -0700 Subject: [PATCH] feat(ui): tighten workboard card operations (cherry picked from commit 7cbdebc4edeef21c48b436cbd3467b7d68c18f96) # Conflicts: # ui/src/styles/workboard.css # ui/src/ui/views/workboard.ts --- ui/src/i18n/.i18n/ar.meta.json | 16 +- ui/src/i18n/.i18n/de.meta.json | 16 +- ui/src/i18n/.i18n/es.meta.json | 16 +- ui/src/i18n/.i18n/fa.meta.json | 16 +- ui/src/i18n/.i18n/fr.meta.json | 16 +- ui/src/i18n/.i18n/id.meta.json | 16 +- ui/src/i18n/.i18n/it.meta.json | 16 +- ui/src/i18n/.i18n/ja-JP.meta.json | 16 +- ui/src/i18n/.i18n/ko.meta.json | 16 +- ui/src/i18n/.i18n/nl.meta.json | 16 +- ui/src/i18n/.i18n/pl.meta.json | 16 +- ui/src/i18n/.i18n/pt-BR.meta.json | 16 +- ui/src/i18n/.i18n/th.meta.json | 16 +- ui/src/i18n/.i18n/tr.meta.json | 16 +- ui/src/i18n/.i18n/uk.meta.json | 16 +- ui/src/i18n/.i18n/vi.meta.json | 16 +- ui/src/i18n/.i18n/zh-CN.meta.json | 16 +- ui/src/i18n/.i18n/zh-TW.meta.json | 16 +- ui/src/i18n/locales/ar.ts | 27 ++ ui/src/i18n/locales/de.ts | 27 ++ ui/src/i18n/locales/en.ts | 27 ++ ui/src/i18n/locales/es.ts | 27 ++ ui/src/i18n/locales/fa.ts | 27 ++ ui/src/i18n/locales/fr.ts | 27 ++ ui/src/i18n/locales/id.ts | 27 ++ ui/src/i18n/locales/it.ts | 27 ++ ui/src/i18n/locales/ja-JP.ts | 27 ++ ui/src/i18n/locales/ko.ts | 27 ++ ui/src/i18n/locales/nl.ts | 27 ++ ui/src/i18n/locales/pl.ts | 27 ++ ui/src/i18n/locales/pt-BR.ts | 27 ++ ui/src/i18n/locales/th.ts | 27 ++ ui/src/i18n/locales/tr.ts | 27 ++ ui/src/i18n/locales/uk.ts | 27 ++ ui/src/i18n/locales/vi.ts | 27 ++ ui/src/i18n/locales/zh-CN.ts | 27 ++ ui/src/i18n/locales/zh-TW.ts | 27 ++ ui/src/styles/workboard.css | 374 +++++++++++++++++-- ui/src/ui/controllers/workboard.test.ts | 49 ++- ui/src/ui/controllers/workboard.ts | 54 ++- ui/src/ui/icons.ts | 37 ++ ui/src/ui/views/workboard.test.ts | 246 ++++++++++++- ui/src/ui/views/workboard.ts | 466 ++++++++++++++++++++---- 43 files changed, 1831 insertions(+), 196 deletions(-) diff --git a/ui/src/i18n/.i18n/ar.meta.json b/ui/src/i18n/.i18n/ar.meta.json index d14c1be0464d..ad201332e323 100644 --- a/ui/src/i18n/.i18n/ar.meta.json +++ b/ui/src/i18n/.i18n/ar.meta.json @@ -1,5 +1,12 @@ { "fallbackKeys": [ + "workboard.dependencies", + "workboard.dependenciesBlocked", + "workboard.dependenciesBlockedTitle", + "workboard.dependenciesReady", + "workboard.dependenciesReadyTitle", + "workboard.dependencyMissing", + "workboard.dependencyStatusMissing", "workboard.detailAddNote", "workboard.detailAutomation", "workboard.detailAutomationBoard", @@ -19,14 +26,15 @@ "workboard.detailUpdatedValue", "workboard.detailWorkerLogs", "workboard.detailWorkerProtocol", + "workboard.unknownStatus", "workboard.viewDetails" ], - "generatedAt": "2026-06-01T02:32:31.090Z", + "generatedAt": "2026-06-01T07:19:23.359Z", "locale": "ar", "model": "claude-opus-4-8", "provider": "anthropic", - "sourceHash": "59589c3b29f7126995ed4763e88b763702a7c20b1791b4e16c62b394bca02d45", - "totalKeys": 1304, - "translatedKeys": 1284, + "sourceHash": "0639815f4b249fd84b5149556602c9e5da1136d73a747f26a1c12981a4319ebb", + "totalKeys": 1330, + "translatedKeys": 1302, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/de.meta.json b/ui/src/i18n/.i18n/de.meta.json index 382fcdbca579..dee506d46ab2 100644 --- a/ui/src/i18n/.i18n/de.meta.json +++ b/ui/src/i18n/.i18n/de.meta.json @@ -1,5 +1,12 @@ { "fallbackKeys": [ + "workboard.dependencies", + "workboard.dependenciesBlocked", + "workboard.dependenciesBlockedTitle", + "workboard.dependenciesReady", + "workboard.dependenciesReadyTitle", + "workboard.dependencyMissing", + "workboard.dependencyStatusMissing", "workboard.detailAddNote", "workboard.detailAutomation", "workboard.detailAutomationBoard", @@ -19,14 +26,15 @@ "workboard.detailUpdatedValue", "workboard.detailWorkerLogs", "workboard.detailWorkerProtocol", + "workboard.unknownStatus", "workboard.viewDetails" ], - "generatedAt": "2026-06-01T02:32:30.623Z", + "generatedAt": "2026-06-01T07:19:21.786Z", "locale": "de", "model": "claude-opus-4-8", "provider": "anthropic", - "sourceHash": "59589c3b29f7126995ed4763e88b763702a7c20b1791b4e16c62b394bca02d45", - "totalKeys": 1304, - "translatedKeys": 1284, + "sourceHash": "0639815f4b249fd84b5149556602c9e5da1136d73a747f26a1c12981a4319ebb", + "totalKeys": 1330, + "translatedKeys": 1302, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/es.meta.json b/ui/src/i18n/.i18n/es.meta.json index 65708747f8a6..3da5766f1a6b 100644 --- a/ui/src/i18n/.i18n/es.meta.json +++ b/ui/src/i18n/.i18n/es.meta.json @@ -1,5 +1,12 @@ { "fallbackKeys": [ + "workboard.dependencies", + "workboard.dependenciesBlocked", + "workboard.dependenciesBlockedTitle", + "workboard.dependenciesReady", + "workboard.dependenciesReadyTitle", + "workboard.dependencyMissing", + "workboard.dependencyStatusMissing", "workboard.detailAddNote", "workboard.detailAutomation", "workboard.detailAutomationBoard", @@ -19,14 +26,15 @@ "workboard.detailUpdatedValue", "workboard.detailWorkerLogs", "workboard.detailWorkerProtocol", + "workboard.unknownStatus", "workboard.viewDetails" ], - "generatedAt": "2026-06-01T02:32:30.724Z", + "generatedAt": "2026-06-01T07:19:22.097Z", "locale": "es", "model": "claude-opus-4-8", "provider": "anthropic", - "sourceHash": "59589c3b29f7126995ed4763e88b763702a7c20b1791b4e16c62b394bca02d45", - "totalKeys": 1304, - "translatedKeys": 1284, + "sourceHash": "0639815f4b249fd84b5149556602c9e5da1136d73a747f26a1c12981a4319ebb", + "totalKeys": 1330, + "translatedKeys": 1302, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/fa.meta.json b/ui/src/i18n/.i18n/fa.meta.json index d3d8fc45e476..09587d31ed48 100644 --- a/ui/src/i18n/.i18n/fa.meta.json +++ b/ui/src/i18n/.i18n/fa.meta.json @@ -1,5 +1,12 @@ { "fallbackKeys": [ + "workboard.dependencies", + "workboard.dependenciesBlocked", + "workboard.dependenciesBlockedTitle", + "workboard.dependenciesReady", + "workboard.dependenciesReadyTitle", + "workboard.dependencyMissing", + "workboard.dependencyStatusMissing", "workboard.detailAddNote", "workboard.detailAutomation", "workboard.detailAutomationBoard", @@ -19,14 +26,15 @@ "workboard.detailUpdatedValue", "workboard.detailWorkerLogs", "workboard.detailWorkerProtocol", + "workboard.unknownStatus", "workboard.viewDetails" ], - "generatedAt": "2026-06-01T02:32:31.893Z", + "generatedAt": "2026-06-01T07:19:26.307Z", "locale": "fa", "model": "claude-opus-4-8", "provider": "anthropic", - "sourceHash": "59589c3b29f7126995ed4763e88b763702a7c20b1791b4e16c62b394bca02d45", - "totalKeys": 1304, - "translatedKeys": 1284, + "sourceHash": "0639815f4b249fd84b5149556602c9e5da1136d73a747f26a1c12981a4319ebb", + "totalKeys": 1330, + "translatedKeys": 1302, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/fr.meta.json b/ui/src/i18n/.i18n/fr.meta.json index c44f52162a9b..afd8935420c5 100644 --- a/ui/src/i18n/.i18n/fr.meta.json +++ b/ui/src/i18n/.i18n/fr.meta.json @@ -1,5 +1,12 @@ { "fallbackKeys": [ + "workboard.dependencies", + "workboard.dependenciesBlocked", + "workboard.dependenciesBlockedTitle", + "workboard.dependenciesReady", + "workboard.dependenciesReadyTitle", + "workboard.dependencyMissing", + "workboard.dependencyStatusMissing", "workboard.detailAddNote", "workboard.detailAutomation", "workboard.detailAutomationBoard", @@ -19,14 +26,15 @@ "workboard.detailUpdatedValue", "workboard.detailWorkerLogs", "workboard.detailWorkerProtocol", + "workboard.unknownStatus", "workboard.viewDetails" ], - "generatedAt": "2026-06-01T02:32:31.001Z", + "generatedAt": "2026-06-01T07:19:23.053Z", "locale": "fr", "model": "claude-opus-4-8", "provider": "anthropic", - "sourceHash": "59589c3b29f7126995ed4763e88b763702a7c20b1791b4e16c62b394bca02d45", - "totalKeys": 1304, - "translatedKeys": 1284, + "sourceHash": "0639815f4b249fd84b5149556602c9e5da1136d73a747f26a1c12981a4319ebb", + "totalKeys": 1330, + "translatedKeys": 1302, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/id.meta.json b/ui/src/i18n/.i18n/id.meta.json index 7b7d5132ecea..98d542010700 100644 --- a/ui/src/i18n/.i18n/id.meta.json +++ b/ui/src/i18n/.i18n/id.meta.json @@ -1,5 +1,12 @@ { "fallbackKeys": [ + "workboard.dependencies", + "workboard.dependenciesBlocked", + "workboard.dependenciesBlockedTitle", + "workboard.dependenciesReady", + "workboard.dependenciesReadyTitle", + "workboard.dependencyMissing", + "workboard.dependencyStatusMissing", "workboard.detailAddNote", "workboard.detailAutomation", "workboard.detailAutomationBoard", @@ -19,14 +26,15 @@ "workboard.detailUpdatedValue", "workboard.detailWorkerLogs", "workboard.detailWorkerProtocol", + "workboard.unknownStatus", "workboard.viewDetails" ], - "generatedAt": "2026-06-01T02:32:31.448Z", + "generatedAt": "2026-06-01T07:19:24.706Z", "locale": "id", "model": "claude-opus-4-8", "provider": "anthropic", - "sourceHash": "59589c3b29f7126995ed4763e88b763702a7c20b1791b4e16c62b394bca02d45", - "totalKeys": 1304, - "translatedKeys": 1284, + "sourceHash": "0639815f4b249fd84b5149556602c9e5da1136d73a747f26a1c12981a4319ebb", + "totalKeys": 1330, + "translatedKeys": 1302, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/it.meta.json b/ui/src/i18n/.i18n/it.meta.json index e6ad9b94da93..88d2cee88bd1 100644 --- a/ui/src/i18n/.i18n/it.meta.json +++ b/ui/src/i18n/.i18n/it.meta.json @@ -1,5 +1,12 @@ { "fallbackKeys": [ + "workboard.dependencies", + "workboard.dependenciesBlocked", + "workboard.dependenciesBlockedTitle", + "workboard.dependenciesReady", + "workboard.dependenciesReadyTitle", + "workboard.dependencyMissing", + "workboard.dependencyStatusMissing", "workboard.detailAddNote", "workboard.detailAutomation", "workboard.detailAutomationBoard", @@ -19,14 +26,15 @@ "workboard.detailUpdatedValue", "workboard.detailWorkerLogs", "workboard.detailWorkerProtocol", + "workboard.unknownStatus", "workboard.viewDetails" ], - "generatedAt": "2026-06-01T02:32:31.183Z", + "generatedAt": "2026-06-01T07:19:23.725Z", "locale": "it", "model": "claude-opus-4-8", "provider": "anthropic", - "sourceHash": "59589c3b29f7126995ed4763e88b763702a7c20b1791b4e16c62b394bca02d45", - "totalKeys": 1304, - "translatedKeys": 1284, + "sourceHash": "0639815f4b249fd84b5149556602c9e5da1136d73a747f26a1c12981a4319ebb", + "totalKeys": 1330, + "translatedKeys": 1302, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/ja-JP.meta.json b/ui/src/i18n/.i18n/ja-JP.meta.json index b5715fcba7c8..8e53a9a0226b 100644 --- a/ui/src/i18n/.i18n/ja-JP.meta.json +++ b/ui/src/i18n/.i18n/ja-JP.meta.json @@ -1,5 +1,12 @@ { "fallbackKeys": [ + "workboard.dependencies", + "workboard.dependenciesBlocked", + "workboard.dependenciesBlockedTitle", + "workboard.dependenciesReady", + "workboard.dependenciesReadyTitle", + "workboard.dependencyMissing", + "workboard.dependencyStatusMissing", "workboard.detailAddNote", "workboard.detailAutomation", "workboard.detailAutomationBoard", @@ -19,14 +26,15 @@ "workboard.detailUpdatedValue", "workboard.detailWorkerLogs", "workboard.detailWorkerProtocol", + "workboard.unknownStatus", "workboard.viewDetails" ], - "generatedAt": "2026-06-01T02:32:30.818Z", + "generatedAt": "2026-06-01T07:19:22.425Z", "locale": "ja-JP", "model": "claude-opus-4-8", "provider": "anthropic", - "sourceHash": "59589c3b29f7126995ed4763e88b763702a7c20b1791b4e16c62b394bca02d45", - "totalKeys": 1304, - "translatedKeys": 1284, + "sourceHash": "0639815f4b249fd84b5149556602c9e5da1136d73a747f26a1c12981a4319ebb", + "totalKeys": 1330, + "translatedKeys": 1302, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/ko.meta.json b/ui/src/i18n/.i18n/ko.meta.json index 7e2df5ae59ee..d95dc523e35f 100644 --- a/ui/src/i18n/.i18n/ko.meta.json +++ b/ui/src/i18n/.i18n/ko.meta.json @@ -1,5 +1,12 @@ { "fallbackKeys": [ + "workboard.dependencies", + "workboard.dependenciesBlocked", + "workboard.dependenciesBlockedTitle", + "workboard.dependenciesReady", + "workboard.dependenciesReadyTitle", + "workboard.dependencyMissing", + "workboard.dependencyStatusMissing", "workboard.detailAddNote", "workboard.detailAutomation", "workboard.detailAutomationBoard", @@ -19,14 +26,15 @@ "workboard.detailUpdatedValue", "workboard.detailWorkerLogs", "workboard.detailWorkerProtocol", + "workboard.unknownStatus", "workboard.viewDetails" ], - "generatedAt": "2026-06-01T02:32:30.911Z", + "generatedAt": "2026-06-01T07:19:22.739Z", "locale": "ko", "model": "claude-opus-4-8", "provider": "anthropic", - "sourceHash": "59589c3b29f7126995ed4763e88b763702a7c20b1791b4e16c62b394bca02d45", - "totalKeys": 1304, - "translatedKeys": 1284, + "sourceHash": "0639815f4b249fd84b5149556602c9e5da1136d73a747f26a1c12981a4319ebb", + "totalKeys": 1330, + "translatedKeys": 1302, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/nl.meta.json b/ui/src/i18n/.i18n/nl.meta.json index b5932b8695dd..0143bc635d9e 100644 --- a/ui/src/i18n/.i18n/nl.meta.json +++ b/ui/src/i18n/.i18n/nl.meta.json @@ -1,5 +1,12 @@ { "fallbackKeys": [ + "workboard.dependencies", + "workboard.dependenciesBlocked", + "workboard.dependenciesBlockedTitle", + "workboard.dependenciesReady", + "workboard.dependenciesReadyTitle", + "workboard.dependencyMissing", + "workboard.dependencyStatusMissing", "workboard.detailAddNote", "workboard.detailAutomation", "workboard.detailAutomationBoard", @@ -19,14 +26,15 @@ "workboard.detailUpdatedValue", "workboard.detailWorkerLogs", "workboard.detailWorkerProtocol", + "workboard.unknownStatus", "workboard.viewDetails" ], - "generatedAt": "2026-06-01T02:32:31.801Z", + "generatedAt": "2026-06-01T07:19:25.987Z", "locale": "nl", "model": "claude-opus-4-8", "provider": "anthropic", - "sourceHash": "59589c3b29f7126995ed4763e88b763702a7c20b1791b4e16c62b394bca02d45", - "totalKeys": 1304, - "translatedKeys": 1284, + "sourceHash": "0639815f4b249fd84b5149556602c9e5da1136d73a747f26a1c12981a4319ebb", + "totalKeys": 1330, + "translatedKeys": 1302, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/pl.meta.json b/ui/src/i18n/.i18n/pl.meta.json index 38a4a3c9eb6e..7f5c8e4e4331 100644 --- a/ui/src/i18n/.i18n/pl.meta.json +++ b/ui/src/i18n/.i18n/pl.meta.json @@ -1,5 +1,12 @@ { "fallbackKeys": [ + "workboard.dependencies", + "workboard.dependenciesBlocked", + "workboard.dependenciesBlockedTitle", + "workboard.dependenciesReady", + "workboard.dependenciesReadyTitle", + "workboard.dependencyMissing", + "workboard.dependencyStatusMissing", "workboard.detailAddNote", "workboard.detailAutomation", "workboard.detailAutomationBoard", @@ -19,14 +26,15 @@ "workboard.detailUpdatedValue", "workboard.detailWorkerLogs", "workboard.detailWorkerProtocol", + "workboard.unknownStatus", "workboard.viewDetails" ], - "generatedAt": "2026-06-01T02:32:31.535Z", + "generatedAt": "2026-06-01T07:19:25.027Z", "locale": "pl", "model": "claude-opus-4-8", "provider": "anthropic", - "sourceHash": "59589c3b29f7126995ed4763e88b763702a7c20b1791b4e16c62b394bca02d45", - "totalKeys": 1304, - "translatedKeys": 1284, + "sourceHash": "0639815f4b249fd84b5149556602c9e5da1136d73a747f26a1c12981a4319ebb", + "totalKeys": 1330, + "translatedKeys": 1302, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/pt-BR.meta.json b/ui/src/i18n/.i18n/pt-BR.meta.json index e483faacd07b..166b36fe1e0f 100644 --- a/ui/src/i18n/.i18n/pt-BR.meta.json +++ b/ui/src/i18n/.i18n/pt-BR.meta.json @@ -1,5 +1,12 @@ { "fallbackKeys": [ + "workboard.dependencies", + "workboard.dependenciesBlocked", + "workboard.dependenciesBlockedTitle", + "workboard.dependenciesReady", + "workboard.dependenciesReadyTitle", + "workboard.dependencyMissing", + "workboard.dependencyStatusMissing", "workboard.detailAddNote", "workboard.detailAutomation", "workboard.detailAutomationBoard", @@ -19,14 +26,15 @@ "workboard.detailUpdatedValue", "workboard.detailWorkerLogs", "workboard.detailWorkerProtocol", + "workboard.unknownStatus", "workboard.viewDetails" ], - "generatedAt": "2026-06-01T02:32:30.531Z", + "generatedAt": "2026-06-01T07:19:21.475Z", "locale": "pt-BR", "model": "claude-opus-4-8", "provider": "anthropic", - "sourceHash": "59589c3b29f7126995ed4763e88b763702a7c20b1791b4e16c62b394bca02d45", - "totalKeys": 1304, - "translatedKeys": 1284, + "sourceHash": "0639815f4b249fd84b5149556602c9e5da1136d73a747f26a1c12981a4319ebb", + "totalKeys": 1330, + "translatedKeys": 1302, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/th.meta.json b/ui/src/i18n/.i18n/th.meta.json index bfd391e20e4a..143f604c0d88 100644 --- a/ui/src/i18n/.i18n/th.meta.json +++ b/ui/src/i18n/.i18n/th.meta.json @@ -1,5 +1,12 @@ { "fallbackKeys": [ + "workboard.dependencies", + "workboard.dependenciesBlocked", + "workboard.dependenciesBlockedTitle", + "workboard.dependenciesReady", + "workboard.dependenciesReadyTitle", + "workboard.dependencyMissing", + "workboard.dependencyStatusMissing", "workboard.detailAddNote", "workboard.detailAutomation", "workboard.detailAutomationBoard", @@ -19,14 +26,15 @@ "workboard.detailUpdatedValue", "workboard.detailWorkerLogs", "workboard.detailWorkerProtocol", + "workboard.unknownStatus", "workboard.viewDetails" ], - "generatedAt": "2026-06-01T02:32:31.623Z", + "generatedAt": "2026-06-01T07:19:25.336Z", "locale": "th", "model": "claude-opus-4-8", "provider": "anthropic", - "sourceHash": "59589c3b29f7126995ed4763e88b763702a7c20b1791b4e16c62b394bca02d45", - "totalKeys": 1304, - "translatedKeys": 1284, + "sourceHash": "0639815f4b249fd84b5149556602c9e5da1136d73a747f26a1c12981a4319ebb", + "totalKeys": 1330, + "translatedKeys": 1302, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/tr.meta.json b/ui/src/i18n/.i18n/tr.meta.json index 6b4bd28aff90..aae73ce608f0 100644 --- a/ui/src/i18n/.i18n/tr.meta.json +++ b/ui/src/i18n/.i18n/tr.meta.json @@ -1,5 +1,12 @@ { "fallbackKeys": [ + "workboard.dependencies", + "workboard.dependenciesBlocked", + "workboard.dependenciesBlockedTitle", + "workboard.dependenciesReady", + "workboard.dependenciesReadyTitle", + "workboard.dependencyMissing", + "workboard.dependencyStatusMissing", "workboard.detailAddNote", "workboard.detailAutomation", "workboard.detailAutomationBoard", @@ -19,14 +26,15 @@ "workboard.detailUpdatedValue", "workboard.detailWorkerLogs", "workboard.detailWorkerProtocol", + "workboard.unknownStatus", "workboard.viewDetails" ], - "generatedAt": "2026-06-01T02:32:31.272Z", + "generatedAt": "2026-06-01T07:19:24.054Z", "locale": "tr", "model": "claude-opus-4-8", "provider": "anthropic", - "sourceHash": "59589c3b29f7126995ed4763e88b763702a7c20b1791b4e16c62b394bca02d45", - "totalKeys": 1304, - "translatedKeys": 1284, + "sourceHash": "0639815f4b249fd84b5149556602c9e5da1136d73a747f26a1c12981a4319ebb", + "totalKeys": 1330, + "translatedKeys": 1302, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/uk.meta.json b/ui/src/i18n/.i18n/uk.meta.json index a338b5c88cb4..cf5670e4e0c4 100644 --- a/ui/src/i18n/.i18n/uk.meta.json +++ b/ui/src/i18n/.i18n/uk.meta.json @@ -1,5 +1,12 @@ { "fallbackKeys": [ + "workboard.dependencies", + "workboard.dependenciesBlocked", + "workboard.dependenciesBlockedTitle", + "workboard.dependenciesReady", + "workboard.dependenciesReadyTitle", + "workboard.dependencyMissing", + "workboard.dependencyStatusMissing", "workboard.detailAddNote", "workboard.detailAutomation", "workboard.detailAutomationBoard", @@ -19,14 +26,15 @@ "workboard.detailUpdatedValue", "workboard.detailWorkerLogs", "workboard.detailWorkerProtocol", + "workboard.unknownStatus", "workboard.viewDetails" ], - "generatedAt": "2026-06-01T02:32:31.362Z", + "generatedAt": "2026-06-01T07:19:24.380Z", "locale": "uk", "model": "claude-opus-4-8", "provider": "anthropic", - "sourceHash": "59589c3b29f7126995ed4763e88b763702a7c20b1791b4e16c62b394bca02d45", - "totalKeys": 1304, - "translatedKeys": 1284, + "sourceHash": "0639815f4b249fd84b5149556602c9e5da1136d73a747f26a1c12981a4319ebb", + "totalKeys": 1330, + "translatedKeys": 1302, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/vi.meta.json b/ui/src/i18n/.i18n/vi.meta.json index f7408dfaca0b..1dd74be8b9c0 100644 --- a/ui/src/i18n/.i18n/vi.meta.json +++ b/ui/src/i18n/.i18n/vi.meta.json @@ -1,5 +1,12 @@ { "fallbackKeys": [ + "workboard.dependencies", + "workboard.dependenciesBlocked", + "workboard.dependenciesBlockedTitle", + "workboard.dependenciesReady", + "workboard.dependenciesReadyTitle", + "workboard.dependencyMissing", + "workboard.dependencyStatusMissing", "workboard.detailAddNote", "workboard.detailAutomation", "workboard.detailAutomationBoard", @@ -19,14 +26,15 @@ "workboard.detailUpdatedValue", "workboard.detailWorkerLogs", "workboard.detailWorkerProtocol", + "workboard.unknownStatus", "workboard.viewDetails" ], - "generatedAt": "2026-06-01T02:32:31.711Z", + "generatedAt": "2026-06-01T07:19:25.659Z", "locale": "vi", "model": "claude-opus-4-8", "provider": "anthropic", - "sourceHash": "59589c3b29f7126995ed4763e88b763702a7c20b1791b4e16c62b394bca02d45", - "totalKeys": 1304, - "translatedKeys": 1284, + "sourceHash": "0639815f4b249fd84b5149556602c9e5da1136d73a747f26a1c12981a4319ebb", + "totalKeys": 1330, + "translatedKeys": 1302, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/zh-CN.meta.json b/ui/src/i18n/.i18n/zh-CN.meta.json index 8f6f0b2abf60..a445d3efa4fc 100644 --- a/ui/src/i18n/.i18n/zh-CN.meta.json +++ b/ui/src/i18n/.i18n/zh-CN.meta.json @@ -1,5 +1,12 @@ { "fallbackKeys": [ + "workboard.dependencies", + "workboard.dependenciesBlocked", + "workboard.dependenciesBlockedTitle", + "workboard.dependenciesReady", + "workboard.dependenciesReadyTitle", + "workboard.dependencyMissing", + "workboard.dependencyStatusMissing", "workboard.detailAddNote", "workboard.detailAutomation", "workboard.detailAutomationBoard", @@ -19,14 +26,15 @@ "workboard.detailUpdatedValue", "workboard.detailWorkerLogs", "workboard.detailWorkerProtocol", + "workboard.unknownStatus", "workboard.viewDetails" ], - "generatedAt": "2026-06-01T02:32:30.090Z", + "generatedAt": "2026-06-01T07:19:20.827Z", "locale": "zh-CN", "model": "claude-opus-4-8", "provider": "anthropic", - "sourceHash": "59589c3b29f7126995ed4763e88b763702a7c20b1791b4e16c62b394bca02d45", - "totalKeys": 1304, - "translatedKeys": 1284, + "sourceHash": "0639815f4b249fd84b5149556602c9e5da1136d73a747f26a1c12981a4319ebb", + "totalKeys": 1330, + "translatedKeys": 1302, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/zh-TW.meta.json b/ui/src/i18n/.i18n/zh-TW.meta.json index 92a900422277..8c25e7271171 100644 --- a/ui/src/i18n/.i18n/zh-TW.meta.json +++ b/ui/src/i18n/.i18n/zh-TW.meta.json @@ -1,5 +1,12 @@ { "fallbackKeys": [ + "workboard.dependencies", + "workboard.dependenciesBlocked", + "workboard.dependenciesBlockedTitle", + "workboard.dependenciesReady", + "workboard.dependenciesReadyTitle", + "workboard.dependencyMissing", + "workboard.dependencyStatusMissing", "workboard.detailAddNote", "workboard.detailAutomation", "workboard.detailAutomationBoard", @@ -19,14 +26,15 @@ "workboard.detailUpdatedValue", "workboard.detailWorkerLogs", "workboard.detailWorkerProtocol", + "workboard.unknownStatus", "workboard.viewDetails" ], - "generatedAt": "2026-06-01T02:32:30.440Z", + "generatedAt": "2026-06-01T07:19:21.160Z", "locale": "zh-TW", "model": "claude-opus-4-8", "provider": "anthropic", - "sourceHash": "59589c3b29f7126995ed4763e88b763702a7c20b1791b4e16c62b394bca02d45", - "totalKeys": 1304, - "translatedKeys": 1284, + "sourceHash": "0639815f4b249fd84b5149556602c9e5da1136d73a747f26a1c12981a4319ebb", + "totalKeys": 1330, + "translatedKeys": 1302, "workflow": 1 } diff --git a/ui/src/i18n/locales/ar.ts b/ui/src/i18n/locales/ar.ts index 41488bc4c018..aef0f2cc52c6 100644 --- a/ui/src/i18n/locales/ar.ts +++ b/ui/src/i18n/locales/ar.ts @@ -506,6 +506,8 @@ export const ar: TranslationMap = { newCard: "بطاقة جديدة", newCardHelp: "أضف العمل إلى قائمة الانتظار لجلسة وكيل.", archiveCard: "أرشفة البطاقة", + unarchiveCard: "Restore from archive", + archived: "Archived", deleteCard: "حذف البطاقة", viewDetails: "View details", detailTitle: "Card details", @@ -530,10 +532,35 @@ export const ar: TranslationMap = { openSession: "فتح الجلسة", openLinkedSession: "فتح الجلسة المرتبطة", defaultAgent: "الوكيل الافتراضي", + allAgents: "All agents", + agentFilter: "Filter by agent", + agentLinked: "Linked to {agent}", + agentDefaultLinked: "Using default agent {agent}", + engineOpenAI: "OpenAI", + engineClaude: "Claude", + engineDisabledRuntime: + "{agent} uses the {runtime} ACP runtime. Use default start for that session.", runEngine: "تشغيل {engine}", openEngine: "فتح {engine}", runDefaultAgent: "تشغيل الوكيل الافتراضي", + run: "Run", + open: "Open", start: "بدء", + dependencies: "Dependencies", + dependenciesReady: "{count} ready", + dependenciesReadyTitle: "{count} dependencies are done.", + dependenciesBlocked: "{count} blocked", + dependenciesBlockedTitle: "Waiting on dependencies: {parents}.", + dependencyMissing: "{parent} (missing)", + dependencyStatusMissing: "Missing", + unknownStatus: "Unknown", + showArchived: "Show archived cards", + hideArchived: "Hide archived cards", + showArchivedShort: "Archived", + hideArchivedShort: "Hide archived", + layout: "Card layout", + layoutCompact: "Compact cards", + layoutComfortable: "Comfortable cards", dispatch: "منبّه الموزّع", dispatchSummary: "Dispatch complete: started {started}, promoted {promoted}, blocked {blocked}, reclaimed {reclaimed}, orchestrated {orchestrated}, failures {failures}.", diff --git a/ui/src/i18n/locales/de.ts b/ui/src/i18n/locales/de.ts index 92c8adb9c049..6343a3ebac91 100644 --- a/ui/src/i18n/locales/de.ts +++ b/ui/src/i18n/locales/de.ts @@ -511,6 +511,8 @@ export const de: TranslationMap = { newCard: "Neue Karte", newCardHelp: "Arbeit für eine Agentensitzung in die Warteschlange einreihen.", archiveCard: "Karte archivieren", + unarchiveCard: "Restore from archive", + archived: "Archived", deleteCard: "Karte löschen", viewDetails: "View details", detailTitle: "Card details", @@ -535,10 +537,35 @@ export const de: TranslationMap = { openSession: "Sitzung öffnen", openLinkedSession: "Verknüpfte Sitzung öffnen", defaultAgent: "Standard-Agent", + allAgents: "All agents", + agentFilter: "Filter by agent", + agentLinked: "Linked to {agent}", + agentDefaultLinked: "Using default agent {agent}", + engineOpenAI: "OpenAI", + engineClaude: "Claude", + engineDisabledRuntime: + "{agent} uses the {runtime} ACP runtime. Use default start for that session.", runEngine: "{engine} ausführen", openEngine: "{engine} öffnen", runDefaultAgent: "Standard-Agent ausführen", + run: "Run", + open: "Open", start: "Starten", + dependencies: "Dependencies", + dependenciesReady: "{count} ready", + dependenciesReadyTitle: "{count} dependencies are done.", + dependenciesBlocked: "{count} blocked", + dependenciesBlockedTitle: "Waiting on dependencies: {parents}.", + dependencyMissing: "{parent} (missing)", + dependencyStatusMissing: "Missing", + unknownStatus: "Unknown", + showArchived: "Show archived cards", + hideArchived: "Hide archived cards", + showArchivedShort: "Archived", + hideArchivedShort: "Hide archived", + layout: "Card layout", + layoutCompact: "Compact cards", + layoutComfortable: "Comfortable cards", dispatch: "Dispatcher anstoßen", dispatchSummary: "Dispatch complete: started {started}, promoted {promoted}, blocked {blocked}, reclaimed {reclaimed}, orchestrated {orchestrated}, failures {failures}.", diff --git a/ui/src/i18n/locales/en.ts b/ui/src/i18n/locales/en.ts index 9663cf6e0b5a..09e28601bafb 100644 --- a/ui/src/i18n/locales/en.ts +++ b/ui/src/i18n/locales/en.ts @@ -505,6 +505,8 @@ export const en: TranslationMap = { newCard: "New card", newCardHelp: "Queue work for an agent session.", archiveCard: "Archive card", + unarchiveCard: "Restore from archive", + archived: "Archived", deleteCard: "Delete card", viewDetails: "View details", detailTitle: "Card details", @@ -529,10 +531,35 @@ export const en: TranslationMap = { openSession: "Open session", openLinkedSession: "Open linked session", defaultAgent: "Default agent", + allAgents: "All agents", + agentFilter: "Filter by agent", + agentLinked: "Linked to {agent}", + agentDefaultLinked: "Using default agent {agent}", + engineOpenAI: "OpenAI", + engineClaude: "Claude", + engineDisabledRuntime: + "{agent} uses the {runtime} ACP runtime. Use default start for that session.", runEngine: "Run {engine}", openEngine: "Open {engine}", runDefaultAgent: "Run default agent", + run: "Run", + open: "Open", start: "Start", + dependencies: "Dependencies", + dependenciesReady: "{count} ready", + dependenciesReadyTitle: "{count} dependencies are done.", + dependenciesBlocked: "{count} blocked", + dependenciesBlockedTitle: "Waiting on dependencies: {parents}.", + dependencyMissing: "{parent} (missing)", + dependencyStatusMissing: "Missing", + unknownStatus: "Unknown", + showArchived: "Show archived cards", + hideArchived: "Hide archived cards", + showArchivedShort: "Archived", + hideArchivedShort: "Hide archived", + layout: "Card layout", + layoutCompact: "Compact cards", + layoutComfortable: "Comfortable cards", dispatch: "Dispatch ready work", dispatchSummary: "Dispatch complete: started {started}, promoted {promoted}, blocked {blocked}, reclaimed {reclaimed}, orchestrated {orchestrated}, failures {failures}.", diff --git a/ui/src/i18n/locales/es.ts b/ui/src/i18n/locales/es.ts index 368b25834141..f71fd9000c38 100644 --- a/ui/src/i18n/locales/es.ts +++ b/ui/src/i18n/locales/es.ts @@ -508,6 +508,8 @@ export const es: TranslationMap = { newCard: "Nueva tarjeta", newCardHelp: "Pon trabajo en cola para una sesión de agente.", archiveCard: "Archivar tarjeta", + unarchiveCard: "Restore from archive", + archived: "Archived", deleteCard: "Eliminar tarjeta", viewDetails: "View details", detailTitle: "Card details", @@ -532,10 +534,35 @@ export const es: TranslationMap = { openSession: "Abrir sesión", openLinkedSession: "Abrir sesión vinculada", defaultAgent: "Agente predeterminado", + allAgents: "All agents", + agentFilter: "Filter by agent", + agentLinked: "Linked to {agent}", + agentDefaultLinked: "Using default agent {agent}", + engineOpenAI: "OpenAI", + engineClaude: "Claude", + engineDisabledRuntime: + "{agent} uses the {runtime} ACP runtime. Use default start for that session.", runEngine: "Ejecutar {engine}", openEngine: "Abrir {engine}", runDefaultAgent: "Ejecutar agente predeterminado", + run: "Run", + open: "Open", start: "Iniciar", + dependencies: "Dependencies", + dependenciesReady: "{count} ready", + dependenciesReadyTitle: "{count} dependencies are done.", + dependenciesBlocked: "{count} blocked", + dependenciesBlockedTitle: "Waiting on dependencies: {parents}.", + dependencyMissing: "{parent} (missing)", + dependencyStatusMissing: "Missing", + unknownStatus: "Unknown", + showArchived: "Show archived cards", + hideArchived: "Hide archived cards", + showArchivedShort: "Archived", + hideArchivedShort: "Hide archived", + layout: "Card layout", + layoutCompact: "Compact cards", + layoutComfortable: "Comfortable cards", dispatch: "Avisar al despachador", dispatchSummary: "Dispatch complete: started {started}, promoted {promoted}, blocked {blocked}, reclaimed {reclaimed}, orchestrated {orchestrated}, failures {failures}.", diff --git a/ui/src/i18n/locales/fa.ts b/ui/src/i18n/locales/fa.ts index 63a523553eca..e6d29c4e00cb 100644 --- a/ui/src/i18n/locales/fa.ts +++ b/ui/src/i18n/locales/fa.ts @@ -508,6 +508,8 @@ export const fa: TranslationMap = { newCard: "کارت جدید", newCardHelp: "کار را برای یک نشست عامل در صف قرار دهید.", archiveCard: "بایگانی کارت", + unarchiveCard: "Restore from archive", + archived: "Archived", deleteCard: "حذف کارت", viewDetails: "View details", detailTitle: "Card details", @@ -532,10 +534,35 @@ export const fa: TranslationMap = { openSession: "باز کردن نشست", openLinkedSession: "باز کردن نشست پیوندشده", defaultAgent: "عامل پیش‌فرض", + allAgents: "All agents", + agentFilter: "Filter by agent", + agentLinked: "Linked to {agent}", + agentDefaultLinked: "Using default agent {agent}", + engineOpenAI: "OpenAI", + engineClaude: "Claude", + engineDisabledRuntime: + "{agent} uses the {runtime} ACP runtime. Use default start for that session.", runEngine: "اجرای {engine}", openEngine: "باز کردن {engine}", runDefaultAgent: "اجرای عامل پیش‌فرض", + run: "Run", + open: "Open", start: "شروع", + dependencies: "Dependencies", + dependenciesReady: "{count} ready", + dependenciesReadyTitle: "{count} dependencies are done.", + dependenciesBlocked: "{count} blocked", + dependenciesBlockedTitle: "Waiting on dependencies: {parents}.", + dependencyMissing: "{parent} (missing)", + dependencyStatusMissing: "Missing", + unknownStatus: "Unknown", + showArchived: "Show archived cards", + hideArchived: "Hide archived cards", + showArchivedShort: "Archived", + hideArchivedShort: "Hide archived", + layout: "Card layout", + layoutCompact: "Compact cards", + layoutComfortable: "Comfortable cards", dispatch: "تلنگر به توزیع‌کننده", dispatchSummary: "Dispatch complete: started {started}, promoted {promoted}, blocked {blocked}, reclaimed {reclaimed}, orchestrated {orchestrated}, failures {failures}.", diff --git a/ui/src/i18n/locales/fr.ts b/ui/src/i18n/locales/fr.ts index 0a813470a35c..a540cb39f604 100644 --- a/ui/src/i18n/locales/fr.ts +++ b/ui/src/i18n/locales/fr.ts @@ -510,6 +510,8 @@ export const fr: TranslationMap = { newCard: "Nouvelle carte", newCardHelp: "Mettez du travail en file d’attente pour une session d’agent.", archiveCard: "Archiver la carte", + unarchiveCard: "Restore from archive", + archived: "Archived", deleteCard: "Supprimer la carte", viewDetails: "View details", detailTitle: "Card details", @@ -534,10 +536,35 @@ export const fr: TranslationMap = { openSession: "Ouvrir la session", openLinkedSession: "Ouvrir la session liée", defaultAgent: "Agent par défaut", + allAgents: "All agents", + agentFilter: "Filter by agent", + agentLinked: "Linked to {agent}", + agentDefaultLinked: "Using default agent {agent}", + engineOpenAI: "OpenAI", + engineClaude: "Claude", + engineDisabledRuntime: + "{agent} uses the {runtime} ACP runtime. Use default start for that session.", runEngine: "Exécuter {engine}", openEngine: "Ouvrir {engine}", runDefaultAgent: "Exécuter l’agent par défaut", + run: "Run", + open: "Open", start: "Démarrer", + dependencies: "Dependencies", + dependenciesReady: "{count} ready", + dependenciesReadyTitle: "{count} dependencies are done.", + dependenciesBlocked: "{count} blocked", + dependenciesBlockedTitle: "Waiting on dependencies: {parents}.", + dependencyMissing: "{parent} (missing)", + dependencyStatusMissing: "Missing", + unknownStatus: "Unknown", + showArchived: "Show archived cards", + hideArchived: "Hide archived cards", + showArchivedShort: "Archived", + hideArchivedShort: "Hide archived", + layout: "Card layout", + layoutCompact: "Compact cards", + layoutComfortable: "Comfortable cards", dispatch: "Relancer le répartiteur", dispatchSummary: "Dispatch complete: started {started}, promoted {promoted}, blocked {blocked}, reclaimed {reclaimed}, orchestrated {orchestrated}, failures {failures}.", diff --git a/ui/src/i18n/locales/id.ts b/ui/src/i18n/locales/id.ts index bf18ba5d5847..1d02951f3823 100644 --- a/ui/src/i18n/locales/id.ts +++ b/ui/src/i18n/locales/id.ts @@ -507,6 +507,8 @@ export const id: TranslationMap = { newCard: "Kartu baru", newCardHelp: "Antrekan pekerjaan untuk sesi agen.", archiveCard: "Arsipkan kartu", + unarchiveCard: "Restore from archive", + archived: "Archived", deleteCard: "Hapus kartu", viewDetails: "View details", detailTitle: "Card details", @@ -531,10 +533,35 @@ export const id: TranslationMap = { openSession: "Buka sesi", openLinkedSession: "Buka sesi tertaut", defaultAgent: "Agen default", + allAgents: "All agents", + agentFilter: "Filter by agent", + agentLinked: "Linked to {agent}", + agentDefaultLinked: "Using default agent {agent}", + engineOpenAI: "OpenAI", + engineClaude: "Claude", + engineDisabledRuntime: + "{agent} uses the {runtime} ACP runtime. Use default start for that session.", runEngine: "Jalankan {engine}", openEngine: "Buka {engine}", runDefaultAgent: "Jalankan agen default", + run: "Run", + open: "Open", start: "Mulai", + dependencies: "Dependencies", + dependenciesReady: "{count} ready", + dependenciesReadyTitle: "{count} dependencies are done.", + dependenciesBlocked: "{count} blocked", + dependenciesBlockedTitle: "Waiting on dependencies: {parents}.", + dependencyMissing: "{parent} (missing)", + dependencyStatusMissing: "Missing", + unknownStatus: "Unknown", + showArchived: "Show archived cards", + hideArchived: "Hide archived cards", + showArchivedShort: "Archived", + hideArchivedShort: "Hide archived", + layout: "Card layout", + layoutCompact: "Compact cards", + layoutComfortable: "Comfortable cards", dispatch: "Dorong dispatcher", dispatchSummary: "Dispatch complete: started {started}, promoted {promoted}, blocked {blocked}, reclaimed {reclaimed}, orchestrated {orchestrated}, failures {failures}.", diff --git a/ui/src/i18n/locales/it.ts b/ui/src/i18n/locales/it.ts index e513e89bbc91..4a31afbee066 100644 --- a/ui/src/i18n/locales/it.ts +++ b/ui/src/i18n/locales/it.ts @@ -509,6 +509,8 @@ export const it: TranslationMap = { newCard: "Nuova scheda", newCardHelp: "Metti in coda il lavoro per una sessione dell'agente.", archiveCard: "Archivia scheda", + unarchiveCard: "Restore from archive", + archived: "Archived", deleteCard: "Elimina scheda", viewDetails: "View details", detailTitle: "Card details", @@ -533,10 +535,35 @@ export const it: TranslationMap = { openSession: "Apri sessione", openLinkedSession: "Apri sessione collegata", defaultAgent: "Agente predefinito", + allAgents: "All agents", + agentFilter: "Filter by agent", + agentLinked: "Linked to {agent}", + agentDefaultLinked: "Using default agent {agent}", + engineOpenAI: "OpenAI", + engineClaude: "Claude", + engineDisabledRuntime: + "{agent} uses the {runtime} ACP runtime. Use default start for that session.", runEngine: "Esegui {engine}", openEngine: "Apri {engine}", runDefaultAgent: "Esegui agente predefinito", + run: "Run", + open: "Open", start: "Avvia", + dependencies: "Dependencies", + dependenciesReady: "{count} ready", + dependenciesReadyTitle: "{count} dependencies are done.", + dependenciesBlocked: "{count} blocked", + dependenciesBlockedTitle: "Waiting on dependencies: {parents}.", + dependencyMissing: "{parent} (missing)", + dependencyStatusMissing: "Missing", + unknownStatus: "Unknown", + showArchived: "Show archived cards", + hideArchived: "Hide archived cards", + showArchivedShort: "Archived", + hideArchivedShort: "Hide archived", + layout: "Card layout", + layoutCompact: "Compact cards", + layoutComfortable: "Comfortable cards", dispatch: "Sollecita dispatcher", dispatchSummary: "Dispatch complete: started {started}, promoted {promoted}, blocked {blocked}, reclaimed {reclaimed}, orchestrated {orchestrated}, failures {failures}.", diff --git a/ui/src/i18n/locales/ja-JP.ts b/ui/src/i18n/locales/ja-JP.ts index befaa5357bbc..468e6b588395 100644 --- a/ui/src/i18n/locales/ja-JP.ts +++ b/ui/src/i18n/locales/ja-JP.ts @@ -510,6 +510,8 @@ export const ja_JP: TranslationMap = { newCard: "新規カード", newCardHelp: "エージェントセッションの作業をキューに追加します。", archiveCard: "カードをアーカイブ", + unarchiveCard: "Restore from archive", + archived: "Archived", deleteCard: "カードを削除", viewDetails: "View details", detailTitle: "Card details", @@ -534,10 +536,35 @@ export const ja_JP: TranslationMap = { openSession: "セッションを開く", openLinkedSession: "リンクされたセッションを開く", defaultAgent: "デフォルトエージェント", + allAgents: "All agents", + agentFilter: "Filter by agent", + agentLinked: "Linked to {agent}", + agentDefaultLinked: "Using default agent {agent}", + engineOpenAI: "OpenAI", + engineClaude: "Claude", + engineDisabledRuntime: + "{agent} uses the {runtime} ACP runtime. Use default start for that session.", runEngine: "{engine} を実行", openEngine: "{engine} を開く", runDefaultAgent: "デフォルトエージェントを実行", + run: "Run", + open: "Open", start: "開始", + dependencies: "Dependencies", + dependenciesReady: "{count} ready", + dependenciesReadyTitle: "{count} dependencies are done.", + dependenciesBlocked: "{count} blocked", + dependenciesBlockedTitle: "Waiting on dependencies: {parents}.", + dependencyMissing: "{parent} (missing)", + dependencyStatusMissing: "Missing", + unknownStatus: "Unknown", + showArchived: "Show archived cards", + hideArchived: "Hide archived cards", + showArchivedShort: "Archived", + hideArchivedShort: "Hide archived", + layout: "Card layout", + layoutCompact: "Compact cards", + layoutComfortable: "Comfortable cards", dispatch: "ディスパッチャーを促す", dispatchSummary: "Dispatch complete: started {started}, promoted {promoted}, blocked {blocked}, reclaimed {reclaimed}, orchestrated {orchestrated}, failures {failures}.", diff --git a/ui/src/i18n/locales/ko.ts b/ui/src/i18n/locales/ko.ts index fc28af5eb69f..cf4726490f5e 100644 --- a/ui/src/i18n/locales/ko.ts +++ b/ui/src/i18n/locales/ko.ts @@ -506,6 +506,8 @@ export const ko: TranslationMap = { newCard: "새 카드", newCardHelp: "에이전트 세션을 위한 작업을 대기열에 추가합니다.", archiveCard: "카드 보관", + unarchiveCard: "Restore from archive", + archived: "Archived", deleteCard: "카드 삭제", viewDetails: "View details", detailTitle: "Card details", @@ -530,10 +532,35 @@ export const ko: TranslationMap = { openSession: "세션 열기", openLinkedSession: "연결된 세션 열기", defaultAgent: "기본 에이전트", + allAgents: "All agents", + agentFilter: "Filter by agent", + agentLinked: "Linked to {agent}", + agentDefaultLinked: "Using default agent {agent}", + engineOpenAI: "OpenAI", + engineClaude: "Claude", + engineDisabledRuntime: + "{agent} uses the {runtime} ACP runtime. Use default start for that session.", runEngine: "{engine} 실행", openEngine: "{engine} 열기", runDefaultAgent: "기본 에이전트 실행", + run: "Run", + open: "Open", start: "시작", + dependencies: "Dependencies", + dependenciesReady: "{count} ready", + dependenciesReadyTitle: "{count} dependencies are done.", + dependenciesBlocked: "{count} blocked", + dependenciesBlockedTitle: "Waiting on dependencies: {parents}.", + dependencyMissing: "{parent} (missing)", + dependencyStatusMissing: "Missing", + unknownStatus: "Unknown", + showArchived: "Show archived cards", + hideArchived: "Hide archived cards", + showArchivedShort: "Archived", + hideArchivedShort: "Hide archived", + layout: "Card layout", + layoutCompact: "Compact cards", + layoutComfortable: "Comfortable cards", dispatch: "디스패처 넛지", dispatchSummary: "Dispatch complete: started {started}, promoted {promoted}, blocked {blocked}, reclaimed {reclaimed}, orchestrated {orchestrated}, failures {failures}.", diff --git a/ui/src/i18n/locales/nl.ts b/ui/src/i18n/locales/nl.ts index 8dca15b8e420..3707960b760e 100644 --- a/ui/src/i18n/locales/nl.ts +++ b/ui/src/i18n/locales/nl.ts @@ -509,6 +509,8 @@ export const nl: TranslationMap = { newCard: "Nieuwe kaart", newCardHelp: "Zet werk in de wachtrij voor een agentsessie.", archiveCard: "Kaart archiveren", + unarchiveCard: "Restore from archive", + archived: "Archived", deleteCard: "Kaart verwijderen", viewDetails: "View details", detailTitle: "Card details", @@ -533,10 +535,35 @@ export const nl: TranslationMap = { openSession: "Sessie openen", openLinkedSession: "Gekoppelde sessie openen", defaultAgent: "Standaardagent", + allAgents: "All agents", + agentFilter: "Filter by agent", + agentLinked: "Linked to {agent}", + agentDefaultLinked: "Using default agent {agent}", + engineOpenAI: "OpenAI", + engineClaude: "Claude", + engineDisabledRuntime: + "{agent} uses the {runtime} ACP runtime. Use default start for that session.", runEngine: "{engine} uitvoeren", openEngine: "{engine} openen", runDefaultAgent: "Standaardagent uitvoeren", + run: "Run", + open: "Open", start: "Starten", + dependencies: "Dependencies", + dependenciesReady: "{count} ready", + dependenciesReadyTitle: "{count} dependencies are done.", + dependenciesBlocked: "{count} blocked", + dependenciesBlockedTitle: "Waiting on dependencies: {parents}.", + dependencyMissing: "{parent} (missing)", + dependencyStatusMissing: "Missing", + unknownStatus: "Unknown", + showArchived: "Show archived cards", + hideArchived: "Hide archived cards", + showArchivedShort: "Archived", + hideArchivedShort: "Hide archived", + layout: "Card layout", + layoutCompact: "Compact cards", + layoutComfortable: "Comfortable cards", dispatch: "Dispatcher een zetje geven", dispatchSummary: "Dispatch complete: started {started}, promoted {promoted}, blocked {blocked}, reclaimed {reclaimed}, orchestrated {orchestrated}, failures {failures}.", diff --git a/ui/src/i18n/locales/pl.ts b/ui/src/i18n/locales/pl.ts index 4db465486154..a70f6e0bd6d4 100644 --- a/ui/src/i18n/locales/pl.ts +++ b/ui/src/i18n/locales/pl.ts @@ -508,6 +508,8 @@ export const pl: TranslationMap = { newCard: "Nowa karta", newCardHelp: "Dodaj zadanie do kolejki dla sesji agenta.", archiveCard: "Archiwizuj kartę", + unarchiveCard: "Restore from archive", + archived: "Archived", deleteCard: "Usuń kartę", viewDetails: "View details", detailTitle: "Card details", @@ -532,10 +534,35 @@ export const pl: TranslationMap = { openSession: "Otwórz sesję", openLinkedSession: "Otwórz powiązaną sesję", defaultAgent: "Domyślny agent", + allAgents: "All agents", + agentFilter: "Filter by agent", + agentLinked: "Linked to {agent}", + agentDefaultLinked: "Using default agent {agent}", + engineOpenAI: "OpenAI", + engineClaude: "Claude", + engineDisabledRuntime: + "{agent} uses the {runtime} ACP runtime. Use default start for that session.", runEngine: "Uruchom {engine}", openEngine: "Otwórz {engine}", runDefaultAgent: "Uruchom domyślnego agenta", + run: "Run", + open: "Open", start: "Rozpocznij", + dependencies: "Dependencies", + dependenciesReady: "{count} ready", + dependenciesReadyTitle: "{count} dependencies are done.", + dependenciesBlocked: "{count} blocked", + dependenciesBlockedTitle: "Waiting on dependencies: {parents}.", + dependencyMissing: "{parent} (missing)", + dependencyStatusMissing: "Missing", + unknownStatus: "Unknown", + showArchived: "Show archived cards", + hideArchived: "Hide archived cards", + showArchivedShort: "Archived", + hideArchivedShort: "Hide archived", + layout: "Card layout", + layoutCompact: "Compact cards", + layoutComfortable: "Comfortable cards", dispatch: "Daj znać dyspozytorowi", dispatchSummary: "Dispatch complete: started {started}, promoted {promoted}, blocked {blocked}, reclaimed {reclaimed}, orchestrated {orchestrated}, failures {failures}.", diff --git a/ui/src/i18n/locales/pt-BR.ts b/ui/src/i18n/locales/pt-BR.ts index 7d66cde75efe..78136c904fac 100644 --- a/ui/src/i18n/locales/pt-BR.ts +++ b/ui/src/i18n/locales/pt-BR.ts @@ -507,6 +507,8 @@ export const pt_BR: TranslationMap = { newCard: "Novo cartão", newCardHelp: "Enfileire trabalho para uma sessão de agente.", archiveCard: "Arquivar cartão", + unarchiveCard: "Restore from archive", + archived: "Archived", deleteCard: "Excluir cartão", viewDetails: "View details", detailTitle: "Card details", @@ -531,10 +533,35 @@ export const pt_BR: TranslationMap = { openSession: "Abrir sessão", openLinkedSession: "Abrir sessão vinculada", defaultAgent: "Agente padrão", + allAgents: "All agents", + agentFilter: "Filter by agent", + agentLinked: "Linked to {agent}", + agentDefaultLinked: "Using default agent {agent}", + engineOpenAI: "OpenAI", + engineClaude: "Claude", + engineDisabledRuntime: + "{agent} uses the {runtime} ACP runtime. Use default start for that session.", runEngine: "Executar {engine}", openEngine: "Abrir {engine}", runDefaultAgent: "Executar agente padrão", + run: "Run", + open: "Open", start: "Iniciar", + dependencies: "Dependencies", + dependenciesReady: "{count} ready", + dependenciesReadyTitle: "{count} dependencies are done.", + dependenciesBlocked: "{count} blocked", + dependenciesBlockedTitle: "Waiting on dependencies: {parents}.", + dependencyMissing: "{parent} (missing)", + dependencyStatusMissing: "Missing", + unknownStatus: "Unknown", + showArchived: "Show archived cards", + hideArchived: "Hide archived cards", + showArchivedShort: "Archived", + hideArchivedShort: "Hide archived", + layout: "Card layout", + layoutCompact: "Compact cards", + layoutComfortable: "Comfortable cards", dispatch: "Acionar despachante", dispatchSummary: "Dispatch complete: started {started}, promoted {promoted}, blocked {blocked}, reclaimed {reclaimed}, orchestrated {orchestrated}, failures {failures}.", diff --git a/ui/src/i18n/locales/th.ts b/ui/src/i18n/locales/th.ts index 2bdb57967e47..5b030c844d0d 100644 --- a/ui/src/i18n/locales/th.ts +++ b/ui/src/i18n/locales/th.ts @@ -505,6 +505,8 @@ export const th: TranslationMap = { newCard: "การ์ดใหม่", newCardHelp: "จัดคิวงานสำหรับเซสชันของเอเจนต์", archiveCard: "เก็บถาวรการ์ด", + unarchiveCard: "Restore from archive", + archived: "Archived", deleteCard: "ลบการ์ด", viewDetails: "View details", detailTitle: "Card details", @@ -529,10 +531,35 @@ export const th: TranslationMap = { openSession: "เปิดเซสชัน", openLinkedSession: "เปิดเซสชันที่ลิงก์ไว้", defaultAgent: "เอเจนต์เริ่มต้น", + allAgents: "All agents", + agentFilter: "Filter by agent", + agentLinked: "Linked to {agent}", + agentDefaultLinked: "Using default agent {agent}", + engineOpenAI: "OpenAI", + engineClaude: "Claude", + engineDisabledRuntime: + "{agent} uses the {runtime} ACP runtime. Use default start for that session.", runEngine: "เรียกใช้ {engine}", openEngine: "เปิด {engine}", runDefaultAgent: "เรียกใช้เอเจนต์เริ่มต้น", + run: "Run", + open: "Open", start: "เริ่ม", + dependencies: "Dependencies", + dependenciesReady: "{count} ready", + dependenciesReadyTitle: "{count} dependencies are done.", + dependenciesBlocked: "{count} blocked", + dependenciesBlockedTitle: "Waiting on dependencies: {parents}.", + dependencyMissing: "{parent} (missing)", + dependencyStatusMissing: "Missing", + unknownStatus: "Unknown", + showArchived: "Show archived cards", + hideArchived: "Hide archived cards", + showArchivedShort: "Archived", + hideArchivedShort: "Hide archived", + layout: "Card layout", + layoutCompact: "Compact cards", + layoutComfortable: "Comfortable cards", dispatch: "กระตุ้น dispatcher", dispatchSummary: "Dispatch complete: started {started}, promoted {promoted}, blocked {blocked}, reclaimed {reclaimed}, orchestrated {orchestrated}, failures {failures}.", diff --git a/ui/src/i18n/locales/tr.ts b/ui/src/i18n/locales/tr.ts index 9a99838838c3..2a1aaa066498 100644 --- a/ui/src/i18n/locales/tr.ts +++ b/ui/src/i18n/locales/tr.ts @@ -510,6 +510,8 @@ export const tr: TranslationMap = { newCard: "Yeni kart", newCardHelp: "Bir ajan oturumu için işi kuyruğa alın.", archiveCard: "Kartı arşivle", + unarchiveCard: "Restore from archive", + archived: "Archived", deleteCard: "Kartı sil", viewDetails: "View details", detailTitle: "Card details", @@ -534,10 +536,35 @@ export const tr: TranslationMap = { openSession: "Oturumu aç", openLinkedSession: "Bağlantılı oturumu aç", defaultAgent: "Varsayılan ajan", + allAgents: "All agents", + agentFilter: "Filter by agent", + agentLinked: "Linked to {agent}", + agentDefaultLinked: "Using default agent {agent}", + engineOpenAI: "OpenAI", + engineClaude: "Claude", + engineDisabledRuntime: + "{agent} uses the {runtime} ACP runtime. Use default start for that session.", runEngine: "{engine} çalıştır", openEngine: "{engine} aç", runDefaultAgent: "Varsayılan ajanı çalıştır", + run: "Run", + open: "Open", start: "Başlat", + dependencies: "Dependencies", + dependenciesReady: "{count} ready", + dependenciesReadyTitle: "{count} dependencies are done.", + dependenciesBlocked: "{count} blocked", + dependenciesBlockedTitle: "Waiting on dependencies: {parents}.", + dependencyMissing: "{parent} (missing)", + dependencyStatusMissing: "Missing", + unknownStatus: "Unknown", + showArchived: "Show archived cards", + hideArchived: "Hide archived cards", + showArchivedShort: "Archived", + hideArchivedShort: "Hide archived", + layout: "Card layout", + layoutCompact: "Compact cards", + layoutComfortable: "Comfortable cards", dispatch: "Dağıtıcıyı dürt", dispatchSummary: "Dispatch complete: started {started}, promoted {promoted}, blocked {blocked}, reclaimed {reclaimed}, orchestrated {orchestrated}, failures {failures}.", diff --git a/ui/src/i18n/locales/uk.ts b/ui/src/i18n/locales/uk.ts index c2414af0b409..462c8e66677d 100644 --- a/ui/src/i18n/locales/uk.ts +++ b/ui/src/i18n/locales/uk.ts @@ -509,6 +509,8 @@ export const uk: TranslationMap = { newCard: "Нова картка", newCardHelp: "Поставте роботу в чергу для сесії агента.", archiveCard: "Архівувати картку", + unarchiveCard: "Restore from archive", + archived: "Archived", deleteCard: "Видалити картку", viewDetails: "View details", detailTitle: "Card details", @@ -533,10 +535,35 @@ export const uk: TranslationMap = { openSession: "Відкрити сесію", openLinkedSession: "Відкрити пов’язану сесію", defaultAgent: "Агент за замовчуванням", + allAgents: "All agents", + agentFilter: "Filter by agent", + agentLinked: "Linked to {agent}", + agentDefaultLinked: "Using default agent {agent}", + engineOpenAI: "OpenAI", + engineClaude: "Claude", + engineDisabledRuntime: + "{agent} uses the {runtime} ACP runtime. Use default start for that session.", runEngine: "Запустити {engine}", openEngine: "Відкрити {engine}", runDefaultAgent: "Запустити агента за замовчуванням", + run: "Run", + open: "Open", start: "Почати", + dependencies: "Dependencies", + dependenciesReady: "{count} ready", + dependenciesReadyTitle: "{count} dependencies are done.", + dependenciesBlocked: "{count} blocked", + dependenciesBlockedTitle: "Waiting on dependencies: {parents}.", + dependencyMissing: "{parent} (missing)", + dependencyStatusMissing: "Missing", + unknownStatus: "Unknown", + showArchived: "Show archived cards", + hideArchived: "Hide archived cards", + showArchivedShort: "Archived", + hideArchivedShort: "Hide archived", + layout: "Card layout", + layoutCompact: "Compact cards", + layoutComfortable: "Comfortable cards", dispatch: "Підштовхнути диспетчер", dispatchSummary: "Dispatch complete: started {started}, promoted {promoted}, blocked {blocked}, reclaimed {reclaimed}, orchestrated {orchestrated}, failures {failures}.", diff --git a/ui/src/i18n/locales/vi.ts b/ui/src/i18n/locales/vi.ts index d5b424ec2e94..11f5e91ba9fd 100644 --- a/ui/src/i18n/locales/vi.ts +++ b/ui/src/i18n/locales/vi.ts @@ -508,6 +508,8 @@ export const vi: TranslationMap = { newCard: "Thẻ mới", newCardHelp: "Đưa công việc vào hàng đợi cho một phiên agent.", archiveCard: "Lưu trữ thẻ", + unarchiveCard: "Restore from archive", + archived: "Archived", deleteCard: "Xóa thẻ", viewDetails: "View details", detailTitle: "Card details", @@ -532,10 +534,35 @@ export const vi: TranslationMap = { openSession: "Mở phiên", openLinkedSession: "Mở phiên được liên kết", defaultAgent: "Agent mặc định", + allAgents: "All agents", + agentFilter: "Filter by agent", + agentLinked: "Linked to {agent}", + agentDefaultLinked: "Using default agent {agent}", + engineOpenAI: "OpenAI", + engineClaude: "Claude", + engineDisabledRuntime: + "{agent} uses the {runtime} ACP runtime. Use default start for that session.", runEngine: "Chạy {engine}", openEngine: "Mở {engine}", runDefaultAgent: "Chạy agent mặc định", + run: "Run", + open: "Open", start: "Bắt đầu", + dependencies: "Dependencies", + dependenciesReady: "{count} ready", + dependenciesReadyTitle: "{count} dependencies are done.", + dependenciesBlocked: "{count} blocked", + dependenciesBlockedTitle: "Waiting on dependencies: {parents}.", + dependencyMissing: "{parent} (missing)", + dependencyStatusMissing: "Missing", + unknownStatus: "Unknown", + showArchived: "Show archived cards", + hideArchived: "Hide archived cards", + showArchivedShort: "Archived", + hideArchivedShort: "Hide archived", + layout: "Card layout", + layoutCompact: "Compact cards", + layoutComfortable: "Comfortable cards", dispatch: "Nhắc dispatcher", dispatchSummary: "Dispatch complete: started {started}, promoted {promoted}, blocked {blocked}, reclaimed {reclaimed}, orchestrated {orchestrated}, failures {failures}.", diff --git a/ui/src/i18n/locales/zh-CN.ts b/ui/src/i18n/locales/zh-CN.ts index f1bb73eca288..d65ca200f15f 100644 --- a/ui/src/i18n/locales/zh-CN.ts +++ b/ui/src/i18n/locales/zh-CN.ts @@ -504,6 +504,8 @@ export const zh_CN: TranslationMap = { newCard: "新建卡片", newCardHelp: "为代理会话排队工作。", archiveCard: "归档卡片", + unarchiveCard: "Restore from archive", + archived: "Archived", deleteCard: "删除卡片", viewDetails: "View details", detailTitle: "Card details", @@ -528,10 +530,35 @@ export const zh_CN: TranslationMap = { openSession: "打开会话", openLinkedSession: "打开关联会话", defaultAgent: "默认代理", + allAgents: "All agents", + agentFilter: "Filter by agent", + agentLinked: "Linked to {agent}", + agentDefaultLinked: "Using default agent {agent}", + engineOpenAI: "OpenAI", + engineClaude: "Claude", + engineDisabledRuntime: + "{agent} uses the {runtime} ACP runtime. Use default start for that session.", runEngine: "运行 {engine}", openEngine: "打开 {engine}", runDefaultAgent: "运行默认代理", + run: "Run", + open: "Open", start: "开始", + dependencies: "Dependencies", + dependenciesReady: "{count} ready", + dependenciesReadyTitle: "{count} dependencies are done.", + dependenciesBlocked: "{count} blocked", + dependenciesBlockedTitle: "Waiting on dependencies: {parents}.", + dependencyMissing: "{parent} (missing)", + dependencyStatusMissing: "Missing", + unknownStatus: "Unknown", + showArchived: "Show archived cards", + hideArchived: "Hide archived cards", + showArchivedShort: "Archived", + hideArchivedShort: "Hide archived", + layout: "Card layout", + layoutCompact: "Compact cards", + layoutComfortable: "Comfortable cards", dispatch: "提醒调度器", dispatchSummary: "Dispatch complete: started {started}, promoted {promoted}, blocked {blocked}, reclaimed {reclaimed}, orchestrated {orchestrated}, failures {failures}.", diff --git a/ui/src/i18n/locales/zh-TW.ts b/ui/src/i18n/locales/zh-TW.ts index 7a6250fc3cd2..1aafef61f11e 100644 --- a/ui/src/i18n/locales/zh-TW.ts +++ b/ui/src/i18n/locales/zh-TW.ts @@ -504,6 +504,8 @@ export const zh_TW: TranslationMap = { newCard: "新增卡片", newCardHelp: "為代理程式工作階段排入工作。", archiveCard: "封存卡片", + unarchiveCard: "Restore from archive", + archived: "Archived", deleteCard: "刪除卡片", viewDetails: "View details", detailTitle: "Card details", @@ -528,10 +530,35 @@ export const zh_TW: TranslationMap = { openSession: "開啟工作階段", openLinkedSession: "開啟連結的工作階段", defaultAgent: "預設代理程式", + allAgents: "All agents", + agentFilter: "Filter by agent", + agentLinked: "Linked to {agent}", + agentDefaultLinked: "Using default agent {agent}", + engineOpenAI: "OpenAI", + engineClaude: "Claude", + engineDisabledRuntime: + "{agent} uses the {runtime} ACP runtime. Use default start for that session.", runEngine: "執行 {engine}", openEngine: "開啟 {engine}", runDefaultAgent: "執行預設代理程式", + run: "Run", + open: "Open", start: "開始", + dependencies: "Dependencies", + dependenciesReady: "{count} ready", + dependenciesReadyTitle: "{count} dependencies are done.", + dependenciesBlocked: "{count} blocked", + dependenciesBlockedTitle: "Waiting on dependencies: {parents}.", + dependencyMissing: "{parent} (missing)", + dependencyStatusMissing: "Missing", + unknownStatus: "Unknown", + showArchived: "Show archived cards", + hideArchived: "Hide archived cards", + showArchivedShort: "Archived", + hideArchivedShort: "Hide archived", + layout: "Card layout", + layoutCompact: "Compact cards", + layoutComfortable: "Comfortable cards", dispatch: "提醒分派器", dispatchSummary: "Dispatch complete: started {started}, promoted {promoted}, blocked {blocked}, reclaimed {reclaimed}, orchestrated {orchestrated}, failures {failures}.", diff --git a/ui/src/styles/workboard.css b/ui/src/styles/workboard.css index f5ecd5372fa3..99caf761a8be 100644 --- a/ui/src/styles/workboard.css +++ b/ui/src/styles/workboard.css @@ -1,7 +1,7 @@ .workboard { display: flex; flex-direction: column; - gap: 14px; + gap: 12px; flex: 1; min-height: 0; --workboard-control-height: 34px; @@ -14,15 +14,15 @@ .workboard-toolbar { display: flex; justify-content: space-between; - gap: 10px; + gap: 12px; align-items: center; } .workboard-toolbar { - padding: 8px; - border: 1px solid color-mix(in srgb, var(--border) 86%, transparent); + padding: 9px; + border: 1px solid color-mix(in srgb, var(--border-strong) 56%, transparent); border-radius: 8px; - background: color-mix(in srgb, var(--panel) 84%, transparent); + background: color-mix(in srgb, var(--panel-strong) 64%, var(--panel) 36%); } .workboard-toolbar__filters, @@ -31,8 +31,11 @@ .workboard-template-strip, .workboard-card__actions, .workboard-card__badges, +.workboard-card__chips, .workboard-card__meta, +.workboard-card__quick-actions, .workboard-card__top, +.workboard-layout-toggle, .workboard-labels { display: flex; align-items: center; @@ -45,6 +48,14 @@ min-width: 260px; } +.workboard-layout-toggle { + gap: 4px; + padding: 2px; + border: 1px solid color-mix(in srgb, var(--border) 70%, transparent); + border-radius: var(--workboard-control-radius); + background: color-mix(in srgb, var(--bg-elevated) 42%, transparent); +} + .workboard .input { min-height: var(--workboard-control-height); border: 1px solid var(--workboard-control-border); @@ -101,8 +112,8 @@ } .workboard-toolbar__filters .input[type="search"] { - width: min(360px, 100%); - min-width: min(260px, 100%); + width: min(420px, 100%); + min-width: min(280px, 100%); } .workboard-modal { @@ -224,28 +235,52 @@ border-radius: var(--workboard-control-radius); } +.workboard .btn.active { + border-color: color-mix(in srgb, var(--accent) 44%, var(--border-strong)); + background: color-mix(in srgb, var(--accent) 14%, var(--bg-elevated)); + color: var(--text); +} + .workboard-board { display: grid; grid-auto-flow: column; - grid-auto-columns: minmax(220px, 1fr); + grid-auto-columns: minmax(292px, 1fr); grid-template-columns: none; - gap: 12px; + gap: 10px; flex: 1; min-height: 0; overflow-x: auto; overflow-y: hidden; padding-bottom: 8px; + scroll-snap-type: x proximity; +} + +.workboard-board--compact { + grid-auto-columns: minmax(262px, 1fr); +} + +.workboard-board--compact .workboard-column { + min-width: 262px; +} + +.workboard-board--comfortable { + grid-auto-columns: minmax(312px, 1fr); +} + +.workboard-board--comfortable .workboard-column { + min-width: 312px; } .workboard-column { min-height: 0; - background: color-mix(in srgb, var(--panel) 78%, transparent); - border: 1px solid color-mix(in srgb, var(--border) 80%, transparent); + background: color-mix(in srgb, var(--panel-strong) 34%, var(--panel) 66%); + border: 1px solid color-mix(in srgb, var(--border-strong) 52%, transparent); border-radius: 8px; display: flex; flex-direction: column; - min-width: 220px; + min-width: 292px; overflow: hidden; + scroll-snap-align: start; } .workboard-column--drop { @@ -257,20 +292,44 @@ display: flex; align-items: center; justify-content: space-between; - padding: 10px 12px; + padding: 10px 11px 9px; border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent); + box-shadow: inset 0 2px 0 color-mix(in srgb, var(--accent) 30%, transparent); +} + +.workboard-column--ready .workboard-column__header, +.workboard-column--scheduled .workboard-column__header { + box-shadow: inset 0 2px 0 color-mix(in srgb, var(--warn) 38%, transparent); +} + +.workboard-column--running .workboard-column__header, +.workboard-column--review .workboard-column__header { + box-shadow: inset 0 2px 0 color-mix(in srgb, var(--accent-2) 38%, transparent); +} + +.workboard-column--blocked .workboard-column__header { + box-shadow: inset 0 2px 0 color-mix(in srgb, var(--danger) 40%, transparent); +} + +.workboard-column--done .workboard-column__header { + box-shadow: inset 0 2px 0 color-mix(in srgb, var(--ok) 40%, transparent); } .workboard-column__header h2 { margin: 0; - font-size: 0.82rem; + color: color-mix(in srgb, var(--text) 72%, var(--muted)); + font-size: 0.75rem; text-transform: uppercase; - color: var(--muted); } .workboard-column__header span { - color: var(--muted); - font-size: 0.82rem; + min-width: 24px; + border-radius: 999px; + padding: 2px 7px; + background: color-mix(in srgb, var(--bg-elevated) 72%, transparent); + color: var(--text); + font-size: 0.76rem; + text-align: center; } .workboard-column__cards { @@ -284,17 +343,30 @@ } .workboard-card { - border: 1px solid var(--border); + position: relative; + overflow: hidden; + border: 1px solid color-mix(in srgb, var(--border-strong) 58%, transparent); border-radius: 8px; - background: var(--bg); - padding: 11px; + background: color-mix(in srgb, var(--bg-elevated) 72%, var(--bg) 28%); + padding: 12px; display: grid; - gap: 8px; - box-shadow: 0 1px 0 color-mix(in srgb, var(--border) 60%, transparent); + gap: 9px; + box-shadow: + 0 1px 0 color-mix(in srgb, white 3%, transparent) inset, + 0 8px 22px color-mix(in srgb, #000 18%, transparent); transition: border-color var(--duration-fast) var(--ease-out), background var(--duration-fast) var(--ease-out), - box-shadow var(--duration-fast) var(--ease-out); + box-shadow var(--duration-fast) var(--ease-out), + transform var(--duration-fast) var(--ease-out); +} + +.workboard-card::before { + position: absolute; + inset: 0 auto 0 0; + width: 3px; + background: color-mix(in srgb, var(--muted) 44%, transparent); + content: ""; } .workboard-card--openable { @@ -302,8 +374,12 @@ } .workboard-card--openable:hover { - border-color: color-mix(in srgb, var(--accent) 34%, var(--border-strong)); - background: color-mix(in srgb, var(--bg-elevated) 54%, var(--bg) 46%); + border-color: color-mix(in srgb, var(--accent) 36%, var(--border-strong)); + background: color-mix(in srgb, var(--bg-elevated) 84%, var(--bg) 16%); + box-shadow: + 0 1px 0 color-mix(in srgb, white 4%, transparent) inset, + 0 12px 28px color-mix(in srgb, #000 24%, transparent); + transform: translateY(-1px); } .workboard-card--openable:focus-visible { @@ -316,19 +392,28 @@ opacity: 0.62; } +.workboard-card--archived { + opacity: 0.72; +} + .workboard-card h3 { margin: 0; - font-size: 0.95rem; - line-height: 1.3; + color: var(--text-strong); + font-size: 0.96rem; + line-height: 1.25; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; } .workboard-card p { margin: 0; - color: var(--muted); - font-size: 0.86rem; - line-height: 1.4; + color: color-mix(in srgb, var(--muted) 88%, var(--text) 12%); + font-size: 0.82rem; + line-height: 1.42; display: -webkit-box; - -webkit-line-clamp: 4; + -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; } @@ -340,14 +425,67 @@ font-size: 0.76rem; } +.workboard-card__chips { + min-width: 0; + gap: 5px; +} + +.workboard-card__quick-actions { + flex: 0 0 auto; + gap: 4px; +} + .workboard-card__actions { justify-content: flex-end; + min-height: 28px; + padding-top: 2px; } .workboard-card__badges { gap: 5px; } +.workboard-dependencies { + display: flex; + min-width: 0; +} + +.workboard-dependency { + display: inline-flex; + align-items: center; + gap: 5px; + min-width: 0; + min-height: 21px; + border-radius: 6px; + padding: 3px 8px; + font-size: 0.74rem; + line-height: 1; + white-space: nowrap; +} + +.workboard-dependency svg { + width: 12px; + height: 12px; + flex: 0 0 auto; + fill: none; + stroke: currentColor; + stroke-linecap: round; + stroke-linejoin: round; + stroke-width: 2.25; +} + +.workboard-dependency--blocked { + border: 1px solid color-mix(in srgb, var(--warn) 34%, transparent); + background: color-mix(in srgb, var(--warn) 10%, var(--bg-hover)); + color: color-mix(in srgb, var(--warn) 76%, var(--text)); +} + +.workboard-dependency--ready { + border: 1px solid color-mix(in srgb, var(--ok) 24%, transparent); + background: color-mix(in srgb, var(--ok) 12%, transparent); + color: color-mix(in srgb, var(--ok) 82%, var(--text)); +} + .workboard-card__execution-controls { display: grid; grid-template-columns: repeat(2, minmax(72px, 1fr)); @@ -369,6 +507,15 @@ height: 15px; } +.workboard-card__quick-actions .workboard-card__icon, +.workboard-card__quick-actions .workboard-card__start--icon { + width: 26px; + min-width: 26px; + height: 26px; + min-height: 26px; + padding: 0; +} + .workboard-card__delete:hover { border-color: color-mix(in srgb, var(--danger) 34%, var(--border)); background: color-mix(in srgb, var(--danger) 14%, transparent); @@ -377,13 +524,18 @@ .workboard-card__start { min-height: 28px; - padding: 4px 9px; + gap: 6px; + padding: 4px 8px; border-radius: 6px; justify-content: center; min-width: 0; white-space: nowrap; } +.workboard-card__start--icon { + color: var(--text); +} + .workboard-card__start--manual { color: var(--muted); } @@ -410,6 +562,8 @@ .workboard-card__priority, .workboard-card__badges span, +.workboard-agent-chip, +.workboard-card__archived, .workboard-live, .workboard-lifecycle, .workboard-labels span { @@ -418,15 +572,92 @@ min-height: 20px; border-radius: 999px; padding: 2px 7px; - background: color-mix(in srgb, var(--border) 60%, transparent); - color: var(--muted); + background: color-mix(in srgb, var(--bg-hover) 72%, transparent); + color: color-mix(in srgb, var(--muted) 82%, var(--text) 18%); font-size: 0.72rem; line-height: 1; white-space: nowrap; } +.workboard-card__badges svg { + width: 13px; + height: 13px; + margin-right: 4px; + fill: none; + stroke: currentColor; + stroke-linecap: round; + stroke-linejoin: round; + stroke-width: 2; +} + +.workboard-card__badge--warning { + background: color-mix(in srgb, var(--warn) 17%, transparent) !important; + color: color-mix(in srgb, var(--warn) 86%, var(--text)) !important; +} + +.workboard-agent-chip { + max-width: 108px; + overflow: hidden; + background: color-mix(in srgb, var(--accent-2) 12%, var(--bg-hover)); + color: color-mix(in srgb, var(--accent-2) 62%, var(--text)); + text-overflow: ellipsis; +} + +.workboard-card__archived { + background: color-mix(in srgb, var(--muted) 14%, transparent); + color: var(--muted); +} + +.workboard-engine-mark { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 42px; + height: 18px; + border-radius: 5px; + padding: 0 5px; + font-size: 0.62rem; + font-weight: 800; + line-height: 1; +} + +.workboard-engine-mark--codex { + background: color-mix(in srgb, var(--ok) 18%, var(--bg-hover)); + color: color-mix(in srgb, var(--ok) 72%, var(--text)); +} + +.workboard-engine-mark--claude { + background: color-mix(in srgb, var(--warn) 20%, var(--bg-hover)); + color: color-mix(in srgb, var(--warn) 76%, var(--text)); +} + +.workboard-board--compact .workboard-column__cards { + gap: 8px; + padding: 8px; +} + +.workboard-board--compact .workboard-card { + gap: 6px; + padding: 9px; +} + +.workboard-board--compact .workboard-card h3 { + font-size: 0.9rem; + line-height: 1.22; +} + +.workboard-board--compact .workboard-card p { + -webkit-line-clamp: 2; + font-size: 0.78rem; +} + +.workboard-board--compact .workboard-labels, +.workboard-board--compact .workboard-card__badges { + gap: 4px; +} + .workboard-events { - display: grid; + display: none; gap: 4px; margin: 0; padding: 7px 0 0; @@ -468,11 +699,20 @@ color: var(--danger); } +.priority-high::before, +.priority-urgent::before { + background: color-mix(in srgb, var(--danger) 76%, var(--accent)); +} + .priority-low .workboard-card__priority { background: color-mix(in srgb, var(--ok) 16%, transparent); color: var(--ok); } +.priority-low::before { + background: color-mix(in srgb, var(--ok) 70%, transparent); +} + .workboard-live { background: color-mix(in srgb, var(--accent) 18%, transparent); color: var(--accent); @@ -619,6 +859,45 @@ line-height: 1.35; } +.workboard-detail__dependencies li { + display: grid; + grid-template-columns: auto minmax(0, 1fr) auto; + align-items: center; + gap: 7px; +} + +.workboard-detail__dependencies li.is-blocked { + border: 1px solid color-mix(in srgb, var(--warn) 26%, transparent); + background: color-mix(in srgb, var(--warn) 10%, var(--bg) 90%); +} + +.workboard-detail__dependencies svg { + width: 14px; + height: 14px; + color: var(--warn); + fill: none; + stroke: currentColor; + stroke-linecap: round; + stroke-linejoin: round; + stroke-width: 2; +} + +.workboard-detail__dependency-spacer { + width: 14px; +} + +.workboard-detail__dependencies span { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.workboard-detail__dependencies span:last-child { + color: var(--muted); + font-size: 0.76rem; +} + .workboard textarea.input.workboard-detail__note { width: 100%; min-height: 84px; @@ -664,7 +943,7 @@ } .workboard-board { - grid-auto-columns: minmax(260px, 82vw); + grid-auto-columns: minmax(282px, 84vw); } .workboard-detail-drawer { @@ -682,3 +961,26 @@ grid-template-columns: 1fr; } } + +@media (hover: hover) { + .workboard-card__actions { + opacity: 0.34; + transition: opacity var(--duration-fast) var(--ease-out); + } + + .workboard-card:hover .workboard-card__actions, + .workboard-card:focus-within .workboard-card__actions { + opacity: 1; + } +} + +@media (prefers-reduced-motion: reduce) { + .workboard-card, + .workboard-card__actions { + transition: none; + } + + .workboard-card--openable:hover { + transform: none; + } +} diff --git a/ui/src/ui/controllers/workboard.test.ts b/ui/src/ui/controllers/workboard.test.ts index a36c85d13fb2..4aaa7f07a79e 100644 --- a/ui/src/ui/controllers/workboard.test.ts +++ b/ui/src/ui/controllers/workboard.test.ts @@ -7,6 +7,7 @@ import { createWorkboardCard, deleteWorkboardCard, getWorkboardLifecycle, + getWorkboardDependencyState, getWorkboardState, loadWorkboard, moveWorkboardCard, @@ -139,6 +140,36 @@ describe("workboard controller", () => { expect(getWorkboardState(host).cards[0]).toMatchObject({ taskId: "task-1" }); }); + it("summarizes parent dependency readiness from loaded cards", () => { + const parentDone = { ...sampleCard, id: "parent-done", title: "Done parent", status: "done" }; + const parentTodo = { ...sampleCard, id: "parent-todo", title: "Todo parent", status: "todo" }; + const child = { + ...sampleCard, + id: "child-1", + metadata: { + links: [ + { id: "link-1", type: "parent", targetCardId: parentDone.id, createdAt: 1 }, + { id: "link-2", type: "parent", targetCardId: parentTodo.id, createdAt: 1 }, + { id: "link-3", type: "parent", targetCardId: "missing-parent", createdAt: 1 }, + ], + }, + } satisfies WorkboardCard; + + const dependencies = getWorkboardDependencyState(child, [parentDone, parentTodo, child]); + + expect( + dependencies.parents.map((parent) => [parent.title, parent.done, parent.missing]), + ).toEqual([ + ["Done parent", true, false], + ["Todo parent", false, false], + ["missing-parent", false, true], + ]); + expect(dependencies.blockedParents.map((parent) => parent.id)).toEqual([ + parentTodo.id, + "missing-parent", + ]); + }); + it("links unassigned default-agent tasks with canonicalized session keys", async () => { const host = {}; const linked = { @@ -717,7 +748,7 @@ describe("workboard controller", () => { ); }); - it("lets the gateway preflight decide starts when local parent state is stale", async () => { + it("lets the gateway decide starts when cached parent dependencies are stale", async () => { const host = {}; const parent = { ...sampleCard, id: "parent-1", title: "Parent", status: "running" }; const child: WorkboardCard = { @@ -728,13 +759,18 @@ describe("workboard controller", () => { links: [{ id: "link-1", type: "parent", targetCardId: parent.id, createdAt: 1 }], }, }; - const running = { ...child, status: "running", sessionKey: "agent:main:dashboard:child" }; + const running = { + ...child, + status: "running", + sessionKey: "subagent:workboard-default-child-1", + runId: "run-1", + } satisfies WorkboardCard; const client = createClient((method) => { if (method === "workboard.cards.list") { return { cards: [parent, child], statuses: ["todo", "running", "done"] }; } if (method === "agent") { - return { sessionKey: "agent:main:dashboard:child", runId: "run-child" }; + return { sessionKey: "subagent:workboard-default-child-1", runId: "run-1" }; } if (method === "tasks.list") { return { tasks: [] }; @@ -750,12 +786,17 @@ describe("workboard controller", () => { card: child, }); - expect(sessionKey).toBe("agent:main:dashboard:child"); + expect(sessionKey).toBe("subagent:workboard-default-child-1"); expect(client.request).toHaveBeenNthCalledWith( 1, "workboard.cards.update", expect.objectContaining({ id: child.id, patch: { status: "running" } }), ); + expect(client.request).toHaveBeenNthCalledWith( + 2, + "agent", + expect.objectContaining({ sessionKey: "subagent:workboard-default-child-1" }), + ); }); it("does not create a session when the gateway rejects start preflight", async () => { diff --git a/ui/src/ui/controllers/workboard.ts b/ui/src/ui/controllers/workboard.ts index 4c815d7ed5c4..a51ded2c30df 100644 --- a/ui/src/ui/controllers/workboard.ts +++ b/ui/src/ui/controllers/workboard.ts @@ -316,6 +316,19 @@ export type WorkboardTaskSummary = { error?: string; }; +export type WorkboardDependencyParent = { + id: string; + title: string; + status?: WorkboardStatus; + done: boolean; + missing: boolean; +}; + +export type WorkboardDependencyState = { + parents: WorkboardDependencyParent[]; + blockedParents: WorkboardDependencyParent[]; +}; + export type WorkboardDispatchSummary = { started: number; failures: number; @@ -336,6 +349,9 @@ export type WorkboardUiState = { lastDispatchSummary: WorkboardDispatchSummary | null; query: string; priorityFilter: "all" | WorkboardPriority; + agentFilter: string; + showArchived: boolean; + layout: "comfortable" | "compact"; draftOpen: boolean; editingCardId: string | null; draftTitle: string; @@ -380,6 +396,9 @@ function createDefaultState(): WorkboardUiState { lastDispatchSummary: null, query: "", priorityFilter: "all", + agentFilter: "all", + showArchived: false, + layout: "compact", draftOpen: false, editingCardId: null, draftTitle: "", @@ -1128,6 +1147,38 @@ function replaceCard(state: WorkboardUiState, card: WorkboardCard) { state.cards = next.toSorted((left, right) => left.position - right.position); } +function parentDependencyIds(card: WorkboardCard): string[] { + const ids: string[] = []; + for (const link of card.metadata?.links ?? []) { + const id = link.type === "parent" ? link.targetCardId?.trim() : ""; + if (id && !ids.includes(id)) { + ids.push(id); + } + } + return ids; +} + +export function getWorkboardDependencyState( + card: WorkboardCard, + cards: readonly WorkboardCard[], +): WorkboardDependencyState { + const cardsById = new Map(cards.map((entry) => [entry.id, entry])); + const parents = parentDependencyIds(card).map((id) => { + const parent = cardsById.get(id); + return { + id, + title: parent?.title ?? id, + status: parent?.status, + done: parent?.status === "done", + missing: !parent, + }; + }); + return { + parents, + blockedParents: parents.filter((parent) => !parent.done), + }; +} + function removeCardAndReferences(cards: readonly WorkboardCard[], cardId: string): WorkboardCard[] { const nextCards: WorkboardCard[] = []; for (const card of cards) { @@ -1775,6 +1826,7 @@ export async function archiveWorkboardCard(params: { host: WorkboardHost; client: GatewayBrowserClient | null; cardId: string; + archived?: boolean; requestUpdate?: () => void; }) { const state = getWorkboardState(params.host); @@ -1787,7 +1839,7 @@ export async function archiveWorkboardCard(params: { try { const payload = await params.client.request("workboard.cards.archive", { id: params.cardId, - archived: true, + archived: params.archived ?? true, }); replaceCard(state, normalizeCardPayload(payload)); } catch (error) { diff --git a/ui/src/ui/icons.ts b/ui/src/ui/icons.ts index 43af2a4d94c7..d543fdeae557 100644 --- a/ui/src/ui/icons.ts +++ b/ui/src/ui/icons.ts @@ -128,6 +128,43 @@ export const icons = { `, check: html` `, play: html` `, + archive: html` + + + + + + `, + archiveRestore: html` + + + + + + + `, + alertTriangle: html` + + + + + + `, + layoutComfortable: html` + + + + + + + `, + layoutCompact: html` + + + + + + `, arrowDown: html` diff --git a/ui/src/ui/views/workboard.test.ts b/ui/src/ui/views/workboard.test.ts index 4bb37eaed1a8..6eb93f64cb49 100644 --- a/ui/src/ui/views/workboard.test.ts +++ b/ui/src/ui/views/workboard.test.ts @@ -206,7 +206,7 @@ describe("renderWorkboard", () => { const startButtons = [ ...container.querySelectorAll(".workboard-card__start"), ]; - expect(startButtons.map((button) => button.textContent?.trim())).toEqual(["Start"]); + expect(startButtons.map((button) => button.textContent?.trim())).toEqual([""]); expect(startButtons.map((button) => button.title)).toEqual(["Run default agent"]); expect(container.querySelector(".workboard-card")?.getAttribute("role")).toBe("button"); @@ -229,15 +229,91 @@ describe("renderWorkboard", () => { const detailStartButtons = [ ...container.querySelectorAll(".workboard-detail .workboard-card__start"), ]; - expect(detailStartButtons.map((button) => button.textContent?.trim())).toEqual([ + expect(detailStartButtons.map((button) => button.textContent?.replace(/\s+/g, ""))).toEqual([ "Start", - "codex", - "claude", - "codex", - "claude", + "OpenAIRun", + "ClaudeRun", + "OpenAIOpen", + "ClaudeOpen", ]); }); + it("shows unfinished parent dependencies without blocking stale local starts", () => { + const host = {}; + const state = getWorkboardState(host); + state.loaded = true; + state.cards = [ + { + id: "parent-1", + title: "Finish art pass", + status: "todo", + priority: "normal", + labels: [], + position: 1000, + createdAt: 1, + updatedAt: 1, + }, + { + id: "child-1", + title: "Ship game shell", + status: "todo", + priority: "normal", + labels: [], + position: 2000, + createdAt: 1, + updatedAt: 1, + metadata: { + links: [{ id: "link-1", type: "parent", targetCardId: "parent-1", createdAt: 1 }], + }, + }, + ]; + const container = document.createElement("div"); + const props = { + host, + client: null, + connected: true, + pluginEnabled: true, + agentsList: null, + sessions: [], + onOpenSession: () => undefined, + onRequestUpdate: () => undefined, + } satisfies WorkboardRenderProps; + + render(renderWorkboard(props), container); + + const childCard = [...container.querySelectorAll(".workboard-card")].find((card) => + card.textContent?.includes("Ship game shell"), + ); + const start = childCard?.querySelector(".workboard-card__start"); + expect(childCard?.textContent).toContain("1 blocked"); + expect(start?.disabled).toBe(false); + expect(start?.title).toBe("Run default agent"); + + childCard + ?.querySelector('button[title="View details"]') + ?.dispatchEvent(new MouseEvent("click", { bubbles: true })); + render(renderWorkboard(props), container); + + const detail = container.querySelector(".workboard-detail"); + expect(detail?.textContent).toContain("Dependencies"); + expect(detail?.textContent).toContain("Finish art pass"); + expect(detail?.textContent).toContain("Todo"); + const detailRunButtons = [ + ...container.querySelectorAll( + ".workboard-detail .workboard-card__start--autonomous", + ), + ]; + const detailOpenButtons = [ + ...container.querySelectorAll( + ".workboard-detail .workboard-card__start--manual", + ), + ]; + expect(detailRunButtons.length).toBeGreaterThan(0); + expect(detailRunButtons.every((button) => button.disabled)).toBe(false); + expect(detailOpenButtons.length).toBeGreaterThan(0); + expect(detailOpenButtons.every((button) => button.disabled)).toBe(false); + }); + it("hides autonomous model override actions for non-admin operators", () => { const host = {}; const state = getWorkboardState(host); @@ -273,7 +349,7 @@ describe("renderWorkboard", () => { const startButtons = [ ...container.querySelectorAll(".workboard-card__start"), ]; - expect(startButtons.map((button) => button.textContent?.trim())).toEqual(["Start"]); + expect(startButtons.map((button) => button.textContent?.trim())).toEqual([""]); expect(startButtons.map((button) => button.title)).toEqual(["Run default agent"]); container @@ -295,10 +371,10 @@ describe("renderWorkboard", () => { const detailStartButtons = [ ...container.querySelectorAll(".workboard-detail .workboard-card__start"), ]; - expect(detailStartButtons.map((button) => button.textContent?.trim())).toEqual([ + expect(detailStartButtons.map((button) => button.textContent?.replace(/\s+/g, ""))).toEqual([ "Start", - "codex", - "claude", + "OpenAIOpen", + "ClaudeOpen", ]); }); @@ -751,6 +827,26 @@ describe("renderWorkboard", () => { expect(container.textContent).toContain("stale"); expect(container.textContent).not.toContain("Archived task"); + container + .querySelector('button[title="Show archived cards"]') + ?.dispatchEvent(new MouseEvent("click", { bubbles: true })); + render( + renderWorkboard({ + host, + client: null, + connected: true, + pluginEnabled: true, + agentsList: null, + sessions: [], + onOpenSession: () => undefined, + }), + container, + ); + expect(container.textContent).toContain("Archived task"); + expect( + container.querySelector('button[title="Hide archived cards"]'), + ).not.toBeNull(); + container .querySelector('button[title="View details"]') ?.dispatchEvent(new MouseEvent("click", { bubbles: true })); @@ -787,6 +883,136 @@ describe("renderWorkboard", () => { ); }); + it("filters cards by linked agent", () => { + const host = {}; + const state = getWorkboardState(host); + state.loaded = true; + state.cards = [ + { + id: "card-1", + title: "Main work", + status: "todo", + priority: "normal", + labels: [], + agentId: "main", + position: 1000, + createdAt: 1, + updatedAt: 1, + }, + { + id: "card-2", + title: "Ops work", + status: "todo", + priority: "normal", + labels: [], + agentId: "ops", + position: 2000, + createdAt: 1, + updatedAt: 1, + }, + ]; + const container = document.createElement("div"); + + render( + renderWorkboard({ + host, + client: null, + connected: true, + pluginEnabled: true, + agentsList: { + defaultId: "main", + mainKey: "agent:main:main", + scope: "test", + agents: [ + { id: "main", name: "Main" }, + { id: "ops", name: "Ops" }, + ], + }, + sessions: [], + onOpenSession: () => undefined, + }), + container, + ); + + const agentFilter = [...container.querySelectorAll("select")].find( + (select) => select.title === "Filter by agent", + ); + agentFilter!.value = "ops"; + agentFilter!.dispatchEvent(new Event("change", { bubbles: true })); + render( + renderWorkboard({ + host, + client: null, + connected: true, + pluginEnabled: true, + agentsList: { + defaultId: "main", + mainKey: "agent:main:main", + scope: "test", + agents: [ + { id: "main", name: "Main" }, + { id: "ops", name: "Ops" }, + ], + }, + sessions: [], + onOpenSession: () => undefined, + }), + container, + ); + + expect(container.textContent).not.toContain("Main work"); + expect(container.textContent).toContain("Ops work"); + expect(container.textContent).toContain("Ops"); + }); + + it("preflights model-specific starts for ACP runtime agents", () => { + const host = {}; + const state = getWorkboardState(host); + state.loaded = true; + state.detailCardId = "card-1"; + state.cards = [ + { + id: "card-1", + title: "ACP-backed work", + status: "todo", + priority: "normal", + labels: [], + agentId: "main", + position: 1000, + createdAt: 1, + updatedAt: 1, + }, + ]; + const container = document.createElement("div"); + + render( + renderWorkboard({ + host, + client: null, + connected: true, + pluginEnabled: true, + agentsList: { + defaultId: "main", + mainKey: "agent:main:main", + scope: "test", + agents: [{ id: "main", name: "Main", agentRuntime: { id: "codex", source: "agent" } }], + }, + sessions: [], + onOpenSession: () => undefined, + }), + container, + ); + + const engineButtons = [ + ...container.querySelectorAll( + ".workboard-detail .workboard-card__start:not(.workboard-card__start--default)", + ), + ]; + expect(engineButtons).toHaveLength(4); + expect(engineButtons.every((button) => button.disabled)).toBe(true); + expect(engineButtons[0]?.title).toContain("uses the codex ACP runtime"); + }); + it("does not render details for archived selected cards", () => { const host = {}; const state = getWorkboardState(host); diff --git a/ui/src/ui/views/workboard.ts b/ui/src/ui/views/workboard.ts index eb9178667701..a387fd99d4b3 100644 --- a/ui/src/ui/views/workboard.ts +++ b/ui/src/ui/views/workboard.ts @@ -1,4 +1,4 @@ -import { html, nothing } from "lit"; +import { html, nothing, type TemplateResult } from "lit"; import { t } from "../../i18n/index.ts"; import { addWorkboardCardComment, @@ -6,6 +6,7 @@ import { deleteWorkboardCard, dispatchWorkboard, findWorkboardSession, + getWorkboardDependencyState, getWorkboardLifecycle, getWorkboardState, loadWorkboard, @@ -15,6 +16,7 @@ import { stopWorkboardCard, syncWorkboardLifecycle, WORKBOARD_PRIORITIES, + type WorkboardDependencyState, type WorkboardExecutionEngine, type WorkboardExecutionMode, type WorkboardCard, @@ -31,6 +33,8 @@ import type { GatewayBrowserClient } from "../gateway.ts"; import { icons } from "../icons.ts"; import type { AgentsListResult, GatewaySessionRow } from "../types.ts"; +type WorkboardAgentRow = AgentsListResult["agents"][number]; + type WorkboardProps = { host: object; client: GatewayBrowserClient | null; @@ -187,30 +191,59 @@ function renderEvents(card: WorkboardCard) { function renderCompactBadges(card: WorkboardCard, task?: WorkboardTaskSummary) { const metadata = card.metadata; - const badges = [ - metadata?.templateId ? t(`workboard.template.${metadata.templateId}`) : null, - (task ?? card.taskId) ? t("workboard.badgeTaskLinked") : null, - metadata?.failureCount - ? t("workboard.badgeFailures", { count: String(metadata.failureCount) }) - : null, - metadata?.comments?.length - ? t("workboard.badgeComments", { count: String(metadata.comments.length) }) - : null, - metadata?.proof?.length - ? t("workboard.badgeProof", { count: String(metadata.proof.length) }) - : null, - metadata?.claim ? t("workboard.badgeClaimed", { owner: metadata.claim.ownerId }) : null, - metadata?.diagnostics?.length - ? t("workboard.badgeDiagnostics", { count: String(metadata.diagnostics.length) }) - : null, - metadata?.stale ? t("workboard.badgeStale") : null, - ].filter((badge): badge is string => Boolean(badge)); + const badges: TemplateResult[] = []; + if (metadata?.templateId) { + badges.push(html`${t(`workboard.template.${metadata.templateId}`)}`); + } + if (task ?? card.taskId) { + badges.push(html`${t("workboard.badgeTaskLinked")}`); + } + if (metadata?.failureCount) { + badges.push(html` + + ${icons.alertTriangle}${t("workboard.badgeFailures", { + count: String(metadata.failureCount), + })} + + `); + } + if (metadata?.comments?.length) { + badges.push( + html`${t("workboard.badgeComments", { count: String(metadata.comments.length) })}`, + ); + } + if (metadata?.proof?.length) { + badges.push( + html`${t("workboard.badgeProof", { count: String(metadata.proof.length) })}`, + ); + } + if (metadata?.claim) { + badges.push( + html`${t("workboard.badgeClaimed", { owner: metadata.claim.ownerId })}`, + ); + } + if (metadata?.diagnostics?.length) { + badges.push( + html` + ${icons.alertTriangle}${t("workboard.badgeDiagnostics", { + count: String(metadata.diagnostics.length), + })} + `, + ); + } + if (metadata?.stale) { + badges.push( + html`${icons.alertTriangle}${t("workboard.badgeStale")}`, + ); + } if (badges.length === 0) { return nothing; } - return html` -
${badges.map((badge) => html`${badge}`)}
- `; + return html`
${badges}
`; } function matchesFilter( @@ -299,6 +332,112 @@ function isCardActionTarget(event: Event): boolean { : false; } +function agentDisplayName(agent: WorkboardAgentRow | undefined, fallback: string): string { + return agent?.name ?? agent?.identity?.name ?? agent?.id ?? fallback; +} + +function cardAgentId(card: WorkboardCard, agentsList: AgentsListResult | null): string { + return card.agentId?.trim() || agentsList?.defaultId || ""; +} + +function findCardAgent( + card: WorkboardCard, + agentsList: AgentsListResult | null, +): WorkboardAgentRow | undefined { + const id = cardAgentId(card, agentsList); + return id ? agentsList?.agents.find((agent) => agent.id === id) : undefined; +} + +function cardAgentLabel(card: WorkboardCard, agentsList: AgentsListResult | null): string { + const fallback = card.agentId?.trim() || t("workboard.defaultAgent"); + return agentDisplayName(findCardAgent(card, agentsList), fallback); +} + +function matchesAgentFilter( + card: WorkboardCard, + agentsList: AgentsListResult | null, + filter: WorkboardUiState["agentFilter"], +): boolean { + if (filter === "all") { + return true; + } + const explicitAgentId = card.agentId?.trim(); + if (filter === "default") { + return !explicitAgentId; + } + return explicitAgentId === filter || (!explicitAgentId && agentsList?.defaultId === filter); +} + +function buildAgentFilterOptions( + cards: readonly WorkboardCard[], + agentsList: AgentsListResult | null, +) { + const seen = new Set(); + const options: Array<{ id: WorkboardUiState["agentFilter"]; label: string }> = [ + { id: "all", label: t("workboard.allAgents") }, + { id: "default", label: t("workboard.defaultAgent") }, + ]; + for (const agent of agentsList?.agents ?? []) { + if (seen.has(agent.id)) { + continue; + } + seen.add(agent.id); + options.push({ id: agent.id, label: agentDisplayName(agent, agent.id) }); + } + for (const card of cards) { + const id = card.agentId?.trim(); + if (!id || seen.has(id)) { + continue; + } + seen.add(id); + options.push({ id, label: id }); + } + return options; +} + +function engineDisplayName(engine: WorkboardExecutionEngine): string { + return engine === "codex" ? t("workboard.engineOpenAI") : t("workboard.engineClaude"); +} + +function engineBlockedByRuntime( + props: WorkboardProps, + card: WorkboardCard, + engine: WorkboardExecutionEngine | null, +): string | null { + if (!engine) { + return null; + } + const agent = findCardAgent(card, props.agentsList); + const runtime = agent?.agentRuntime?.id?.trim(); + if (!runtime) { + return null; + } + const normalized = runtime.toLowerCase(); + if (normalized === "openclaw" || normalized === "pi") { + return null; + } + return t("workboard.engineDisabledRuntime", { + agent: agentDisplayName(agent, card.agentId ?? t("workboard.defaultAgent")), + runtime, + }); +} + +function renderAgentChip(props: WorkboardProps, card: WorkboardCard) { + const label = cardAgentLabel(card, props.agentsList); + const title = card.agentId + ? t("workboard.agentLinked", { agent: label }) + : t("workboard.agentDefaultLinked", { agent: label }); + return html`${label}`; +} + +function renderEngineMark(engine: WorkboardExecutionEngine) { + return html` + + `; +} + function openCardDetails(state: WorkboardUiState, card: WorkboardCard) { state.detailCardId = card.id; state.detailCommentBody = ""; @@ -699,6 +838,82 @@ function cardCanStart( return !activeTask && (!linkedSessionKey || !session); } +function formatDependencyParent(parent: WorkboardDependencyState["parents"][number]): string { + if (parent.missing) { + return t("workboard.dependencyMissing", { parent: parent.title }); + } + const status = parent.status ? formatStatusLabel(parent.status) : t("workboard.unknownStatus"); + return `${parent.title} (${status})`; +} + +function formatDependencyBlockerTitle(dependencies: WorkboardDependencyState): string | null { + if (dependencies.blockedParents.length === 0) { + return null; + } + return t("workboard.dependenciesBlockedTitle", { + parents: dependencies.blockedParents.map(formatDependencyParent).join(", "), + }); +} + +function renderDependencyBadges(dependencies: WorkboardDependencyState) { + if (dependencies.parents.length === 0) { + return nothing; + } + const blocked = dependencies.blockedParents.length; + const title = + formatDependencyBlockerTitle(dependencies) ?? + t("workboard.dependenciesReadyTitle", { + count: String(dependencies.parents.length), + }); + return html` +
+ ${blocked > 0 + ? html` + + ${icons.alertTriangle}${t("workboard.dependenciesBlocked", { + count: String(blocked), + })} + + ` + : html` + + ${t("workboard.dependenciesReady", { count: String(dependencies.parents.length) })} + + `} +
+ `; +} + +function renderDependencyDetailList(dependencies: WorkboardDependencyState) { + if (dependencies.parents.length === 0) { + return nothing; + } + return html` +
+

${t("workboard.dependencies")}

+
    + ${dependencies.parents.map( + (parent) => html` +
  • + ${parent.done + ? html`` + : icons.alertTriangle} + ${parent.title} + + ${parent.missing + ? t("workboard.dependencyStatusMissing") + : parent.status + ? formatStatusLabel(parent.status) + : t("workboard.unknownStatus")} + +
  • + `, + )} +
+
+ `; +} + function renderLifecycle( card: WorkboardCard, sessions: readonly GatewaySessionRow[], @@ -733,21 +948,29 @@ function renderStartExecutionButton( card: WorkboardCard, engine: WorkboardExecutionEngine | null, mode: WorkboardExecutionMode, + options: { iconOnly?: boolean } = {}, ) { const state = getWorkboardState(props.host); const busy = state.busyCardId === card.id; - const title = engine - ? mode === "autonomous" - ? t("workboard.runEngine", { engine }) - : t("workboard.openEngine", { engine }) - : t("workboard.runDefaultAgent"); + const runtimeBlock = engineBlockedByRuntime(props, card, engine); + const disabled = + busy || !props.connected || Boolean(runtimeBlock) || Boolean(card.metadata?.archivedAt); + const title = runtimeBlock + ? runtimeBlock + : engine + ? mode === "autonomous" + ? t("workboard.runEngine", { engine: engineDisplayName(engine) }) + : t("workboard.openEngine", { engine: engineDisplayName(engine) }) + : t("workboard.runDefaultAgent"); return html` `; } @@ -825,7 +1056,7 @@ function renderCardDetailsPanel(props: WorkboardProps) { const card = state.detailCardId ? (state.cards.find((entry) => entry.id === state.detailCardId) ?? null) : null; - if (!card || card.metadata?.archivedAt) { + if (!card || (card.metadata?.archivedAt && !state.showArchived)) { return nothing; } const task = state.tasksByCardId.get(card.id); @@ -847,6 +1078,7 @@ function renderCardDetailsPanel(props: WorkboardProps) { const events = (card.events ?? []).slice(-6).toReversed(); const busy = state.busyCardId === card.id; const showStartControls = writable && cardCanStart(state, props.sessions, card); + const dependencies = getWorkboardDependencyState(card, state.cards); return html`