Skip to content

Commit ed0d6f0

Browse files
scheinrieseclaude
authored andcommitted
Address pr-review findings: accessibility, dead code, and consistency fixes
- Add ARIA live regions to feedback banners (role=alert/status) - Add role=dialog and aria-label to customize popover - Add aria-pressed to filter pill buttons - Remove disabled rows from tab order - Return focus to trigger on popover close - Add shortcut-badge-in to prefers-reduced-motion - Remove dead shortcut-conflicts-display component - Replace js/console.warn with lambdaisland.glogi - Merge duplicate cond branches in key handler - Add missing :shortcut-id to left sidebar Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent f6668ae commit ed0d6f0

4 files changed

Lines changed: 94 additions & 117 deletions

File tree

src/main/frontend/components/left_sidebar.cljs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,8 @@
123123
(when shortcut
124124
[:span.ml-1
125125
(ui/render-keyboard-shortcut
126-
(ui/keyboard-shortcut-from-config shortcut {:pick-first? true}))])
126+
(ui/keyboard-shortcut-from-config shortcut {:pick-first? true})
127+
:shortcut-id shortcut)])
127128
more]])
128129

129130
(rum/defc sidebar-graphs

src/main/frontend/components/shortcut.cljs

Lines changed: 84 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,7 @@
312312
[:button.shortcut-filter-pill
313313
{:key (name k)
314314
:class (when active? "shortcut-filter-pill--active")
315+
:aria-pressed (str active?)
315316
:on-click #(set-filter-key! (when-not (or (= k :All) (= filter-key k)) k))}
316317
[:span.shortcut-filter-pill-title title]
317318
[:span.shortcut-filter-pill-count (str " \u00B7 " cnt)]])]
@@ -363,7 +364,8 @@
363364
{:id popup-id
364365
:force-popover? true
365366
:align "start"
366-
:on-after-hide #(reset! *active-shortcut-id nil)
367+
:on-after-hide #(do (reset! *active-shortcut-id nil)
368+
(when anchor-el (.focus anchor-el)))
367369
:content-props
368370
{:class "p-0 w-auto"
369371
:collision-padding 12
@@ -376,36 +378,6 @@
376378
(.preventDefault e)
377379
false)))}}))))
378380

379-
(rum/defc shortcut-conflicts-display
380-
[_k conflicts-map]
381-
382-
[:div.cp__shortcut-conflicts-list-wrap
383-
(for [[g ks] conflicts-map]
384-
^{:key (str g)}
385-
[:section.relative
386-
[:h2 (ui/icon "alert-triangle" {:size 15})
387-
[:span (t :keymap/conflicts-for-label)]
388-
[:code (shortcut-utils/decorate-binding g)]]
389-
[:ul
390-
(for [v (vals ks)
391-
:let [k (first v)
392-
vs (second v)]
393-
[id' handler-id] vs
394-
:let [m (dh/shortcut-item id')]
395-
:when (not (nil? m))]
396-
[:li
397-
{:key (str id')}
398-
[:button.select-none.hover:underline
399-
{:on-click (fn [^js e] (open-customize-shortcut-dialog! e id'))
400-
:title (str handler-id)
401-
:aria-label (dh/get-shortcut-desc m)}
402-
[:code.inline-block.mr-1.text-xs
403-
(shortcut-utils/decorate-binding k)]
404-
[:span
405-
(dh/get-shortcut-desc m)
406-
(ui/icon "external-link" {:size 18})]
407-
[:code [:small (str id')]]]])]])])
408-
409381
(defn- execute-undo!
410382
"Restore previous bindings for all affected actions."
411383
[snapshot]
@@ -649,14 +621,8 @@
649621
(= state :conflict-cross)
650622
nil
651623

652-
;; Conflict-same / esc-hint + key => start new recording
653-
(#{:conflict-same :esc-hint} state)
654-
(when-let [kn (shortcut/keyname e)]
655-
(set-rec-state! :recording)
656-
(set-keystroke! (util/trim-safe kn)))
657-
658-
;; Idle / accepted / removed / reset / dismissing + key => start recording
659-
(#{:idle :accepted :removed :reset :dismissing} state)
624+
;; Any non-recording, non-conflict-cross state + key => start new recording
625+
(#{:conflict-same :esc-hint :idle :accepted :removed :reset :dismissing} state)
660626
(when-let [kn (shortcut/keyname e)]
661627
(set-rec-state! :recording)
662628
(set-keystroke! (util/trim-safe kn)))
@@ -675,7 +641,9 @@
675641
;; === V3 LAYOUT ===
676642
[:div.shortcut-popover
677643
{:tab-index -1
678-
:ref *ref-el}
644+
:ref *ref-el
645+
:role "dialog"
646+
:aria-label action-name}
679647

680648
;; TITLE
681649
[:div.shortcut-popover-title action-name]
@@ -717,79 +685,81 @@
717685
(when (#{:idle :recording :accepted :removed :reset} render-state)
718686
[:span.shortcut-input-placeholder (t :keymap/press-a-shortcut)])]
719687

720-
;; FEEDBACK BANNER (conditional)
721-
(let [undo-link
722-
(when undo-snapshot
723-
[:button.shortcut-feedback-action
724-
{:on-click (fn []
725-
(execute-undo! undo-snapshot)
726-
(when-let [own (some #(when (= (:action-id %) k) %) (:entries undo-snapshot))]
727-
(set-current-binding! (:previous-binding own)))
728-
(set-undo-snapshot! nil)
729-
(set-rec-state! :idle))}
730-
(t :keymap/undo)])]
731-
(case rec-state
732-
:conflict-cross
733-
[:div.shortcut-feedback.shortcut-feedback--error
734-
[:span (t :keymap/used-by)
735-
[:span.shortcut-feedback-name (conflict-action-names key-conflicts)]]
736-
(ui/tooltip
737-
(shui/button {:variant :destructive
738-
:size :xs
739-
:on-click override-fn!}
740-
(t :keymap/reassign))
741-
(t :keymap/reassign-tooltip))]
742-
743-
:conflict-same
744-
[:div.shortcut-feedback.shortcut-feedback--error
745-
[:span (t :keymap/already-bound)]]
746-
747-
:accepted
748-
(cond
749-
(:cross-context? accepted-info)
750-
[:div.shortcut-feedback.shortcut-feedback--warning
751-
[:span (t :keymap/also-used-for)
752-
[:span.shortcut-feedback-name
753-
(:cross-action-name accepted-info)]
754-
(when-let [ctx (:cross-context-label accepted-info)]
755-
(str (t :keymap/in-context) ctx))]]
756-
757-
(:from accepted-info)
758-
[:div.shortcut-feedback.shortcut-feedback--success
759-
[:span (t :keymap/reassigned-from)
760-
[:span.shortcut-feedback-name (:from accepted-info)]]
761-
undo-link]
762-
763-
:else
764-
[:div.shortcut-feedback.shortcut-feedback--success
765-
[:span (t :keymap/shortcut-added)]])
766-
767-
:removed
768-
[:div.shortcut-feedback.shortcut-feedback--muted
769-
[:span (t :keymap/shortcut-removed)]
770-
undo-link]
771-
772-
:reset
773-
[:div.shortcut-feedback.shortcut-feedback--muted
774-
[:span (t :keymap/reset-to-default)]
775-
undo-link]
776-
777-
:esc-hint
778-
[:div.shortcut-feedback.shortcut-feedback--muted
779-
[:span (t :keymap/esc-is-reserved)]]
780-
781-
:dismissing
782-
(let [prev (rum/deref *prev-rec-state)
783-
variant (case prev
784-
(:conflict-cross :conflict-same) "shortcut-feedback--error"
785-
:accepted (if (:cross-context? accepted-info)
786-
"shortcut-feedback--warning"
787-
"shortcut-feedback--success")
788-
"shortcut-feedback--muted")]
789-
[:div {:class (str "shortcut-feedback " variant " is-dismissing")
790-
:on-animation-end #(set-rec-state! :idle)}])
791-
792-
nil))
688+
;; FEEDBACK BANNER (conditional) — wrapped in live region for screen readers
689+
[:div {:role (if (#{:conflict-cross :conflict-same} rec-state) "alert" "status")
690+
:aria-live (if (#{:conflict-cross :conflict-same} rec-state) "assertive" "polite")}
691+
(let [undo-link
692+
(when undo-snapshot
693+
[:button.shortcut-feedback-action
694+
{:on-click (fn []
695+
(execute-undo! undo-snapshot)
696+
(when-let [own (some #(when (= (:action-id %) k) %) (:entries undo-snapshot))]
697+
(set-current-binding! (:previous-binding own)))
698+
(set-undo-snapshot! nil)
699+
(set-rec-state! :idle))}
700+
(t :keymap/undo)])]
701+
(case rec-state
702+
:conflict-cross
703+
[:div.shortcut-feedback.shortcut-feedback--error
704+
[:span (t :keymap/used-by)
705+
[:span.shortcut-feedback-name (conflict-action-names key-conflicts)]]
706+
(ui/tooltip
707+
(shui/button {:variant :destructive
708+
:size :xs
709+
:on-click override-fn!}
710+
(t :keymap/reassign))
711+
(t :keymap/reassign-tooltip))]
712+
713+
:conflict-same
714+
[:div.shortcut-feedback.shortcut-feedback--error
715+
[:span (t :keymap/already-bound)]]
716+
717+
:accepted
718+
(cond
719+
(:cross-context? accepted-info)
720+
[:div.shortcut-feedback.shortcut-feedback--warning
721+
[:span (t :keymap/also-used-for)
722+
[:span.shortcut-feedback-name
723+
(:cross-action-name accepted-info)]
724+
(when-let [ctx (:cross-context-label accepted-info)]
725+
(str (t :keymap/in-context) ctx))]]
726+
727+
(:from accepted-info)
728+
[:div.shortcut-feedback.shortcut-feedback--success
729+
[:span (t :keymap/reassigned-from)
730+
[:span.shortcut-feedback-name (:from accepted-info)]]
731+
undo-link]
732+
733+
:else
734+
[:div.shortcut-feedback.shortcut-feedback--success
735+
[:span (t :keymap/shortcut-added)]])
736+
737+
:removed
738+
[:div.shortcut-feedback.shortcut-feedback--muted
739+
[:span (t :keymap/shortcut-removed)]
740+
undo-link]
741+
742+
:reset
743+
[:div.shortcut-feedback.shortcut-feedback--muted
744+
[:span (t :keymap/reset-to-default)]
745+
undo-link]
746+
747+
:esc-hint
748+
[:div.shortcut-feedback.shortcut-feedback--muted
749+
[:span (t :keymap/esc-is-reserved)]]
750+
751+
:dismissing
752+
(let [prev (rum/deref *prev-rec-state)
753+
variant (case prev
754+
(:conflict-cross :conflict-same) "shortcut-feedback--error"
755+
:accepted (if (:cross-context? accepted-info)
756+
"shortcut-feedback--warning"
757+
"shortcut-feedback--success")
758+
"shortcut-feedback--muted")]
759+
[:div {:class (str "shortcut-feedback " variant " is-dismissing")
760+
:on-animation-end #(set-rec-state! :idle)}])
761+
762+
nil))]
793763

794764
;; SEPARATOR + TOOLBAR
795765
(shui/separator)
@@ -1049,7 +1019,7 @@
10491019
[:li.shortcut-row.flex.items-start.justify-between.text-sm
10501020
{:key (str id)
10511021
:class (when (= active-id id) "active")
1052-
:tab-index (when id 0)
1022+
:tab-index (when (and id (not disabled?)) 0)
10531023
:role (when id "button")
10541024
:aria-disabled (when disabled? "true")
10551025
:on-click row-action

src/main/frontend/components/shortcut.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,11 @@ button.shortcut-feedback-action {
493493
}
494494

495495
@media (prefers-reduced-motion: reduce) {
496+
@keyframes shortcut-badge-in {
497+
from { opacity: 0; }
498+
to { opacity: 1; }
499+
}
500+
496501
@keyframes shortcut-fade-in {
497502
from { opacity: 0; }
498503
to { opacity: 1; }

src/main/frontend/modules/shortcut/utils.cljs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
(ns frontend.modules.shortcut.utils
22
(:require [clojure.string :as string]
3-
[frontend.util :as util])
3+
[frontend.util :as util]
4+
[lambdaisland.glogi :as log])
45
(:import [goog.ui KeyboardShortcutHandler]))
56

67
(def ^:private modifier-order
@@ -39,7 +40,7 @@
3940
(try
4041
(KeyboardShortcutHandler/parseStringShortcut binding)
4142
(catch js/Error e
42-
(js/console.warn "[shortcuts] parse key error: " e) binding)))
43+
(log/warn :shortcut/parse-key-error {:error e :binding binding}) binding)))
4344

4445
(defn mod-key [binding]
4546
(string/replace binding #"(?i)mod"

0 commit comments

Comments
 (0)