mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
Merge master into perf/zoneless-flag
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
ecf1245b5e
24 files changed
+219
-59
No files matched your search
@@ -70,9 +70,21 @@ function readEagerChecklist(): { open: string[]; done: string[] } {
|
||||
|
||||
const sources = readSources();
|
||||
|
||||
// Component metadata only: a comment or string that names the strategy is
|
||||
// not an Eager component.
|
||||
const eagerMetadata = /changeDetection\s*:\s*ChangeDetectionStrategy\.Eager\b/;
|
||||
|
||||
function withoutComments(text: string): string {
|
||||
return text.replace(/\/\*[\s\S]*?\*\//g, '').replace(/\/\/.*$/gm, '');
|
||||
}
|
||||
|
||||
function isEagerComponent(text: string): boolean {
|
||||
return eagerMetadata.test(withoutComments(text));
|
||||
}
|
||||
|
||||
test('the zoneless checklist lists exactly the components that are still Eager', () => {
|
||||
const eager = [...sources]
|
||||
.filter(([, text]) => text.includes('ChangeDetectionStrategy.Eager'))
|
||||
.filter(([, text]) => isEagerComponent(text))
|
||||
.map(([file]) => file)
|
||||
.sort();
|
||||
const { open } = readEagerChecklist();
|
||||
@@ -106,6 +118,23 @@ test('the guard skips test-only file names and keeps production ones', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('a comment that names the Eager strategy is not an Eager component', () => {
|
||||
assert.equal(
|
||||
isEagerComponent(
|
||||
'// was ChangeDetectionStrategy.Eager before C6\n' +
|
||||
'/* changeDetection: ChangeDetectionStrategy.Eager */\n' +
|
||||
'@Component({ changeDetection: ChangeDetectionStrategy.OnPush })'
|
||||
),
|
||||
false
|
||||
);
|
||||
assert.equal(
|
||||
isEagerComponent(
|
||||
'@Component({\n changeDetection: ChangeDetectionStrategy.Eager,\n})'
|
||||
),
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
test('ticked checklist entries name files that exist', () => {
|
||||
for (const file of readEagerChecklist().done) {
|
||||
assert.ok(sources.has(file), `${file} is ticked but does not exist`);
|
||||
|
||||
@@ -101,8 +101,7 @@ import { AppUpdateInstallService } from './services/app-update-install.service';
|
||||
</section>
|
||||
}
|
||||
`,
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -- Preserve pre-Angular 22 eager checking during the framework upgrade.
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
styles: [
|
||||
`
|
||||
.app-update-notification {
|
||||
|
||||
@@ -55,8 +55,7 @@ const debugAppComponent = createDevLogger('AppComponent');
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -- Preserve pre-Angular 22 eager checking during the framework upgrade.
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [
|
||||
AppStartupStatusComponent,
|
||||
AppUpdateNotificationPanelComponent,
|
||||
|
||||
@@ -178,8 +178,7 @@ function decorateReleaseNotesHtml(html: string): string {
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
`,
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -- Preserve pre-Angular 22 eager checking during the framework upgrade.
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
styles: [
|
||||
`
|
||||
.release-notes-dialog {
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
ElectronBridgeAppUpdateStatus,
|
||||
} from '@iptvnator/shared/interfaces';
|
||||
import { UpdateChannelOption } from './settings.models';
|
||||
import { markSectionForCheckOnFormEvents } from './settings-section-form-render';
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings-about-section',
|
||||
@@ -32,8 +33,7 @@ import { UpdateChannelOption } from './settings.models';
|
||||
],
|
||||
templateUrl: './settings-about-section.component.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -- Preserve pre-Angular 22 eager checking during the framework upgrade.
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
styles: [
|
||||
':host { display: contents; }',
|
||||
'.version-block .build-commit { opacity: 0.65; font-size: 0.85em; }',
|
||||
@@ -58,6 +58,13 @@ export class SettingsAboutSectionComponent {
|
||||
* setting. Absent in hosts that only render the version block.
|
||||
*/
|
||||
readonly form = input<FormGroup | null>(null);
|
||||
|
||||
constructor() {
|
||||
// Parent patches (Discard, backup import) change the form outside
|
||||
// this OnPush section's events.
|
||||
markSectionForCheckOnFormEvents(this.form);
|
||||
}
|
||||
|
||||
readonly updateChannelOptions = input<UpdateChannelOption[]>([]);
|
||||
|
||||
readonly buildCommitShort = computed(() => {
|
||||
|
||||
@@ -20,8 +20,7 @@ import { TranslateModule } from '@ngx-translate/core';
|
||||
],
|
||||
templateUrl: './settings-backup-section.component.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -- Preserve pre-Angular 22 eager checking during the framework upgrade.
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
styles: [':host { display: contents; }'],
|
||||
})
|
||||
export class SettingsBackupSectionComponent {
|
||||
|
||||
@@ -9,6 +9,7 @@ import { FormGroup, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { markSectionForCheckOnFormEvents } from './settings-section-form-render';
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings-dashboard-section',
|
||||
@@ -21,10 +22,15 @@ import { TranslateModule } from '@ngx-translate/core';
|
||||
],
|
||||
templateUrl: './settings-dashboard-section.component.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -- Preserve pre-Angular 22 eager checking during the framework upgrade.
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
styles: [':host { display: contents; }'],
|
||||
})
|
||||
export class SettingsDashboardSectionComponent {
|
||||
readonly form = input.required<FormGroup>();
|
||||
|
||||
constructor() {
|
||||
// Parent patches (Discard, backup import) change the form outside
|
||||
// this OnPush section's events.
|
||||
markSectionForCheckOnFormEvents(this.form);
|
||||
}
|
||||
}
|
||||
@@ -31,8 +31,7 @@ type SettingsDeleteSummaryItem = {
|
||||
selector: 'app-settings-delete-all-playlists-dialog',
|
||||
templateUrl: './settings-delete-all-playlists-dialog.component.html',
|
||||
styleUrls: ['./settings-delete-all-playlists-dialog.component.scss'],
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -- Preserve pre-Angular 22 eager checking during the framework upgrade.
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [
|
||||
CommonModule,
|
||||
MatButtonModule,
|
||||
|
||||
@@ -17,6 +17,7 @@ import { EpgViewMode } from '@iptvnator/shared/interfaces';
|
||||
import { EpgSourceStatusComponent } from '@iptvnator/ui/epg';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { EpgViewModeOption } from './settings.models';
|
||||
import { markSectionForCheckOnFormEvents } from './settings-section-form-render';
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings-epg-section',
|
||||
@@ -34,12 +35,18 @@ import { EpgViewModeOption } from './settings.models';
|
||||
],
|
||||
templateUrl: './settings-epg-section.component.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -- Preserve pre-Angular 22 eager checking during the framework upgrade.
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
styles: [':host { display: contents; }'],
|
||||
})
|
||||
export class SettingsEpgSectionComponent {
|
||||
readonly form = input.required<FormGroup>();
|
||||
|
||||
constructor() {
|
||||
// Parent patches (Discard, backup import) change the form outside
|
||||
// this OnPush section's events.
|
||||
markSectionForCheckOnFormEvents(this.form);
|
||||
}
|
||||
|
||||
readonly epgUrl = input.required<FormArray>();
|
||||
readonly isClearingEpgData = input(false);
|
||||
readonly canBrowseFiles = input(false);
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
StartupWindowModeOption,
|
||||
ThemeOption,
|
||||
} from './settings.models';
|
||||
import { markSectionForCheckOnFormEvents } from './settings-section-form-render';
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings-general-section',
|
||||
@@ -33,12 +34,18 @@ import {
|
||||
],
|
||||
templateUrl: './settings-general-section.component.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -- Preserve pre-Angular 22 eager checking during the framework upgrade.
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
styles: [':host { display: contents; }'],
|
||||
})
|
||||
export class SettingsGeneralSectionComponent {
|
||||
readonly form = input.required<FormGroup>();
|
||||
|
||||
constructor() {
|
||||
// Parent patches (Discard, backup import) change the form outside
|
||||
// this OnPush section's events.
|
||||
markSectionForCheckOnFormEvents(this.form);
|
||||
}
|
||||
|
||||
readonly languageEnum = input.required<typeof Language>();
|
||||
readonly themeOptions = input.required<ThemeOption[]>();
|
||||
readonly coverSizeOptions = input.required<CoverSizeOption[]>();
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
reportsPlaybackFailures,
|
||||
} from '@iptvnator/shared/interfaces';
|
||||
import { SettingsPlayerOption } from './settings.models';
|
||||
import { markSectionForCheckOnFormEvents } from './settings-section-form-render';
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings-playback-section',
|
||||
@@ -36,8 +37,7 @@ import { SettingsPlayerOption } from './settings.models';
|
||||
],
|
||||
templateUrl: './settings-playback-section.component.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -- Preserve pre-Angular 22 eager checking during the framework upgrade.
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
styles: [':host { display: contents; }'],
|
||||
})
|
||||
export class SettingsPlaybackSectionComponent {
|
||||
@@ -53,6 +53,13 @@ export class SettingsPlaybackSectionComponent {
|
||||
].join('\n');
|
||||
|
||||
readonly form = input.required<FormGroup>();
|
||||
|
||||
constructor() {
|
||||
// Parent patches (Discard, backup import) change the form outside
|
||||
// this OnPush section's events.
|
||||
markSectionForCheckOnFormEvents(this.form);
|
||||
}
|
||||
|
||||
readonly players = input.required<SettingsPlayerOption[]>();
|
||||
readonly streamFormatEnum = input.required<typeof StreamFormat>();
|
||||
readonly isDesktop = input(false);
|
||||
|
||||
@@ -14,6 +14,7 @@ import { MatInputModule } from '@angular/material/input';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { QRCodeComponent } from 'angularx-qrcode';
|
||||
import { markSectionForCheckOnFormEvents } from './settings-section-form-render';
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings-remote-control-section',
|
||||
@@ -30,12 +31,18 @@ import { QRCodeComponent } from 'angularx-qrcode';
|
||||
],
|
||||
templateUrl: './settings-remote-control-section.component.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -- Preserve pre-Angular 22 eager checking during the framework upgrade.
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
styles: [':host { display: contents; }'],
|
||||
})
|
||||
export class SettingsRemoteControlSectionComponent {
|
||||
readonly form = input.required<FormGroup>();
|
||||
|
||||
constructor() {
|
||||
// Parent patches (Discard, backup import) change the form outside
|
||||
// this OnPush section's events.
|
||||
markSectionForCheckOnFormEvents(this.form);
|
||||
}
|
||||
|
||||
readonly localIpAddresses = input.required<string[]>();
|
||||
readonly visibleQrCodeIp = input<string | null>(null);
|
||||
|
||||
|
||||
@@ -22,8 +22,7 @@ import { SettingsPlaylistDeleteSummary } from './settings.models';
|
||||
],
|
||||
templateUrl: './settings-reset-section.component.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -- Preserve pre-Angular 22 eager checking during the framework upgrade.
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
styles: [':host { display: contents; }'],
|
||||
})
|
||||
export class SettingsResetSectionComponent {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { ChangeDetectorRef, inject, type Signal } from '@angular/core';
|
||||
import { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop';
|
||||
import type { AbstractControl } from '@angular/forms';
|
||||
import { EMPTY, switchMap } from 'rxjs';
|
||||
|
||||
/**
|
||||
* Marks an OnPush settings section for check on every event of its form.
|
||||
*
|
||||
* The sections read form values and states in their templates (selected
|
||||
* theme, `epgField.value`, `form().value.player`), which are not signals.
|
||||
* The parent changes the form outside the section's template events: Discard
|
||||
* and backup import patch it, the store hydrates it, and the EPG file picker
|
||||
* sets a control after an `await`. Without this the section keeps showing
|
||||
* the previous value until some unrelated event marks it. `events` covers
|
||||
* value, status, touched and pristine changes, including those of child
|
||||
* controls, which bubble up to the group.
|
||||
*
|
||||
* Call it from a field initializer or the constructor.
|
||||
*/
|
||||
export function markSectionForCheckOnFormEvents(
|
||||
form: Signal<AbstractControl | null>
|
||||
): void {
|
||||
const changeDetector = inject(ChangeDetectorRef);
|
||||
toObservable(form)
|
||||
.pipe(
|
||||
switchMap((control) => control?.events ?? EMPTY),
|
||||
takeUntilDestroyed()
|
||||
)
|
||||
.subscribe(() => changeDetector.markForCheck());
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { TmdbApiService, TmdbCacheService } from '@iptvnator/services';
|
||||
import type { TmdbCacheStats } from '@iptvnator/shared/interfaces';
|
||||
import { markSectionForCheckOnFormEvents } from './settings-section-form-render';
|
||||
|
||||
type TmdbKeyTestState = 'idle' | 'testing' | 'success' | 'error';
|
||||
|
||||
@@ -33,8 +34,7 @@ type TmdbKeyTestState = 'idle' | 'testing' | 'success' | 'error';
|
||||
],
|
||||
templateUrl: './settings-tmdb-section.component.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -- Preserve pre-Angular 22 eager checking during the framework upgrade.
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
styles: [
|
||||
`
|
||||
app-settings-tmdb-section {
|
||||
@@ -92,6 +92,7 @@ export class SettingsTmdbSectionComponent {
|
||||
readonly isClearing = signal(false);
|
||||
|
||||
constructor() {
|
||||
markSectionForCheckOnFormEvents(this.form);
|
||||
// Sizing the cache is a full table scan, but this component only
|
||||
// exists while its section page is open, so loading on construction
|
||||
// preserves the old "wait until the user is actually looking"
|
||||
|
||||
@@ -54,8 +54,7 @@ export interface SettingsUnsavedChangesDialogData {
|
||||
}
|
||||
`,
|
||||
],
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -- Preserve pre-Angular 22 eager checking during the framework upgrade.
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<h2 mat-dialog-title>
|
||||
{{ 'SETTINGS.UNSAVED_DIALOG_TITLE' | translate }}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { FormArray, FormControl } from '@angular/forms';
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { EpgRuntimeBridgeService } from '@iptvnator/epg/data-access';
|
||||
@@ -143,6 +144,25 @@ describe('SettingsComponent form', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// The sections are OnPush and a Discard or backup import patches the
|
||||
// form outside their template events, so the section must mark
|
||||
// itself on the form's events. The fixture renders on its own here:
|
||||
// a forced detectChanges() would hide a section that is not marked.
|
||||
it('re-renders section selections after a value-only form patch', async () => {
|
||||
const darkTheme = () =>
|
||||
(fixture.nativeElement as HTMLElement).querySelector(
|
||||
'[data-test-id="DARK_THEME"]'
|
||||
);
|
||||
fixture.autoDetectChanges();
|
||||
await fixture.whenStable();
|
||||
expect(darkTheme()?.getAttribute('aria-checked')).toBe('false');
|
||||
|
||||
component.settingsForm.patchValue({ theme: Theme.DarkTheme });
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(darkTheme()?.getAttribute('aria-checked')).toBe('true');
|
||||
});
|
||||
|
||||
it('hydrates a shared web controls opt-out from the settings store', () => {
|
||||
settingsStore._setSettings({
|
||||
webPlayerSharedControls: false,
|
||||
@@ -185,6 +205,26 @@ describe('SettingsComponent form', () => {
|
||||
expect(settingsStore.updateSettings).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// The native file picker sets the EPG control after an await, with
|
||||
// no template event in the OnPush section; its status must follow.
|
||||
it('shows the source status after a control is set outside the section', async () => {
|
||||
setSettingsSection('epg');
|
||||
fixture.autoDetectChanges();
|
||||
const epgUrls = component.settingsForm.get('epgUrl') as FormArray;
|
||||
epgUrls.push(new FormControl(''));
|
||||
await fixture.whenStable();
|
||||
const status = () =>
|
||||
(fixture.nativeElement as HTMLElement).querySelector(
|
||||
'app-epg-source-status'
|
||||
);
|
||||
expect(status()).toBeNull();
|
||||
|
||||
epgUrls.at(epgUrls.length - 1).setValue('/tmp/guide.xml');
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(status()).not.toBeNull();
|
||||
});
|
||||
|
||||
it('stages the EPG view mode without writing to the store until Save', () => {
|
||||
setSettingsSection('epg');
|
||||
fixture.detectChanges();
|
||||
@@ -314,6 +354,29 @@ describe('SettingsComponent form', () => {
|
||||
expect(unsavedBar()).toBeNull();
|
||||
});
|
||||
|
||||
// The page owns the bar and is OnPush, and Save marks the form
|
||||
// pristine after an async store write, also on a page without a form
|
||||
// section. `pristine` and `valid` read the form's state signals, so
|
||||
// the page re-renders without a form subscription; no forced render
|
||||
// here, so a regression shows.
|
||||
it('hides after a save on a page without a form section', async () => {
|
||||
settingsStore.updateSettings.mockResolvedValue(undefined);
|
||||
setSettingsSection('backup');
|
||||
fixture.autoDetectChanges();
|
||||
component.settingsForm.get('theme')?.setValue(Theme.DarkTheme);
|
||||
component.settingsForm.markAsDirty();
|
||||
await fixture.whenStable();
|
||||
expect(unsavedBar()).not.toBeNull();
|
||||
|
||||
component.onSubmit();
|
||||
await fixture.whenStable();
|
||||
// The render the form event scheduled runs in the next macrotask.
|
||||
await new Promise((resolve) => setTimeout(resolve));
|
||||
|
||||
expect(component.settingsForm.pristine).toBe(true);
|
||||
expect(unsavedBar()).toBeNull();
|
||||
});
|
||||
|
||||
it('discard reverts a staged cover size (regression: eager persist made it stick)', () => {
|
||||
const largeCoverButton = (
|
||||
fixture.nativeElement as HTMLElement
|
||||
|
||||
@@ -102,8 +102,7 @@ export const SETTINGS_DEFAULT_SECTION = 'general';
|
||||
SettingsSearchResultsComponent,
|
||||
SettingsTmdbSectionComponent,
|
||||
],
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -- Preserve pre-Angular 22 eager checking during the framework upgrade.
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
providers: [
|
||||
SettingsAppUpdateFacade,
|
||||
SettingsBackupFacade,
|
||||
|
||||
@@ -749,7 +749,7 @@ allowlisted display name.
|
||||
|
||||
`network-error` is reserved for provider/network loading failures. Engines that expose concrete browser security evidence, such as CORS, mixed content, Content Security Policy, or private-network-access blocks, use `browser-access-error` so the UI can explain that the browser player was blocked before playback reached decoding.
|
||||
|
||||
mpegts.js `1.8.1` errors cross one shared structured boundary before the HTML5,
|
||||
mpegts.js `1.8.2` errors cross one shared structured boundary before the HTML5,
|
||||
Video.js, or ArtPlayer owner emits a diagnostic. Version-locked tests compare
|
||||
the installed public `ErrorTypes` and `ErrorDetails` exports with the accepted
|
||||
contract. Evidence retains only an exact type/detail pair, terminal
|
||||
|
||||
@@ -64,25 +64,30 @@ must be ticked here.
|
||||
two). Tick an entry by deleting `changeDetection: ChangeDetectionStrategy.Eager`
|
||||
(or setting OnPush) once its template state is signals, signal inputs or
|
||||
explicitly marked. The guard spec compares the unticked entries with the
|
||||
files that still contain `ChangeDetectionStrategy.Eager`.
|
||||
files whose component metadata still sets
|
||||
`changeDetection: ChangeDetectionStrategy.Eager` (comments do not count).
|
||||
The settings sections read form values in their templates and the parent
|
||||
patches the form outside their events (Discard, backup import, the EPG file
|
||||
picker), so each marks itself on the form's `events` through
|
||||
`markSectionForCheckOnFormEvents` (`apps/web/src/app/settings`).
|
||||
|
||||
### apps/web (15)
|
||||
|
||||
- [ ] `apps/web/src/app/app.component.ts` (idle audit root)
|
||||
- [ ] `apps/web/src/app/app-update-notification-panel.component.ts` (idle audit root)
|
||||
- [ ] `apps/web/src/app/settings/app-update-release-notes-dialog.component.ts`
|
||||
- [ ] `apps/web/src/app/settings/settings.component.ts`
|
||||
- [ ] `apps/web/src/app/settings/settings-about-section.component.ts`
|
||||
- [ ] `apps/web/src/app/settings/settings-backup-section.component.ts`
|
||||
- [ ] `apps/web/src/app/settings/settings-dashboard-section.component.ts`
|
||||
- [ ] `apps/web/src/app/settings/settings-delete-all-playlists-dialog.component.ts`
|
||||
- [ ] `apps/web/src/app/settings/settings-epg-section.component.ts`
|
||||
- [ ] `apps/web/src/app/settings/settings-general-section.component.ts`
|
||||
- [ ] `apps/web/src/app/settings/settings-playback-section.component.ts`
|
||||
- [ ] `apps/web/src/app/settings/settings-remote-control-section.component.ts`
|
||||
- [ ] `apps/web/src/app/settings/settings-reset-section.component.ts`
|
||||
- [ ] `apps/web/src/app/settings/settings-tmdb-section.component.ts`
|
||||
- [ ] `apps/web/src/app/settings/settings-unsaved-changes-dialog.component.ts`
|
||||
- [x] `apps/web/src/app/app.component.ts` (idle audit root)
|
||||
- [x] `apps/web/src/app/app-update-notification-panel.component.ts` (idle audit root)
|
||||
- [x] `apps/web/src/app/settings/app-update-release-notes-dialog.component.ts`
|
||||
- [x] `apps/web/src/app/settings/settings.component.ts`
|
||||
- [x] `apps/web/src/app/settings/settings-about-section.component.ts`
|
||||
- [x] `apps/web/src/app/settings/settings-backup-section.component.ts`
|
||||
- [x] `apps/web/src/app/settings/settings-dashboard-section.component.ts`
|
||||
- [x] `apps/web/src/app/settings/settings-delete-all-playlists-dialog.component.ts`
|
||||
- [x] `apps/web/src/app/settings/settings-epg-section.component.ts`
|
||||
- [x] `apps/web/src/app/settings/settings-general-section.component.ts`
|
||||
- [x] `apps/web/src/app/settings/settings-playback-section.component.ts`
|
||||
- [x] `apps/web/src/app/settings/settings-remote-control-section.component.ts`
|
||||
- [x] `apps/web/src/app/settings/settings-reset-section.component.ts`
|
||||
- [x] `apps/web/src/app/settings/settings-tmdb-section.component.ts`
|
||||
- [x] `apps/web/src/app/settings/settings-unsaved-changes-dialog.component.ts`
|
||||
|
||||
### libs/ui (20 files, 21 components)
|
||||
|
||||
@@ -177,7 +182,7 @@ the field a signal (or a `computed`), or writes it through one.
|
||||
| [ ] | `libs/portal/stalker/feature/src/lib/stalker-live-stream-layout/stalker-live-stream-layout.component.ts` (favorites load) | `favorites` Map filled in a `subscribe` without `markForCheck`; the component is OnPush already, so this is a latent bug today | portal |
|
||||
| [ ] | `libs/portal/xtream/feature/src/lib/portal-channels-list/portal-channels-list.component.ts` (favorites load) | same pattern; the neighbouring `favoriteMarks.changes$` handler does call `markForCheck` | portal |
|
||||
| [ ] | same file, programme dialog `afterClosed` | deletes from `epgPrograms`/`currentProgramsProgress` after `await` without marking | portal |
|
||||
| [ ] | `apps/web/src/app/settings/settings-backup.facade.ts` (backup import) | `change` listener on a detached file input → `hydrateFromStore()`; section templates read `form().value.theme`/`coverSize` | apps/web |
|
||||
| [x] | `apps/web/src/app/settings/settings-backup.facade.ts` (backup import) | `change` listener on a detached file input → `hydrateFromStore()`; section templates read `form().value.theme`/`coverSize`; each section now marks itself on its form's `events` (`markSectionForCheckOnFormEvents`), which `settings.component.form.spec.ts` guards without a forced render | apps/web |
|
||||
| [x] | `libs/ui/epg/src/lib/epg-guide/epg-guide.component.ts` (jump to now, keyboard focus) | `afterNextRender` registered from CDK/RxJS callbacks; zone.js followed them with a tick, zoneless schedules no render, so the guide opened at midnight. It now marks itself when it registers the hook. Found by `epg-guide.e2e.ts` on the zoneless build | flag |
|
||||
| [ ] | `libs/ui/remote-control/src/lib/remote-control/remote-control.component.ts` | plain `isLoading`/`error`/`status` written after `await` and from a 2 s `setInterval` | only if `apps/remote-control-web` goes zoneless |
|
||||
|
||||
@@ -187,7 +192,7 @@ They keep working under zoneless (`NgZone` becomes `NoopNgZone`, so `run`
|
||||
and `runOutsideAngular` just call through). Remove them in the flip PR, not
|
||||
before: with zone.js on they still matter.
|
||||
|
||||
- [ ] `apps/web/src/app/settings/settings-unload-guard.service.ts`: two
|
||||
- [x] `apps/web/src/app/settings/settings-unload-guard.service.ts`: two
|
||||
`zone.run` calls around the window-close dialog (IPC
|
||||
`onWindowCloseRequested` and `beforeunload`).
|
||||
- [x] `libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-session-controller.ts`:
|
||||
|
||||
@@ -21,7 +21,7 @@ const METADATA = createPlaybackSourceMetadata({
|
||||
});
|
||||
|
||||
describe('mpegts.js playback evidence', () => {
|
||||
it('locks the accepted public contract to mpegts.js 1.8.1', () => {
|
||||
it('locks the accepted public contract to mpegts.js 1.8.2', () => {
|
||||
expect(mpegts.version).toBe(MPEGTS_DIAGNOSTIC_VERSION);
|
||||
expect(mpegts.ErrorTypes).toEqual({
|
||||
NETWORK_ERROR: MpegTsPlaybackEngineType.Network,
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
MpegTsPlaybackStage,
|
||||
} from './mpegts-playback-evidence.model';
|
||||
|
||||
export const MPEGTS_DIAGNOSTIC_VERSION = '1.8.1';
|
||||
export const MPEGTS_DIAGNOSTIC_VERSION = '1.8.2';
|
||||
|
||||
interface MpegTsPlaybackCause {
|
||||
readonly stage: MpegTsPlaybackStageValue;
|
||||
|
||||
+1
-1
@@ -142,7 +142,7 @@
|
||||
"hls.js": "1.7.1",
|
||||
"iptv-playlist-parser": "github:4gray/iptv-playlist-parser#v0.15.2-iptvnator.2",
|
||||
"marked": "18.0.11",
|
||||
"mpegts.js": "1.8.1",
|
||||
"mpegts.js": "1.8.2",
|
||||
"ms": "2.1.3",
|
||||
"ngx-indexed-db": "22.0.0",
|
||||
"ngx-skeleton-loader": "11.3.0",
|
||||
|
||||
Generated
+5
-6
@@ -155,8 +155,8 @@ importers:
|
||||
specifier: 18.0.11
|
||||
version: 18.0.11
|
||||
mpegts.js:
|
||||
specifier: 1.8.1
|
||||
version: 1.8.1
|
||||
specifier: 1.8.2
|
||||
version: 1.8.2
|
||||
ms:
|
||||
specifier: 2.1.3
|
||||
version: 2.1.3
|
||||
@@ -7701,8 +7701,8 @@ packages:
|
||||
resolution: {integrity: sha512-blbA7XpAaOdC/PR0Tu8GiUxlx6CpBuRowQPYV7lhi9Yr9G9+dkPXIUjqjzc/NCn/uZZPopluaJlIYntIEI/VLw==}
|
||||
hasBin: true
|
||||
|
||||
mpegts.js@1.8.1:
|
||||
resolution: {integrity: sha512-mQ2daxqBaS+QmBYTmJMDUGwAOw08534b08KSyqUGkjkhuBODNod15j8pnRfVEIQVWulKNe201/6UqILe4m4YRQ==}
|
||||
mpegts.js@1.8.2:
|
||||
resolution: {integrity: sha512-cZYMa5muASH55wrS6JK5IGkUqnCA96c0f+WdnW91jIK9s/N6NzjmTQDJNYg9A5u/G2Q5nj2b3w1Zwzze/9guxA==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
|
||||
mrmime@2.0.1:
|
||||
@@ -8854,7 +8854,6 @@ packages:
|
||||
|
||||
shaka-player@5.2.12:
|
||||
resolution: {integrity: sha512-vEsuVDI3+oLh+yqpjXexQZlg2XMUBI0VrqatDuKWacPaxAud6Uph5uCowUPLhUgNG5XKWCpoM4l1r8qYzWH+zg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
shallow-clone@3.0.1:
|
||||
resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==}
|
||||
@@ -18064,7 +18063,7 @@ snapshots:
|
||||
'@xmldom/xmldom': 0.8.15
|
||||
global: 4.4.0
|
||||
|
||||
mpegts.js@1.8.1:
|
||||
mpegts.js@1.8.2:
|
||||
dependencies:
|
||||
es6-promise: 4.2.8
|
||||
events: 3.3.0
|
||||
|
||||
Reference in new issue
Block a user