9.9 KiB
9.9 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 16-add-ctrl-a-hotkey-to-multi-select-views- | 01 | execute | 1 |
|
true |
|
|
Purpose: Currently app.selectAll calls document.execCommand('selectAll') which is the browser's text selection — useless for the app's track/queue selection. This needs to trigger the SelectionController's select-all in whichever panel is focused.
Output: Ctrl+A selects all items in the active multi-select view using the existing shortcut infrastructure and SelectionController.
<execution_context> @/home/caleb/.config/Claude/get-shit-done/workflows/execute-plan.md @/home/caleb/.config/Claude/get-shit-done/templates/summary.md </execution_context>
@frontend/src/utils/selection-controller.ts @frontend/src/services/keyboard-shortcut-service.ts @frontend/src/components/track-list/track-list.ts @frontend/src/components/queue-panel/queue-panel.ts @frontend/src/components/playlist-view/playlist-view.ts @backend/shortcuts/config.goFrom frontend/src/utils/selection-controller.ts:
export interface SelectionHost extends ReactiveControllerHost {
getItemKey(index: number): string | undefined;
getItemCount(): number;
onSelectionChanged?(): void;
}
export class SelectionController implements ReactiveController {
get selectedItems(): ReadonlySet<string>;
get hasSelection(): boolean;
get selectionCount(): number;
isSelected(key: string): boolean;
handleItemClick(e: MouseEvent, key: string, index: number): void;
handleContextMenu(key: string): void;
clear(): void;
getSelectedKeysOrdered(): string[];
getSelectedIndices(): number[];
}
From frontend/src/services/keyboard-shortcut-service.ts (dispatch function):
// Existing pattern for panel-specific shortcuts:
case 'tracklist.play':
document.dispatchEvent(new CustomEvent('shortcut:tracklist-play'));
break;
case 'tracklist.delete':
document.dispatchEvent(new CustomEvent('shortcut:tracklist-delete'));
break;
From backend/shortcuts/config.go (existing binding):
"app.selectAll": "Ctrl+A", // Already bound — just need to change the dispatch action
```typescript
/** Select all items. */
selectAll(): void {
const count = this.host.getItemCount();
const next = new Set<string>();
for (let i = 0; i < count; i++) {
const key = this.host.getItemKey(i);
if (key !== undefined) next.add(key);
}
if (next.size === this._selectedItems.size) return;
this._selectedItems = next;
this.lastSelectedIndex = count > 0 ? count - 1 : null;
this.host.requestUpdate();
this.host.onSelectionChanged?.();
}
```
**keyboard-shortcut-service.ts** (`frontend/src/services/keyboard-shortcut-service.ts`):
In the `dispatch()` function, change the `app.selectAll` case from:
```typescript
case 'app.selectAll':
document.execCommand('selectAll');
break;
```
To:
```typescript
case 'app.selectAll':
document.dispatchEvent(
new CustomEvent('shortcut:select-all'),
);
break;
```
This follows the exact same pattern used by `tracklist.play` and `tracklist.delete`.
**For each component (track-list, queue-panel, playlist-view):**
1. Add a bound handler method that calls `this.selection.selectAll()`:
```typescript
private handleSelectAll = (): void => {
this.selection.selectAll();
};
```
2. In `connectedCallback()` (create one if it doesn't exist, calling `super.connectedCallback()`):
```typescript
document.addEventListener('shortcut:select-all', this.handleSelectAll);
```
3. In `disconnectedCallback()` (create one if it doesn't exist, calling `super.disconnectedCallback()`):
```typescript
document.removeEventListener('shortcut:select-all', this.handleSelectAll);
```
**IMPORTANT consideration for playlist-view**: The playlist-view has a dual-mode UI (playlist list vs expanded playlist tracks). `getItemCount()` and `getItemKey()` already handle this — when a playlist is expanded, they return the tracks for that playlist; when no playlist is expanded, they return playlist entries. So `selectAll()` will naturally select all items in whatever mode is active.
**IMPORTANT consideration for multiple listeners**: All three components may be connected simultaneously (track-list in the main panel, queue-panel as a sidebar, playlist-view as a panel). This is correct behavior — `selectAll()` on a component that has 0 items (e.g., queue-panel when it's closed or has no items) is harmless since `getItemCount()` returns 0 and the early-return guard in `selectAll()` triggers (0 === 0). The user's focused panel receives the visual feedback. This matches how `tracklist.play` and `tracklist.delete` already broadcast to all listeners.
**Check if connectedCallback/disconnectedCallback already exist** in each component before adding. If they exist, add the addEventListener/removeEventListener lines to the existing methods. If they don't exist, create them.
<success_criteria>
- Ctrl+A selects all items in track-list, queue-panel, and playlist-view
- Selection count badge/indicator updates to show total count
- Text input focus is not affected (shortcut service's text-input scope suppression handles this)
- No TypeScript compilation errors
- Linting passes </success_criteria>