Skip to content

feat: session ⋯ action dropdown replaces per-row buttons (extracted from #242) - #252

Closed
nesquena-hermes wants to merge 1 commit into
masterfrom
feat/session-action-menu
Closed

feat: session ⋯ action dropdown replaces per-row buttons (extracted from #242)#252
nesquena-hermes wants to merge 1 commit into
masterfrom
feat/session-action-menu

Conversation

@nesquena-hermes

Copy link
Copy Markdown
Collaborator

Summary

Replaces the five per-row hover action buttons (pin, move, archive, duplicate, trash) in the session list with a single trigger button that opens a positioned dropdown menu. Extracted from @aronprins's PR #242 as a self-contained piece.

What changed

static/sessions.js

  • New ICONS const at top with inline SVG for pin, unpin, folder, archive, unarchive, dup, trash, more (⋯)
  • closeSessionActionMenu() — removes the menu and cleans up anchor state
  • _positionSessionActionMenu(anchorEl) — positions the menu in the viewport, flips above anchor if not enough space below
  • _buildSessionAction(label, meta, icon, onSelect, extraClass) — builds a single menu option with icon + label + subtitle
  • _openSessionActionMenu(session, anchorEl) — builds and opens the full dropdown with 5 actions
  • Close handlers: click-outside, scroll (capture), Escape keydown, resize repositions
  • renderSessionListFromCache updated: per-row buttons replaced with single menuBtn

static/style.css

  • Removed: .session-actions gradient overlay + per-row button styles
  • Added: .session-actions-trigger (26×26px, hover background), .session-action-menu (position:fixed, z-index:999, flipped shadow), .session-action-opt with icon/meta layout, danger state

tests/test_sprint16.py

  • Updated test_sessions_js_active_skips_project_bordertest_sessions_js_uses_action_menu_not_per_row_buttons — asserts session-actions-trigger, _openSessionActionMenu, closeSessionActionMenu exist and old act-pin/act-archive are gone

Tests

624 passed, 0 failed, 0 skipped (same count as master — this PR updates an existing test, not adds new ones)

Browser tested: menu opens with correct 5 items (Pin, Move to project, Archive, Duplicate, Delete), icons and subtitles render, Escape closes cleanly, zero JS console errors.

Relationship to PR #242

Extracted from @aronprins's feat/ui-improvements-round1. The session action menu is self-contained — it doesn't depend on the composer footer restructure, control center, or activity bar removal in the larger PR. Merging this now improves the session list UX independently.

…rom #242)

Replaces the five per-row hover action buttons (pin, move, archive, duplicate,
trash) with a single ⋯ trigger button that opens a positioned dropdown menu.

Changes:
  - static/sessions.js: adds _openSessionActionMenu(), closeSessionActionMenu(),
    _positionSessionActionMenu(), _buildSessionAction() with full keyboard
    (Escape), click-outside, scroll, and resize handling. Menu uses
    position:fixed to avoid sidebar clipping.
  - static/style.css: replaces .session-actions overlay CSS with new
    .session-actions-trigger and .session-action-menu rules.
  - tests/test_sprint16.py: updates test_sessions_js_active_skips_project_border
    → test_sessions_js_uses_action_menu_not_per_row_buttons, asserting the new
    ⋯ trigger and menu functions exist and old per-row buttons are gone.

The dropdown menu includes:
  - Pin / Unpin conversation
  - Move to project
  - Archive / Unarchive conversation
  - Duplicate conversation
  - Delete conversation (danger style)

Each action item has a label and a descriptive subtitle. The menu closes on
Escape, outside click, scroll, and resize-repositions to stay in viewport.

Browser tested: menu opens, correct 5 items with icons and subtitles, no JS
errors, Escape closes cleanly.

Extracted from PR #242 (feat/ui-improvements-round1) by @aronprins.
@nesquena

Copy link
Copy Markdown
Owner

Full Review: PR #252 — Session action dropdown

Security Audit

Clean. All user data (session.title, project.name) is passed through esc() in _buildSessionAction(). The dropdown is a DOM-constructed element (not innerHTML from raw data). Icons come from the static ICONS const. No XSS vectors.

Code Review

Dropdown system (sessions.js):
Well-implemented with proper lifecycle:

  • closeSessionActionMenu() — cleans up DOM, resets anchor state, removes menu-open class
  • _positionSessionActionMenu() — viewport-aware positioning with flip-above logic when no room below. Correct boundary clamping (8px margin from edges).
  • _buildSessionAction() — clean builder with icon, label, subtitle, and danger class support. Uses esc() on all text content.
  • _openSessionActionMenu() — toggle behavior (re-click closes), 5 menu items (pin/move/archive/dup/delete)
  • Close handlers: click-outside, scroll (capture), Escape, resize repositions — all correct

CSS changes:

  • Old .session-actions gradient overlay (5 inline buttons) replaced with centered trigger
  • New .session-action-menu uses position:fixed (correct for viewport-positioned dropdown)
  • Danger state on delete option — red text/icon on hover
  • Light theme: removes border-left-color from active session (project color now shown via dot)

Test update: test_sprint16.py correctly updated — asserts new patterns exist and old act-pin/act-archive patterns are gone.

One note

The _positionSessionActionMenu function sets _sessionActionMenu.style.top='8px' as a temporary value, then immediately recalculates. This is fine (the first assignment is overwritten before paint), but could be clearer with just the final assignment.

Tests

573 passed, 0 failed (1 pre-existing failure from unmerged PR #243's test).

Verdict

Approved. Clean UX improvement, proper event lifecycle, accessible. Ready to merge.

@nesquena-hermes

Copy link
Copy Markdown
Collaborator Author

Agent review — APPROVED ✅ (merged to stage)

ICONS.more confirmed present (was missing in a prior extraction attempt — fixed). All menu functions exist: closeSessionActionMenu(), _positionSessionActionMenu(), _buildSessionAction(), _openSessionActionMenu(). Full close handling: click-outside, scroll-to-close (capture=true), Escape, resize-reposition. Old per-row button classes (act-pin, act-archive, act-trash, act-move, act-dup) are gone from sessions.js.

test_sprint16.py updated: test_sessions_js_uses_action_menu_not_per_row_buttons asserts the new trigger and menu functions exist and old button classes are absent.

Browser tested: ⋯ menu opens with correct 5 items and icons, Escape closes, zero JS errors.

Tests: 624 passed on review branch. Stage total: 645 passed, 0 failed.
Merged to stage branch.

nesquena-hermes pushed a commit that referenced this pull request Apr 11, 2026
Replaces the 5 per-row hover action buttons (pin/move/archive/duplicate/trash)
with a single ⋯ trigger that opens a positioned dropdown menu. Menu has full
keyboard (Escape), click-outside, scroll, and resize-reposition handling.
Position:fixed prevents sidebar clipping.

5 actions: Pin/Unpin, Move to project, Archive/Unarchive, Duplicate, Delete
(danger style). Each with icon and descriptive subtitle.

Updated test_sprint16.py: test_sessions_js_uses_action_menu_not_per_row_buttons
asserts the new trigger and menu functions exist, old per-row classes are gone.

Extracted from PR #242.
nesquena-hermes added a commit that referenced this pull request Apr 11, 2026
* fix: custom provider with slash model name no longer rerouted to OpenRouter (#255)

When base_url is configured in config.yaml, resolve_model_provider() now
trusts the configured provider/base_url entirely and skips the slash-based
OpenRouter heuristic. Fixes google/gemma-4-26b-a4b with provider:custom
being silently routed to OpenRouter, resulting in 401 errors.

Fixes #230

* test: mobile layout regression suite — 14 tests for every QA run (#254)

Adds tests/test_mobile_layout.py with 14 static regression tests that run
on every QA pass to catch mobile layout breakage before it reaches prod.
Covers: breakpoints at 900px/640px, right panel slide-over CSS, mobile
overlay, bottom nav, files button, profile dropdown z-index, chip overflow,
workspace close, 100dvh, 44px touch targets, 16px font-size on textarea.

* feat: /skills slash command lists and filters available Hermes skills (#257)

Adds /skills [query] command to commands.js. Fetches from /api/skills,
groups by category (alphabetically sorted), displays as a formatted
assistant message. Optional query filters by name, description, or category.
i18n keys added for en, de, zh, zh-Hant. 1 regression test added.

Fixes #248

* feat: shared app dialogs replace native confirm()/prompt() calls (#251)

Adds showConfirmDialog() and showPromptDialog() helpers to ui.js, backed
by a themed #appDialogOverlay. Replaces all 11 native browser confirm/prompt
call sites across panels.js, sessions.js, ui.js, workspace.js.

Supports: danger mode, keyboard focus trap (Tab/Escape/Enter), focus restore,
ARIA roles, mobile-responsive stacked buttons at 640px. i18n for en/de/zh/zh-Hant.
5 new tests in test_sprint33.py verify markup, CSS, helpers, and absence of
native dialog calls.

Extracted from PR #242.

* fix: Android Chrome mobile — workspace panel close + profile dropdown (#256)

Fix #247: toggleMobileFiles() now shows/hides the mobile overlay when
toggling the right workspace panel. New closeMobileFiles() helper closes
the panel with correct overlay state tracking. Overlay onclick calls both
closeMobileSidebar() and closeMobileFiles(). Mobile-only close button (x)
added to workspace panel header.

Fix #246: profile dropdown uses position:fixed;top:56px;right:8px at
max-width:900px, escaping the overflow-x:auto stacking context that was
clipping it on Android Chrome.

Fix applied during review: closeMobileSidebar() now checks if the right
panel is still open before hiding the overlay, preventing the overlay from
disappearing when only the sidebar is closed.

Fixes #247 Fixes #246

* feat: session ⋯ action dropdown replaces per-row buttons (#252)

Replaces the 5 per-row hover action buttons (pin/move/archive/duplicate/trash)
with a single ⋯ trigger that opens a positioned dropdown menu. Menu has full
keyboard (Escape), click-outside, scroll, and resize-reposition handling.
Position:fixed prevents sidebar clipping.

5 actions: Pin/Unpin, Move to project, Archive/Unarchive, Duplicate, Delete
(danger style). Each with icon and descriptive subtitle.

Updated test_sprint16.py: test_sessions_js_uses_action_menu_not_per_row_buttons
asserts the new trigger and menu functions exist, old per-row classes are gone.

Extracted from PR #242.

* docs: v0.47.0 release notes, bump version, update test counts (645)

---------

Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
@nesquena-hermes

Copy link
Copy Markdown
Collaborator Author

Merged to master via stage branch in PR #258 (v0.47.0).

@nesquena-hermes
nesquena-hermes deleted the feat/session-action-menu branch April 12, 2026 03:14
JKJameson pushed a commit to JKJameson/hermes-webui that referenced this pull request Apr 25, 2026
* fix: custom provider with slash model name no longer rerouted to OpenRouter (nesquena#255)

When base_url is configured in config.yaml, resolve_model_provider() now
trusts the configured provider/base_url entirely and skips the slash-based
OpenRouter heuristic. Fixes google/gemma-4-26b-a4b with provider:custom
being silently routed to OpenRouter, resulting in 401 errors.

Fixes nesquena#230

* test: mobile layout regression suite — 14 tests for every QA run (nesquena#254)

Adds tests/test_mobile_layout.py with 14 static regression tests that run
on every QA pass to catch mobile layout breakage before it reaches prod.
Covers: breakpoints at 900px/640px, right panel slide-over CSS, mobile
overlay, bottom nav, files button, profile dropdown z-index, chip overflow,
workspace close, 100dvh, 44px touch targets, 16px font-size on textarea.

* feat: /skills slash command lists and filters available Hermes skills (nesquena#257)

Adds /skills [query] command to commands.js. Fetches from /api/skills,
groups by category (alphabetically sorted), displays as a formatted
assistant message. Optional query filters by name, description, or category.
i18n keys added for en, de, zh, zh-Hant. 1 regression test added.

Fixes nesquena#248

* feat: shared app dialogs replace native confirm()/prompt() calls (nesquena#251)

Adds showConfirmDialog() and showPromptDialog() helpers to ui.js, backed
by a themed #appDialogOverlay. Replaces all 11 native browser confirm/prompt
call sites across panels.js, sessions.js, ui.js, workspace.js.

Supports: danger mode, keyboard focus trap (Tab/Escape/Enter), focus restore,
ARIA roles, mobile-responsive stacked buttons at 640px. i18n for en/de/zh/zh-Hant.
5 new tests in test_sprint33.py verify markup, CSS, helpers, and absence of
native dialog calls.

Extracted from PR nesquena#242.

* fix: Android Chrome mobile — workspace panel close + profile dropdown (nesquena#256)

Fix nesquena#247: toggleMobileFiles() now shows/hides the mobile overlay when
toggling the right workspace panel. New closeMobileFiles() helper closes
the panel with correct overlay state tracking. Overlay onclick calls both
closeMobileSidebar() and closeMobileFiles(). Mobile-only close button (x)
added to workspace panel header.

Fix nesquena#246: profile dropdown uses position:fixed;top:56px;right:8px at
max-width:900px, escaping the overflow-x:auto stacking context that was
clipping it on Android Chrome.

Fix applied during review: closeMobileSidebar() now checks if the right
panel is still open before hiding the overlay, preventing the overlay from
disappearing when only the sidebar is closed.

Fixes nesquena#247 Fixes nesquena#246

* feat: session ⋯ action dropdown replaces per-row buttons (nesquena#252)

Replaces the 5 per-row hover action buttons (pin/move/archive/duplicate/trash)
with a single ⋯ trigger that opens a positioned dropdown menu. Menu has full
keyboard (Escape), click-outside, scroll, and resize-reposition handling.
Position:fixed prevents sidebar clipping.

5 actions: Pin/Unpin, Move to project, Archive/Unarchive, Duplicate, Delete
(danger style). Each with icon and descriptive subtitle.

Updated test_sprint16.py: test_sessions_js_uses_action_menu_not_per_row_buttons
asserts the new trigger and menu functions exist, old per-row classes are gone.

Extracted from PR nesquena#242.

* docs: v0.47.0 release notes, bump version, update test counts (645)

---------

Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
SysAdminDoc pushed a commit to SysAdminDoc/hermes-webui that referenced this pull request Jun 26, 2026
* fix: custom provider with slash model name no longer rerouted to OpenRouter (nesquena#255)

When base_url is configured in config.yaml, resolve_model_provider() now
trusts the configured provider/base_url entirely and skips the slash-based
OpenRouter heuristic. Fixes google/gemma-4-26b-a4b with provider:custom
being silently routed to OpenRouter, resulting in 401 errors.

Fixes nesquena#230

* test: mobile layout regression suite — 14 tests for every QA run (nesquena#254)

Adds tests/test_mobile_layout.py with 14 static regression tests that run
on every QA pass to catch mobile layout breakage before it reaches prod.
Covers: breakpoints at 900px/640px, right panel slide-over CSS, mobile
overlay, bottom nav, files button, profile dropdown z-index, chip overflow,
workspace close, 100dvh, 44px touch targets, 16px font-size on textarea.

* feat: /skills slash command lists and filters available Hermes skills (nesquena#257)

Adds /skills [query] command to commands.js. Fetches from /api/skills,
groups by category (alphabetically sorted), displays as a formatted
assistant message. Optional query filters by name, description, or category.
i18n keys added for en, de, zh, zh-Hant. 1 regression test added.

Fixes nesquena#248

* feat: shared app dialogs replace native confirm()/prompt() calls (nesquena#251)

Adds showConfirmDialog() and showPromptDialog() helpers to ui.js, backed
by a themed #appDialogOverlay. Replaces all 11 native browser confirm/prompt
call sites across panels.js, sessions.js, ui.js, workspace.js.

Supports: danger mode, keyboard focus trap (Tab/Escape/Enter), focus restore,
ARIA roles, mobile-responsive stacked buttons at 640px. i18n for en/de/zh/zh-Hant.
5 new tests in test_sprint33.py verify markup, CSS, helpers, and absence of
native dialog calls.

Extracted from PR nesquena#242.

* fix: Android Chrome mobile — workspace panel close + profile dropdown (nesquena#256)

Fix nesquena#247: toggleMobileFiles() now shows/hides the mobile overlay when
toggling the right workspace panel. New closeMobileFiles() helper closes
the panel with correct overlay state tracking. Overlay onclick calls both
closeMobileSidebar() and closeMobileFiles(). Mobile-only close button (x)
added to workspace panel header.

Fix nesquena#246: profile dropdown uses position:fixed;top:56px;right:8px at
max-width:900px, escaping the overflow-x:auto stacking context that was
clipping it on Android Chrome.

Fix applied during review: closeMobileSidebar() now checks if the right
panel is still open before hiding the overlay, preventing the overlay from
disappearing when only the sidebar is closed.

Fixes nesquena#247 Fixes nesquena#246

* feat: session ⋯ action dropdown replaces per-row buttons (nesquena#252)

Replaces the 5 per-row hover action buttons (pin/move/archive/duplicate/trash)
with a single ⋯ trigger that opens a positioned dropdown menu. Menu has full
keyboard (Escape), click-outside, scroll, and resize-reposition handling.
Position:fixed prevents sidebar clipping.

5 actions: Pin/Unpin, Move to project, Archive/Unarchive, Duplicate, Delete
(danger style). Each with icon and descriptive subtitle.

Updated test_sprint16.py: test_sessions_js_uses_action_menu_not_per_row_buttons
asserts the new trigger and menu functions exist, old per-row classes are gone.

Extracted from PR nesquena#242.

* docs: v0.47.0 release notes, bump version, update test counts (645)

---------

Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
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.

2 participants