fix(sources): keep ⌘P out of open dialogs; redact refresh errors

Local review round 5 (Greptile; Codex was clean):

- Ctrl/Cmd+P opened the playlist switcher over an open dialog (an
  editor, a confirmation), taking the focus out of it and handing it back
  to the toolbar behind the dialog on close. The shortcut now leaves the
  key alone while MatDialog has open dialogs.
- SourceListRefreshService logged a failed refresh's raw error, which can
  carry the playlist URL and its credentials. It now logs through the
  redacting portal logger (createLogger).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5.5 committed 2026-10-13 18:47:15 +02:00
1 parent 80a9cd946a
commit 9a0fc9b924
3 files changed
+22 -2

No files matched your search

@@ -3,6 +3,7 @@ import { MatSnackBar } from '@angular/material/snack-bar';
import { Store } from '@ngrx/store';
import { TranslateService } from '@ngx-translate/core';
import { PlaylistActions } from '@iptvnator/m3u-state';
import { createLogger } from '@iptvnator/portal/shared/util';
import {
DatabaseService,
DataService,
@@ -60,6 +61,7 @@ export class SourceListRefreshService {
private readonly store = inject(Store);
private readonly translate = inject(TranslateService);
private readonly xtreamRefreshFlow = inject(XtreamRefreshFlowService);
private readonly logger = createLogger('SourceListRefresh');
private readonly refreshingIds = signal<ReadonlySet<string>>(new Set());
private readonly operations = signal<ReadonlyMap<string, BusyOperation>>(
@@ -209,7 +211,8 @@ export class SourceListRefreshService {
);
} catch (error) {
if (!isDbAbortError(error)) {
console.error('Error refreshing playlist:', error);
// Redacted: the error can carry the playlist URL.
this.logger.error('Error refreshing playlist:', error);
this.snackBar.open(
this.refreshErrorMessage(error, item),
this.translate.instant('CLOSE'),
@@ -330,6 +330,7 @@ describe('WorkspaceShellFacade', () => {
{
provide: MatDialog,
useValue: {
openDialogs: [],
open: jest.fn().mockReturnValue({
afterClosed: () => of(undefined),
}),
@@ -1150,6 +1151,17 @@ describe('WorkspaceShellFacade', () => {
expect(press().defaultPrevented).toBe(true);
expect(opened).toHaveBeenCalledTimes(1);
// An open dialog keeps the key and the focus.
const dialogs = (
TestBed.inject(MatDialog) as unknown as {
openDialogs: unknown[];
}
).openDialogs;
dialogs.push({});
expect(press().defaultPrevented).toBe(false);
expect(opened).toHaveBeenCalledTimes(1);
dialogs.length = 0;
unregister();
subscription.unsubscribe();
});
@@ -1,4 +1,5 @@
import { computed, DestroyRef, inject, Injectable } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import { WorkspaceBackNavigationService } from '@iptvnator/portal/shared/data-access';
@@ -58,6 +59,7 @@ export class WorkspaceShellFacade {
}
);
private readonly playlistSwitcher = inject(PlaylistSwitcherOpenService);
private readonly dialog = inject(MatDialog);
private readonly onDocumentKeydown = (event: KeyboardEvent): void => {
if (!(event.ctrlKey || event.metaKey)) {
return;
@@ -75,7 +77,10 @@ export class WorkspaceShellFacade {
key === 'p' &&
!event.shiftKey &&
!event.altKey &&
this.playlistSwitcher.available
this.playlistSwitcher.available &&
// An open dialog keeps the focus: the switcher would take it out
// of the dialog, and give it back to the toolbar behind it.
this.dialog.openDialogs.length === 0
) {
// ⌘K stays the command palette; ⌘P is the playlist switcher (and
// no longer prints the page in the browser build).