fix(sources): offer Refresh only when it can run; drag handle never opens

- PlaylistRefreshActionService runs one refresh at a time, but
  canRefresh checked only the source, so the switcher, the command
  palette and the dashboard offered a Refresh that refresh() then
  ignored. canRefresh now applies the same global guard.
- In Custom order a click on a row's drag handle bubbled to the row and
  opened the source; the handle now stops it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5.5 committed 2026-10-14 02:00:23 +02:00
1 parent ea3dacb052
commit f48911a889
4 files changed
+31 -2

No files matched your search

@@ -369,6 +369,15 @@ describe('PlaylistRefreshActionService', () => {
service.refresh(playlist);
expect(service.isSourceBusy('busy')).toBe(true);
expect(service.isSourceBusy('other')).toBe(false);
// One refresh at a time: no other source is offered meanwhile.
const other = createPlaylistMeta({
_id: 'other',
serverUrl: undefined,
username: undefined,
password: undefined,
url: 'https://source.test/other',
});
expect(service.canRefresh(other)).toBe(false);
// The Sources page and the removal guard see it too.
expect(activity.isBusy('busy')).toBe(true);
resolve({ _id: 'busy', playlist: { items: [] } } as Playlist);
@@ -376,6 +385,7 @@ describe('PlaylistRefreshActionService', () => {
await Promise.resolve();
expect(service.isSourceBusy('busy')).toBe(false);
expect(activity.isBusy('busy')).toBe(false);
expect(service.canRefresh(other)).toBe(true);
});
it('passes trusted TLS hosts to URL-backed M3U refreshes', async () => {
@@ -64,7 +64,13 @@ export class PlaylistRefreshActionService {
readonly refreshPreparation = this.refreshPreparationState.asReadonly();
canRefresh(playlist: PlaylistMeta | null): boolean {
if (!playlist || this.activity.isBusy(playlist._id)) {
// The same guards as refresh(): one refresh runs at a time, so an
// offer made meanwhile would be a silent no-op.
if (
!playlist ||
this.isRefreshing() ||
this.activity.isBusy(playlist._id)
) {
return false;
}
@@ -13,7 +13,12 @@
(keydown)="onRowKeydown($event)"
>
@if (draggable()) {
<mat-icon cdkDragHandle class="source-row__handle" aria-hidden="true"
<!-- Grabbing the handle is not opening the source. -->
<mat-icon
cdkDragHandle
class="source-row__handle"
aria-hidden="true"
(click)="$event.stopPropagation()"
>drag_indicator</mat-icon
>
}
@@ -169,6 +169,14 @@ describe('SourceRowComponent', () => {
expect(opened).toHaveBeenCalledTimes(2);
});
it('does not open the source from a click on the drag handle', () => {
const element = render(M3U, { draggable: true });
const opened = jest.fn();
fixture.componentInstance.activated.subscribe(opened);
element.querySelector<HTMLElement>('.source-row__handle')?.click();
expect(opened).not.toHaveBeenCalled();
});
it('refreshes the focused row with R', () => {
const element = render(M3U, { canRefresh: true });
const refresh = jest.fn();