diff --git a/static/i18n.js b/static/i18n.js index af1669a6..e910228c 100644 --- a/static/i18n.js +++ b/static/i18n.js @@ -490,6 +490,7 @@ const LOCALES = { kanban_include_archived: 'Include archived', kanban_no_matching_tasks: 'No matching tasks', kanban_no_data: 'No Kanban data', + kanban_work_queue_hint: 'This is the Hermes Agent work queue. Create or triage a task, assign it, move it to Ready, then let the dispatcher claim it.', kanban_unavailable: 'Kanban unavailable', kanban_read_only: 'Read-only view', kanban_empty: 'Empty', @@ -515,7 +516,7 @@ const LOCALES = { kanban_add_comment: 'Add comment', kanban_only_mine: 'Only mine', kanban_bulk_action: 'Bulk action', - kanban_nudge_dispatcher: 'Nudge dispatcher', + kanban_nudge_dispatcher: 'Preview dispatcher', kanban_stats: 'Stats', kanban_worker_log: 'Worker log', kanban_block: 'Block', @@ -1509,6 +1510,7 @@ const LOCALES = { kanban_include_archived: 'アーカイブを含める', kanban_no_matching_tasks: '一致するタスクがありません', kanban_no_data: 'カンバンデータがありません', + kanban_work_queue_hint: 'これは Hermes Agent のワークキューです。タスクを作成またはトリアージし、担当者を割り当て、Ready に移動すると、ディスパッチャーがそれをクレームします。', kanban_unavailable: 'カンバンを利用できません', kanban_read_only: '読み取り専用', kanban_empty: '空', @@ -1534,7 +1536,7 @@ const LOCALES = { kanban_add_comment: 'コメント追加', kanban_only_mine: '自分のみ', kanban_bulk_action: '一括操作', - kanban_nudge_dispatcher: 'ディスパッチャーに催促', + kanban_nudge_dispatcher: 'ディスパッチャープレビュー', kanban_stats: '統計', kanban_worker_log: 'ワーカーログ', kanban_block: 'ブロック', @@ -2367,6 +2369,7 @@ const LOCALES = { kanban_include_archived: 'Include archived', kanban_no_matching_tasks: 'No matching tasks', kanban_no_data: 'No Kanban data', + kanban_work_queue_hint: 'This is the Hermes Agent work queue. Create or triage a task, assign it, move it to Ready, then let the dispatcher claim it.', kanban_unavailable: 'Kanban unavailable', kanban_read_only: 'Read-only view', kanban_empty: 'Empty', @@ -2392,7 +2395,7 @@ const LOCALES = { kanban_add_comment: 'Add comment', kanban_only_mine: 'Only mine', kanban_bulk_action: 'Bulk action', - kanban_nudge_dispatcher: 'Nudge dispatcher', + kanban_nudge_dispatcher: 'Preview dispatcher', kanban_stats: 'Stats', kanban_worker_log: 'Worker log', kanban_block: 'Block', @@ -3320,6 +3323,7 @@ const LOCALES = { kanban_include_archived: 'Include archived', kanban_no_matching_tasks: 'No matching tasks', kanban_no_data: 'No Kanban data', + kanban_work_queue_hint: 'This is the Hermes Agent work queue. Create or triage a task, assign it, move it to Ready, then let the dispatcher claim it.', kanban_unavailable: 'Kanban unavailable', kanban_read_only: 'Read-only view', kanban_empty: 'Empty', @@ -3345,7 +3349,7 @@ const LOCALES = { kanban_add_comment: 'Add comment', kanban_only_mine: 'Only mine', kanban_bulk_action: 'Bulk action', - kanban_nudge_dispatcher: 'Nudge dispatcher', + kanban_nudge_dispatcher: 'Preview dispatcher', kanban_stats: 'Stats', kanban_worker_log: 'Worker log', kanban_block: 'Block', @@ -4261,6 +4265,7 @@ const LOCALES = { kanban_include_archived: 'Include archived', kanban_no_matching_tasks: 'No matching tasks', kanban_no_data: 'No Kanban data', + kanban_work_queue_hint: 'This is the Hermes Agent work queue. Create or triage a task, assign it, move it to Ready, then let the dispatcher claim it.', kanban_unavailable: 'Kanban unavailable', kanban_read_only: 'Read-only view', kanban_empty: 'Empty', @@ -4286,7 +4291,7 @@ const LOCALES = { kanban_add_comment: 'Add comment', kanban_only_mine: 'Only mine', kanban_bulk_action: 'Bulk action', - kanban_nudge_dispatcher: 'Nudge dispatcher', + kanban_nudge_dispatcher: 'Preview dispatcher', kanban_stats: 'Stats', kanban_worker_log: 'Worker log', kanban_block: 'Block', @@ -5223,6 +5228,7 @@ const LOCALES = { kanban_include_archived: 'Include archived', kanban_no_matching_tasks: 'No matching tasks', kanban_no_data: 'No Kanban data', + kanban_work_queue_hint: 'This is the Hermes Agent work queue. Create or triage a task, assign it, move it to Ready, then let the dispatcher claim it.', kanban_unavailable: 'Kanban unavailable', kanban_read_only: 'Read-only view', kanban_empty: 'Empty', @@ -5248,7 +5254,7 @@ const LOCALES = { kanban_add_comment: 'Add comment', kanban_only_mine: 'Only mine', kanban_bulk_action: 'Bulk action', - kanban_nudge_dispatcher: 'Nudge dispatcher', + kanban_nudge_dispatcher: 'Preview dispatcher', kanban_stats: 'Stats', kanban_worker_log: 'Worker log', kanban_block: 'Block', @@ -7216,6 +7222,7 @@ const LOCALES = { kanban_include_archived: 'Include archived', kanban_no_matching_tasks: 'No matching tasks', kanban_no_data: 'No Kanban data', + kanban_work_queue_hint: 'This is the Hermes Agent work queue. Create or triage a task, assign it, move it to Ready, then let the dispatcher claim it.', kanban_unavailable: 'Kanban unavailable', kanban_read_only: 'Read-only view', kanban_empty: 'Empty', @@ -7241,7 +7248,7 @@ const LOCALES = { kanban_add_comment: 'Add comment', kanban_only_mine: 'Only mine', kanban_bulk_action: 'Bulk action', - kanban_nudge_dispatcher: 'Nudge dispatcher', + kanban_nudge_dispatcher: 'Preview dispatcher', kanban_stats: 'Stats', kanban_worker_log: 'Worker log', kanban_block: 'Block', @@ -8133,6 +8140,7 @@ const LOCALES = { kanban_include_archived: 'Include archived', kanban_no_matching_tasks: 'No matching tasks', kanban_no_data: 'No Kanban data', + kanban_work_queue_hint: 'This is the Hermes Agent work queue. Create or triage a task, assign it, move it to Ready, then let the dispatcher claim it.', kanban_unavailable: 'Kanban unavailable', kanban_read_only: 'Read-only view', kanban_empty: 'Empty', @@ -8158,7 +8166,7 @@ const LOCALES = { kanban_add_comment: 'Add comment', kanban_only_mine: 'Only mine', kanban_bulk_action: 'Bulk action', - kanban_nudge_dispatcher: 'Nudge dispatcher', + kanban_nudge_dispatcher: 'Preview dispatcher', kanban_stats: 'Stats', kanban_worker_log: 'Worker log', kanban_block: 'Block', diff --git a/static/index.html b/static/index.html index c1f8670f..d21c64b4 100644 --- a/static/index.html +++ b/static/index.html @@ -158,9 +158,9 @@
- + - +
@@ -709,7 +709,7 @@
- +
diff --git a/static/panels.js b/static/panels.js index a8a53640..14d210d6 100644 --- a/static/panels.js +++ b/static/panels.js @@ -1096,10 +1096,9 @@ function _kanbanCardStalenessClass(task){ function _kanbanCardQuickActions(task){ const id = esc(task.id || ''); const status = task.status || ''; - const start = status !== 'running' && status !== 'done' && status !== 'archived' ? `` : ''; const complete = status !== 'done' && status !== 'archived' ? `` : ''; const archive = status !== 'archived' ? `` : ''; - return `
${start}${complete}${archive}
`; + return `
${complete}${archive}
`; } async function quickKanbanCardAction(event, taskId, status){ @@ -1168,17 +1167,25 @@ function _kanbanRenderProfileLanes(columns){ }).join('')}`; } +function _kanbanEmptyBoardHtml(){ + return `
${esc(t('kanban_no_data'))}
${esc(t('kanban_work_queue_hint'))}
`; +} + function _kanbanRenderBoard(){ const board = $('kanbanBoard'); if (!board) return; if (!_kanbanBoard || !_kanbanBoard.columns) { - board.innerHTML = `
${esc(t('kanban_no_data'))}
`; + board.innerHTML = _kanbanEmptyBoardHtml(); return; } const columns = _kanbanVisibleTasks(); const total = columns.reduce((n, col) => n + (col.tasks || []).length, 0); if ($('kanbanSummary')) $('kanbanSummary').textContent = String(t('kanban_visible_tasks')).replace('{0}', total); _kanbanRenderSidebar(columns); + if (total === 0) { + board.innerHTML = _kanbanEmptyBoardHtml(); + return; + } board.innerHTML = _kanbanLanesByProfile ? _kanbanRenderProfileLanes(columns) : columns.map(_kanbanRenderColumn).join(''); } @@ -1219,6 +1226,7 @@ async function hardRefreshWebUIClient(){ function _kanbanLooksLikeStaleClientError(err){ const msg = String((err && err.message) || err || '').toLowerCase(); return !!(err && err.status === 404 && ( + msg === 'not found' || msg.includes('unknown kanban endpoint') || msg.includes('stale cached bundle') )); diff --git a/tests/test_kanban_ui_static.py b/tests/test_kanban_ui_static.py index 3ff1ca15..b9ed8372 100644 --- a/tests/test_kanban_ui_static.py +++ b/tests/test_kanban_ui_static.py @@ -164,6 +164,7 @@ def test_kanban_dashboard_parity_i18n_keys_exist(): "kanban_only_mine", "kanban_bulk_action", "kanban_nudge_dispatcher", + "kanban_work_queue_hint", "kanban_stats", "kanban_worker_log", "kanban_block", @@ -205,6 +206,16 @@ def test_kanban_ui_parity_polish_adds_card_metadata_quick_actions_and_swimlanes( assert "javascript:" not in PANELS.lower() +def test_kanban_lifecycle_controls_do_not_offer_manual_running_start(): + assert "quickKanbanCardAction(event,'${id}','running')" not in PANELS + assert "kanban_card_start" not in PANELS + assert '' not in INDEX + assert "Cannot set status to 'running' directly" not in PANELS + assert "kanban_work_queue_hint" in PANELS + assert "Preview dispatcher" in INDEX + assert "Nudge dispatcher" not in INDEX + + def test_kanban_ui_parity_polish_css_and_i18n_exist(): for selector in ( ".kanban-profile-lanes", @@ -219,7 +230,7 @@ def test_kanban_ui_parity_polish_css_and_i18n_exist(): ): assert selector in STYLE locale_blocks = re.findall(r"\n\s*([a-z]{2}(?:-[A-Z]{2})?): \{(.*?)\n\s*\},", I18N, flags=re.S) - required_keys = ["kanban_lanes_by_profile", "kanban_card_start", "kanban_card_complete", "kanban_card_archive", "kanban_unassigned"] + required_keys = ["kanban_lanes_by_profile", "kanban_card_complete", "kanban_card_archive", "kanban_unassigned", "kanban_work_queue_hint"] missing = [ f"{locale}:{key}" for locale, body in locale_blocks