Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
a0b757a
Fix Kanban dispatch double-click race guard
May 10, 2026
8f077d3
Fix German profile_skill_count interpolation
May 10, 2026
b06eb99
fix(kanban): invalidate profile cache for assignee select
May 10, 2026
b67d267
fix(kanban): show original status hint in edit modal
May 10, 2026
45a5253
fix(i18n): add kanban status hint key to all locales for #1994
May 10, 2026
42a2381
Fix 1974: trap focus in kanban modals
May 10, 2026
4c95d92
test: add kanban modal locale parity regression
May 10, 2026
2427f1e
test(kanban): harden locale-block parsing for quoted locales
May 10, 2026
ba51efe
test(kanban): assert profile-cache invalidation on profile delete
May 10, 2026
c6e4306
Stage 329: PR #1990 — fix(kanban): guard dispatcher actions while in-…
nesquena-hermes May 10, 2026
bf98ffe
Stage 329: PR #1991 — fix(i18n): correct German profile_skill_count p…
nesquena-hermes May 10, 2026
7ce48de
Stage 329: PR #1993 — fix(kanban): invalidate profile cache for assig…
nesquena-hermes May 10, 2026
b01df72
Stage 329: PR #1995 — feat(kanban): trap focus in kanban modals + sta…
nesquena-hermes May 10, 2026
f44ead1
Stage 329: PR #1996 — test(kanban): modal locale parity regression by…
nesquena-hermes May 10, 2026
52c1053
chore: CHANGELOG for v0.51.35 — Release K (kanban polish + i18n DE)
nesquena-hermes May 10, 2026
9242305
fix(stage-329): zh-Hant locale parity for kanban_status_original_hint…
nesquena-hermes May 10, 2026
941c805
chore: CHANGELOG note for stage augmentation 9242305a
nesquena-hermes May 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,36 @@
# Hermes Web UI -- Changelog

## [v0.51.35] — 2026-05-10 — Release K (kanban polish + i18n DE pluralization)

### Fixed

- **PR #1990** by @franksong2702 — Kanban dispatcher race guard. Adds `_kanbanIsDispatching` flag around `runKanbanDispatcher()` and `nudgeKanbanDispatcher()` in `static/panels.js`; both Run/Preview buttons go disabled while the call is in-flight, so a fast double-click can't fire the dispatcher twice (which would post duplicate POSTs and surface duplicate toasts). Re-enables on success or error in `finally`. Closes #1984.

- **PR #1991** by @franksong2702 — German `profile_skill_count` pluralization. The DE locale had `profile_skill_count: '{count} Fähigkeiten'` as a literal string with the placeholder token still in it (so 1, 2, 5 skills all rendered as `{count} Fähigkeiten`). Switched to the same `(count) => …` interpolation function form already used by the other locales. Regression test `tests/test_issue1989_profile_skill_count.py` pins DE to function form and asserts the literal token never reaches the rendered string. Closes #1989.

- **PR #1993** by @franksong2702 — Kanban assignee-dropdown profile cache invalidation. `_kanbanProfileNamesCache` was populated lazily on first modal open and never expired; creating or deleting a profile elsewhere in the UI didn't refresh it, so the assignee dropdown could show a freshly-deleted profile or miss a freshly-created one. Added a 30-second TTL (`_kanbanProfileNamesCacheAt` + `_KANBAN_PROFILE_NAMES_CACHE_TTL_MS`) and an explicit `_invalidateKanbanProfileCache()` helper called from `saveProfileForm()`, `deleteCurrentProfile()`, and `deleteProfile()`. Closes #1985.

- **PR #1995** by @franksong2702 — Kanban modal focus trap + edit-mode status hint. Two related fixes bundled (#1995 was rebased on top of #1994 in the contributor's branch):
- **Focus trap (#1974).** Tab/Shift-Tab in the Kanban task and board modals could move keyboard focus to controls behind the modal. Added a shared `_trapModalFocus(modalEl)` helper in `static/panels.js`; wired into `openKanbanCreate()`, `openKanbanEdit()`, `openKanbanCreateBoard()`, and `openKanbanRenameBoard()`. Cleanup tracker `_kanbanTaskModalFocusCleanup` removes the trap on close so a sequence of open→close→open doesn't leak listeners.
- **Status hint (#1986).** When opening Edit on a task whose real status is `running`/`blocked`/`done`/`archived` (which the dropdown displays as `triage` because the dispatcher only writes to `triage`/`todo`/`ready`), the modal now shows an inline hint explaining the displayed-vs-real mismatch. The dropdown behaviour is unchanged — only an additional UX cue. New CSS for `.kanban-status-hint`, new i18n key `kanban_status_hint_real` across all 8 locales.

Closes #1974, #1986.

- **PR #1996** by @franksong2702 — Kanban modal locale parity regression test. Adds `tests/test_kanban_ui_static.py::test_kanban_modal_locales_have_full_modal_vocabulary` that anchors on the existing `kanban_no_comments` key and asserts every locale supporting Kanban has the modal vocabulary. Hardens locale-block parsing to handle quoted locales. Pure test addition.

### Tests

5049 → **5054 collected, 5054 passing, 0 regressions** (+5 net new). Full suite 154s on Python 3.11 with `HERMES_HOME` isolation.

### Stage augmentation

- **`9242305a`** — Opus advisor flagged that `kanban_status_original_hint` (added by #1995) was missing in the `zh-Hant` block, so Traditional Chinese users would get the English fallback. Added the Traditional Chinese translation (`實際狀態:{0}。此對話框僅支援編輯 Triage/Todo/Ready。`) at line 6537 and extended `tests/test_kanban_ui_static.py::test_kanban_modal_locales_have_full_modal_vocabulary`'s `modal_keys` list to assert the key — so any future kanban modal key added without zh-Hant translation will fail CI.

### Notes

- `static/panels.js` was the high-collision file in this batch (5 PRs touched it). Stage merge cleanly; one syntactic conflict at the `_kanbanProfileNamesCache` declaration block when #1995 landed on top of #1993 — both PRs added new module-level `let` declarations adjacent to `_kanbanProfileNamesCache`. Resolved by preserving both declaration blocks (the variables are independent).
- Six PRs in batch, all from @franksong2702. Disjoint concerns, disjoint i18n keys, disjoint tests. The 5-files panels.js overlap was the only nontrivial integration risk and resolved cleanly.

## [v0.51.34] — 2026-05-09 — Release J (kanban edit/dispatch + zh-Hant kanban i18n)

### Added
Expand Down
11 changes: 10 additions & 1 deletion static/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,7 @@ const LOCALES = {
kanban_title_required: 'Title is required.',
kanban_new_task: 'New task',
kanban_edit_task: 'Edit task',
kanban_status_original_hint: 'Actual status: {0}. This dialog only supports Triage/Todo/Ready edits.',
kanban_run_dispatcher: 'Run dispatcher',
kanban_run_dispatcher_confirm: 'This will claim Ready tasks on this board and spawn worker subprocesses (one per task, up to 8 per click). Continue?',
kanban_assignee_profiles_label: 'Hermes profiles',
Expand Down Expand Up @@ -1589,6 +1590,7 @@ const LOCALES = {
kanban_status_running: '実行中',
kanban_status_blocked: 'ブロック中',
kanban_status_done: '完了',
kanban_status_original_hint: 'Actual status: {0}. This dialog only supports Triage/Todo/Ready edits.',
kanban_comments_count: 'コメント ({0})',
kanban_events_count: 'イベント ({0})',
kanban_links: 'リンク',
Expand Down Expand Up @@ -2490,6 +2492,7 @@ const LOCALES = {
kanban_status_running: 'Running',
kanban_status_blocked: 'Blocked',
kanban_status_done: 'Done',
kanban_status_original_hint: 'Actual status: {0}. This dialog only supports Triage/Todo/Ready edits.',
kanban_comments_count: 'Comments ({0})',
kanban_events_count: 'Events ({0})',
kanban_links: 'Links',
Expand Down Expand Up @@ -3492,6 +3495,7 @@ const LOCALES = {
kanban_status_running: 'Running',
kanban_status_blocked: 'Blocked',
kanban_status_done: 'Done',
kanban_status_original_hint: 'Actual status: {0}. This dialog only supports Triage/Todo/Ready edits.',
kanban_comments_count: 'Comments ({0})',
kanban_events_count: 'Events ({0})',
kanban_links: 'Links',
Expand Down Expand Up @@ -4482,6 +4486,7 @@ const LOCALES = {
kanban_status_running: 'Running',
kanban_status_blocked: 'Blocked',
kanban_status_done: 'Done',
kanban_status_original_hint: 'Actual status: {0}. This dialog only supports Triage/Todo/Ready edits.',
kanban_comments_count: 'Comments ({0})',
kanban_events_count: 'Events ({0})',
kanban_links: 'Links',
Expand Down Expand Up @@ -5027,7 +5032,7 @@ const LOCALES = {
profile_gateway_stopped: 'Gateway gestoppt',
profile_active: 'Aktiv',
profile_no_configuration: 'Keine Konfiguration',
profile_skill_count: '{count} Fähigkeiten',
profile_skill_count: (count) => `${count} Fähigkeit${count === 1 ? '' : 'en'}`,
profile_use: 'Verwenden',
profile_switch_title: 'Profil wechseln',
profile_delete_title: 'Profil löschen',
Expand Down Expand Up @@ -5493,6 +5498,7 @@ const LOCALES = {
kanban_status_running: 'Running',
kanban_status_blocked: 'Blocked',
kanban_status_done: 'Done',
kanban_status_original_hint: 'Actual status: {0}. This dialog only supports Triage/Todo/Ready edits.',
kanban_comments_count: 'Comments ({0})',
kanban_events_count: 'Events ({0})',
kanban_links: 'Links',
Expand Down Expand Up @@ -6529,6 +6535,7 @@ const LOCALES = {
kanban_description: '描述',
kanban_description_placeholder: '選填 — 需要完成的事項、驗收標準、連結',
kanban_status: '狀態',
kanban_status_original_hint: '實際狀態:{0}。此對話框僅支援編輯 Triage/Todo/Ready。',
kanban_assignee: '指派對象',
kanban_assignee_placeholder: '選填 — 個人資料或名稱',
kanban_tenant: '租戶',
Expand Down Expand Up @@ -7648,6 +7655,7 @@ const LOCALES = {
kanban_status_running: 'Running',
kanban_status_blocked: 'Blocked',
kanban_status_done: 'Done',
kanban_status_original_hint: 'Actual status: {0}. This dialog only supports Triage/Todo/Ready edits.',
kanban_comments_count: 'Comments ({0})',
kanban_events_count: 'Events ({0})',
kanban_links: 'Links',
Expand Down Expand Up @@ -8614,6 +8622,7 @@ const LOCALES = {
kanban_status_running: 'Running',
kanban_status_blocked: 'Blocked',
kanban_status_done: 'Done',
kanban_status_original_hint: 'Actual status: {0}. This dialog only supports Triage/Todo/Ready edits.',
kanban_comments_count: 'Comments ({0})',
kanban_events_count: 'Events ({0})',
kanban_links: 'Links',
Expand Down
7 changes: 4 additions & 3 deletions static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,8 @@
<div id="kanbanBulkBar" class="kanban-bulk-bar">
<select id="kanbanBulkStatus" aria-label="Bulk status"><option value="">Status</option><option value="ready">Ready</option><option value="blocked">Blocked</option><option value="done">Done</option><option value="archived">Archived</option></select>
<button class="btn secondary" onclick="bulkUpdateKanban()" data-i18n="kanban_bulk_action">Bulk action</button>
<button class="btn secondary" onclick="nudgeKanbanDispatcher()" data-i18n="kanban_nudge_dispatcher" title="Dry-run: shows what would be claimed without spawning workers">Preview</button>
<button class="btn primary" onclick="runKanbanDispatcher()" data-i18n="kanban_run_dispatcher" title="Claims Ready tasks and spawns worker subprocesses">Run dispatcher</button>
<button class="btn secondary kanban-nudge-dispatch-btn" onclick="nudgeKanbanDispatcher()" data-i18n="kanban_nudge_dispatcher" title="Dry-run: shows what would be claimed without spawning workers">Preview</button>
<button class="btn primary kanban-run-dispatch-btn" onclick="runKanbanDispatcher()" data-i18n="kanban_run_dispatcher" title="Claims Ready tasks and spawns worker subprocesses">Run dispatcher</button>
</div>
<div class="kanban-new-task-row">
<input id="kanbanNewTaskTitle" placeholder="New task" data-i18n-placeholder="kanban_new_task" onkeydown="if(event.key==='Enter')createKanbanTask()">
Expand Down Expand Up @@ -719,7 +719,7 @@ <h2 data-i18n="empty_title">What can I help with?</h2>
</div>
<div class="main-view-actions">
<button class="panel-head-btn has-tooltip has-tooltip--bottom" id="btnKanbanCreateBoard" onclick="openKanbanCreateBoard()" data-tooltip="New board" data-i18n-title="kanban_new_board" aria-label="New board"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><line x1="17.5" y1="14" x2="17.5" y2="21"/><line x1="14" y1="17.5" x2="21" y2="17.5"/></svg></button>
<button class="panel-head-btn has-tooltip has-tooltip--bottom" id="btnKanbanPreviewDispatcher" onclick="nudgeKanbanDispatcher()" data-tooltip="Preview dispatcher (dry-run)" data-i18n-title="kanban_nudge_dispatcher" aria-label="Preview dispatcher (dry-run)">▶</button>
<button class="panel-head-btn has-tooltip has-tooltip--bottom kanban-nudge-dispatch-btn" id="btnKanbanPreviewDispatcher" onclick="nudgeKanbanDispatcher()" data-tooltip="Preview dispatcher (dry-run)" data-i18n-title="kanban_nudge_dispatcher" aria-label="Preview dispatcher (dry-run)">▶</button>
<button class="panel-head-btn has-tooltip has-tooltip--bottom kanban-run-dispatch-btn" id="btnKanbanRunDispatcher" onclick="runKanbanDispatcher()" data-tooltip="Run dispatcher — claim Ready tasks" data-i18n-title="kanban_run_dispatcher" aria-label="Run dispatcher"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M13 2L3 14h7l-1 8 10-12h-7l1-8z"/></svg></button>
</div>
</div>
Expand Down Expand Up @@ -1279,6 +1279,7 @@ <h3 id="kanbanTaskModalTitle" data-i18n="kanban_new_task">New task</h3>
<div class="kanban-modal-row-inline">
<div class="kanban-modal-row">
<label for="kanbanTaskModalStatus" data-i18n="kanban_status">Status</label>
<span id="kanbanTaskModalStatusOriginalHint" class="kanban-status-original-hint" hidden></span>
<select id="kanbanTaskModalStatus">
<option value="triage" data-i18n="kanban_status_triage">Triage</option>
<option value="todo" data-i18n="kanban_status_todo">Todo</option>
Expand Down
Loading
Loading