fix(workspace): open a source row from the keyboard (#1885)

A Sources row opened only on a pointer click: the row div was not
focusable and had no key handler, so a keyboard user could Tab to its
actions but never open the playlist.

Following app-content-card, the row's title and meta block becomes the
keyboard activation element: role="button", tabindex="0", named by the
source title, aria-current on the active source, aria-disabled while
busy. Enter and Space open the source (Space prevents the page scroll).
The drag handle, health indicator and actions stay its siblings, so no
control is nested inside a role="button" and their keys never bubble
into it. A click anywhere on the row still opens it via the row.

The row draws the ring with the shared focus-ring mixin, inset, because
the list's scroller would cut a ring drawn outside the full-width row.
playlist-shared-ui now declares its ui-styles stylesheet dependency.

Tests: unit spec for Enter/Space, bubbling from an action button, busy
and selected states; Electron E2E tabs to a source row, checks the ring
and opens it with Enter (fails on the previous component).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5.5 authored and GitHub committed 2026-10-11 08:15:04 +02:00
1 parent 4281194cb4
commit ea7efdbd9b
8 files changed
+349 -1

No files matched your search

@@ -1252,6 +1252,22 @@ A visual change is not done until:
4. Scroll behavior is correct.
5. The result was checked in the running app for layout-sensitive work.
### Source row keyboard access
A Sources row (`app-playlist-item`) opens on a click anywhere on it, and it
also holds a drag handle, a health indicator and actions. Like
`app-content-card`, it opens from the keyboard through a dedicated element
that is a sibling of those controls: the title and meta block
`.playlist-content` (`role="button"`, `tabindex="0"`, named by the source
title, `aria-current` on the active source, `aria-disabled` while busy).
Enter and Space open the source (Space prevents the page scroll), and Tab
reaches each row before its actions. Never put `role="button"` or key
handlers on the whole row: the actions would sit inside a button, and their
Enter and Space would bubble into it. The row draws the ring for that
element, inset (`$offset: -2px`), because the list's scroller would cut a
ring drawn outside the full-width row. `sources-keyboard.e2e.ts` (Electron)
tabs to a row, checks its ring and opens the source with Enter.
### Network source indicators (Electron)
The switcher and source rows use `SourceHealthIndicatorComponent`: green means