Skip to content

fix: keep kanban drag drop from opening detail - #2530

Merged
1 commit merged into
nesquena:masterfrom
franksong2702:franksong2702/fix-kanban-drop-click
May 18, 2026
Merged

1 commit merged into
nesquena:masterfrom
franksong2702:franksong2702/fix-kanban-drop-click

Conversation

@franksong2702

Copy link
Copy Markdown
Contributor

Thinking Path

  • Kanban cards are draggable and clickable, and both interactions currently share the same card surface.
  • Drag/drop status changes should update the board in place, not imply that the user explicitly selected the moved task.
  • The old path could open the detail pane in two ways: a trailing synthetic click after drag/drop, and the generic task-update helper opening detail after every PATCH.
  • This PR keeps explicit card click and keyboard activation unchanged while making drag/drop a board-only status update.

What Changed

  • Added a short click-suppression window around Kanban drag start/end/drop so browser-emitted trailing clicks do not open task detail.
  • Routed drag/drop PATCHes through updateKanbanTask(..., {openDetail: false}) so board refreshes do not programmatically select the moved task.
  • Left normal card click, keyboard activation, and quick-action status buttons on their existing detail-opening path.
  • Added a static regression test that pins both failure paths.
  • Added an Unreleased changelog entry.

Why It Matters

Dragging a Kanban card is a status update, not a task-selection action. Keeping drag/drop from opening the detail pane makes the board behave predictably and avoids surprising context switches while users reorganize work.

Closes #2529.

Verification

  • node --check static/panels.js
  • python3 -m pytest tests/test_kanban_ui_static.py::test_kanban_dragging_card_does_not_open_detail_on_drop_click tests/test_kanban_ui_static.py::test_kanban_ui_parity_polish_adds_card_metadata_quick_actions_and_swimlanes -q
  • python3 -m pytest tests/test_kanban_ui_static.py -q
  • git diff --check

Screenshots are not included because this is an interaction-state fix with no visual layout change; the regression is whether drag/drop opens the detail pane.

Risks / Follow-ups

  • The click suppressor is intentionally short-lived and scoped to card opening only; keyboard activation still opens task detail.
  • This does not change Kanban backend status semantics, dispatcher rules, board selection, or task detail rendering.

Model Used

OpenAI Codex (GPT-5). AI assisted with code review, implementation, and verification.

@nesquena-hermes

Copy link
Copy Markdown
Collaborator

Summary

Reading static/panels.js:1267-1320 on this branch against origin/master, and the test in tests/test_kanban_ui_static.py:695-749, the fix correctly identifies and patches both failure paths called out in #2529:

  1. The browser's trailing synthetic click after drag/drop. Suppressed via the time-windowed _kanbanSuppressCardClickUntil set on ondragstart/ondragend/ondrop, checked by the new openKanbanCard() interceptor on the card's onclick.
  2. The implicit detail-pane open inside updateKanbanTask() after every PATCH. Now opt-out via {openDetail: false}, only used by the drop path; the explicit click, the Enter/space activation, and quickKanbanCardAction() keep the old open-detail behavior.

The split is the right shape: drag/drop is a status mutation, explicit click is a selection.

Code reference

The card template change at static/panels.js:1384 (this branch):

return `<article class="kanban-card ${esc(stale)}" data-kanban-task-id="${esc(task.id)}" draggable="true" ondragstart="dragKanbanTask(event, '${esc(task.id)}')" ondragend="finishKanbanDrag(event)" onclick="return openKanbanCard(event, '${esc(task.id)}')" tabindex="0" role="button" onkeydown="if(event.key==='Enter'||event.key===' '){event.preventDefault();loadKanbanTask('${esc(task.id)}')}">

And the drop handler at static/panels.js:1314-1322:

async function dropKanbanTask(event, status){
  _kanbanSuppressNextCardClick();
  event.preventDefault();
  event.stopPropagation();
  clearKanbanDrop(event);
  const taskId = event.dataTransfer ? event.dataTransfer.getData('text/plain') : '';
  if (taskId && status) await updateKanbanTask(taskId, {status}, {openDetail: false});
  _kanbanSuppressNextCardClick();
}

The updateKanbanTask signature change at static/panels.js:2297-2308 correctly defaults openDetail to true, so existing call sites (quickKanbanCardAction, the quick-action lifecycle buttons at :2317, the task-modal save at :2265, the cron-from-task path) keep their current behavior unchanged.

Diagnosis / Recommendation

The approach is sound. Two small notes that may or may not matter for landing:

1. The 700ms window is generous but bounded. Browsers emit the trailing click essentially synchronously with dragend; 700ms covers slow event loops on low-end machines without making the card feel unresponsive after a normal click. Worth keeping as-is.

2. The double-call to _kanbanSuppressNextCardClick() inside dropKanbanTask is defensive belt-and-suspenders. The first call arms the window before the async PATCH; the second re-arms after the await in case loadKanban(true) re-rendered the DOM mid-flight and the click fires against the new card node. That's actually a real concern — updateKanbanTask() awaits loadKanban(true) which calls _kanbanRenderBoard(), regenerating _kanbanCard() HTML and replacing the article element the user dropped onto. The re-arm makes sense.

3. The card stays at role="button" and tabindex="0", and Enter/Space keep using loadKanbanTask directly without the click suppressor. That's intentional and correct per the PR description — keyboard activation should always open detail, since there's no drag/drop synthesis path on keyboard activation.

Verification

The static test at tests/test_kanban_ui_static.py:695-749 pins every contract surface the runtime depends on: the existence of the four new functions, the _kanbanSuppressNextCardClick calls inside dragKanbanTask / finishKanbanDrag / dropKanbanTask, the {openDetail: false} argument shape, the event.stopPropagation() inside drop, and that the card template no longer wires onclick="loadKanbanTask(...)" directly. That's the right level of coverage for a JS-only behavior change without browser fixture infrastructure.

One thing the static test doesn't cover but probably can't easily: confirming the time-window logic in openKanbanCard actually allows a click through after the window expires. The token check via assert "Date.now()" in open_body plus _kanbanSuppressCardClickUntil is structural only. That's fine — the alternative needs a JSDOM harness which isn't in this repo's test stack.

Looks good for merge.

@nesquena-hermes nesquena-hermes closed this pull request by merging all changes into nesquena:master in 4589dbe May 18, 2026
Charanis pushed a commit to Charanis/hermes-webui-beyond that referenced this pull request May 18, 2026
# Conflicts:
#	CHANGELOG.md
eleboucher pushed a commit to eleboucher/homelab that referenced this pull request May 18, 2026
… 0.51.90) (#556)

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [ghcr.io/nesquena/hermes-webui](https://github.com/nesquena/hermes-webui) | patch | `0.51.89` → `0.51.90` |

---

### Release Notes

<details>
<summary>nesquena/hermes-webui (ghcr.io/nesquena/hermes-webui)</summary>

### [`v0.51.90`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v05190--2026-05-18--Release-BN-stage-383--10-PR-full-sweep-batch--empty-gateway-messaging-history-fix--previous-messaging-sessions-setting--Kanban-board-switcher-layout--UIUX-demo-theme-controls--Slice-3c-queuegoal-RFC-gate--keyless-custom-endpoints--custom-provider-remote-model-catalog-parity--auto-compression-elapsed-timer--new-conversation-cold-start-guard--Kanban-drag-drop-detail-open-fix)

[Compare Source](nesquena/hermes-webui@v0.51.89...v0.51.90)

##### Fixed

- **PR [#&#8203;2286](nesquena/hermes-webui#2286 by [@&#8203;junjunjunbong](https://github.com/junjunjunbong) (refs [#&#8203;2275](nesquena/hermes-webui#2275)) — Narrow messaging stale-session filtering to active gateway sessions that are visible in the current sidebar candidate set. Older Discord/messaging history is now preserved when the gateway advertises a fresh zero-message session that hasn't yet entered the visible projection, instead of being hidden as stale. Adds a regression test for an empty active Discord gateway row preserving prior history.
- **PR [#&#8203;2459](nesquena/hermes-webui#2459 by [@&#8203;franksong2702](https://github.com/franksong2702) (closes [#&#8203;2458](nesquena/hermes-webui#2458)) — Fix the Kanban board switcher menu when a board's icon slot carries a long text label (e.g. `layout-kanban`). The icon column changed from a fixed `18px` slot to a bounded flex cell with `min-width:18px;max-width:7.5rem`, with overflow ellipsis on the icon itself so long labels render fully when space allows and truncate cleanly when not. Title and count columns keep stable spacing. Adds before/after screenshots and a CSS contract regression in `tests/test_kanban_ui_static.py`.
- **PR [#&#8203;2522](nesquena/hermes-webui#2522 by [@&#8203;Michaelyklam](https://github.com/Michaelyklam) (refs [#&#8203;2271](nesquena/hermes-webui#2271)) — Treat named custom OpenAI-compatible endpoints with a configured `base_url` as key-optional at WebUI agent startup. Local keyless servers (llama-server / vLLM-style LAN deployments) no longer fail early with a synthetic `CUSTOM:<slug>_API_KEY` env-var prompt before the request reaches the endpoint; instead the OpenAI-compatible client initialises with a harmless placeholder key and real configured keys are still preferred when present. Refactors the three near-identical custom-provider rebuild blocks (initial agent setup + two retry/healing paths) through the existing `resolve_custom_provider_connection` helper.
- **PR [#&#8203;2515](nesquena/hermes-webui#2515 by [@&#8203;Michaelyklam](https://github.com/Michaelyklam) (closes [#&#8203;2513](nesquena/hermes-webui#2513)) — Keep named custom-provider model pickers populated from each configured endpoint's live `/models` catalog even when `custom_providers[].model` is present. The singular `model` field now acts as a sticky/fallback entry appended *after* the remote catalog rather than collapsing the picker to just the configured model and hiding sibling named custom providers. Extracts reusable OpenAI-compatible `/models` parsing/fetching helpers and threads them through both the active-base-url and per-named-provider paths.
- **PR [#&#8203;2512](nesquena/hermes-webui#2512 by [@&#8203;dso2ng](https://github.com/dso2ng) (refs [#&#8203;2477](nesquena/hermes-webui#2477), Slice A) — Show an elapsed timer on the running automatic-compression card so long WebUI context-compression pauses no longer look frozen while the browser waits for the `compressed` event. Stamps `startedAt` on the `compressing` SSE event, ticks once per second, and switches to a `5+ min` cap label past the Slice A bound so the UI never frame-freezes at `05:00`. Browser-transient state only — no SSE contract change and no server-side resume reconstruction.
- **PR [#&#8203;2528](nesquena/hermes-webui#2528 by [@&#8203;Michaelyklam](https://github.com/Michaelyklam) (closes [#&#8203;2518](nesquena/hermes-webui#2518)) — Guard New Conversation creation while a previous `/api/session/new` request is still in flight, so cold model/provider catalog resolution gives immediate pending feedback and rapid repeated clicks reuse the same create request instead of enqueueing duplicate blank sessions. Coalesces concurrent `newSession()` calls behind a single in-flight promise, disables the sidebar button with `aria-busy="true"`, and shows a localized `Creating new conversation…` composer status.
- **PR [#&#8203;2530](nesquena/hermes-webui#2530 by [@&#8203;franksong2702](https://github.com/franksong2702) (refs [#&#8203;2529](nesquena/hermes-webui#2529)) — Keep Kanban drag/drop status updates from also opening the task detail pane. Two failure paths were both producing detail-pane opens after drag/drop: the browser's trailing synthetic click after `drop`, and the generic task-update helper opening detail on every PATCH. The fix adds a time-windowed `_kanbanSuppressCardClickUntil` set on `ondragstart`/`ondragend`/`ondrop` and routes drag/drop status changes through a board-only update path. Explicit card click and keyboard activation remain unchanged.

##### Added

- **PR [#&#8203;2294](nesquena/hermes-webui#2294 by [@&#8203;junjunjunbong](https://github.com/junjunjunbong) — Add a `show_previous_messaging_sessions` setting so users can opt back into seeing previous messaging sessions that were replaced by `session_reset` or auto-compression. The preference is wired through boot, settings persistence, and the sidebar projection. Also adds a separate "Hide from list" action for imported messaging/CLI sessions that hides individual rows from the sidebar without deleting source history.

##### Documentation

- **PR [#&#8203;2511](nesquena/hermes-webui#2511 by [@&#8203;franksong2702](https://github.com/franksong2702) (refs [#&#8203;2502](nesquena/hermes-webui#2502) / [#&#8203;2503](nesquena/hermes-webui#2503)) — Update the `docs/ui-ux/` demo appearance controls to initialize as `class="dark" data-skin="slate"` instead of the deprecated `data-theme`-only buttons and legacy theme names. Brings the demo pages in line with the live Theme + Skin contract referenced from the new `docs/CONTRACTS.md` so contributors following the contract-index path don't land on stale demos.
- **PR [#&#8203;2509](nesquena/hermes-webui#2509 by [@&#8203;Michaelyklam](https://github.com/Michaelyklam) (refs [#&#8203;1925](nesquena/hermes-webui#1925)) — Advance the runtime-adapter RFC after the Slice 3b approval/clarify seam shipped in v0.51.89. The RFC now marks Slice 3b as shipped and defines the next Slice 3c queue/continue + goal control gate: route those controls through `RuntimeAdapter.queue_message(...)` / `update_goal(...)` only after pinning stable response contracts, bounded unavailable-control behavior, replayable lifecycle/status evidence, ordering/idempotency expectations, and explicit non-goals for runner/sidecar ownership or a WebUI-owned queue/goal scheduler. Docs + adapter-seam regression test only — no runtime/control routing changes in this PR.

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these updates again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMDEuMSIsInVwZGF0ZWRJblZlciI6IjQzLjEwMS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL3BhdGNoIl19-->

Reviewed-on: https://git.erwanleboucher.dev/eleboucher/homelab/pulls/556
SysAdminDoc pushed a commit to SysAdminDoc/hermes-webui that referenced this pull request Jun 26, 2026
# Conflicts:
#	CHANGELOG.md
bernyforce pushed a commit to bernyforce/hermes-webui that referenced this pull request Jul 29, 2026
# Conflicts:
#	CHANGELOG.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Kanban drag/drop should not open task detail

2 participants