refactor(portal): move the header Back registry to shared data-access

WorkspaceBackNavigationService is root-provided signal state, which the
Nx ownership contract assigns to type:data-access; type:util takes new
pure helpers and contracts only. The service and its spec move to
@iptvnator/portal/shared/data-access, and the pure WorkspaceBackTarget
contract stays in @iptvnator/portal/shared/util.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5.5 committed 2026-10-03 09:09:12 +02:00
1 parent c031a150d8
commit 930ef5e6c1
10 files changed
+30 -31

No files matched your search

@@ -25,12 +25,13 @@ catalog scroll positions retain the existing navigation contract below.
The shell owns the page's Back action, but the workspace header renders it.
While `backAvailable()` is true, the shell registers a target with
`WorkspaceBackNavigationService` (`@iptvnator/portal/shared/util`): a label (the
host's `backLabel`, else the translated "Back"), whether Escape currently runs
it, and `run()`, which emits `backClicked`. The service keeps a stack in which
the newest registration wins, and each release removes only its own target. A
loading shell replaced by the loaded one therefore cannot clear its successor,
whichever is destroyed first. The header shows the target as an `arrow_back`
`WorkspaceBackNavigationService` (`@iptvnator/portal/shared/data-access`). The
target, the `WorkspaceBackTarget` contract in `@iptvnator/portal/shared/util`,
carries a label (the host's `backLabel`, else the translated "Back"), whether
Escape currently runs it, and `run()`, which emits `backClicked`. The service
keeps a stack in which the newest registration wins, and each release removes
only its own target. A loading shell replaced by the loaded one therefore
cannot clear its successor, whichever is destroyed first. The header shows the target as an `arrow_back`
icon button in its leading slot (`data-test-id="workspace-header-back"`), to
the right of the macOS traffic lights. That is where desktop apps and Material's
top app bar keep navigation. The header never scrolls, so the control stays
@@ -10,10 +10,8 @@ import {
SettingsStore,
} from '@iptvnator/services';
import type { DownloadMetadataSnapshot } from '@iptvnator/shared/interfaces';
import {
WorkspaceBackNavigationService,
type WorkspaceNavigationTarget,
} from '@iptvnator/portal/shared/util';
import { WorkspaceBackNavigationService } from '@iptvnator/portal/shared/data-access';
import type { WorkspaceNavigationTarget } from '@iptvnator/portal/shared/util';
import { BehaviorSubject } from 'rxjs';
import type {
DownloadActionResult,
@@ -6,3 +6,5 @@ export * from './lib/live-channel-playback-queue';
export * from './lib/source-health.service';
export * from './lib/source-cleanup.service';
export * from './lib/workspace-back-navigation.service';
@@ -1,9 +1,7 @@
import { signal } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import {
WorkspaceBackNavigationService,
WorkspaceBackTarget,
} from './workspace-back-navigation.service';
import { WorkspaceBackTarget } from '@iptvnator/portal/shared/util';
import { WorkspaceBackNavigationService } from './workspace-back-navigation.service';
describe('WorkspaceBackNavigationService', () => {
function createService(): WorkspaceBackNavigationService {
@@ -1,13 +1,5 @@
import { computed, Injectable, Signal, signal } from '@angular/core';
/** A page's Back action, rendered in the workspace header's leading slot. */
export interface WorkspaceBackTarget {
/** Accessible name and tooltip; null falls back to the generic "Back". */
readonly label: Signal<string | null>;
/** True while Escape on the page runs the same action. */
readonly escapeShortcut: Signal<boolean>;
run(): void;
}
import { computed, Injectable, signal } from '@angular/core';
import { WorkspaceBackTarget } from '@iptvnator/portal/shared/util';
/**
* Owns the header's Back slot. Pages register while they offer Back; the most
+1 -1
View File
@@ -23,7 +23,7 @@ export * from './lib/category-sort-state.service';
export * from './lib/keyboard';
export * from './lib/keyboard-shortcuts';
export * from './lib/remote-channel-navigation';
export * from './lib/workspace-back-navigation.service';
export * from './lib/workspace-back-target';
export * from './lib/workspace-header-context.service';
export * from './lib/workspace-view-command.types';
export * from './lib/workspace-view-command.service';
@@ -0,0 +1,10 @@
import { Signal } from '@angular/core';
/** A page's Back action, rendered in the workspace header's leading slot. */
export interface WorkspaceBackTarget {
/** Accessible name and tooltip; null falls back to the generic "Back". */
readonly label: Signal<string | null>;
/** True while Escape on the page runs the same action. */
readonly escapeShortcut: Signal<boolean>;
run(): void;
}
@@ -1,7 +1,7 @@
import { Component, signal } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { WorkspaceBackNavigationService } from '@iptvnator/portal/shared/util';
import { WorkspaceBackNavigationService } from '@iptvnator/portal/shared/data-access';
import {
DetailActionsTemplateDirective,
DetailMetaTemplateDirective,
@@ -12,10 +12,8 @@ import {
output,
ChangeDetectionStrategy,
} from '@angular/core';
import {
WorkspaceBackNavigationService,
WorkspaceBackTarget,
} from '@iptvnator/portal/shared/util';
import { WorkspaceBackNavigationService } from '@iptvnator/portal/shared/data-access';
import { WorkspaceBackTarget } from '@iptvnator/portal/shared/util';
import { ContentHeroComponent } from '../content-hero/content-hero.component';
import { ContentAboutComponent } from './content-about.component';
import {
@@ -3,9 +3,9 @@ import { toSignal } from '@angular/core/rxjs-interop';
import { Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import { startWith } from 'rxjs';
import { WorkspaceBackNavigationService } from '@iptvnator/portal/shared/data-access';
import {
PORTAL_EXTERNAL_PLAYBACK,
WorkspaceBackNavigationService,
WorkspaceHeaderContextService,
WorkspaceResolvedCommandItem,
} from '@iptvnator/portal/shared/util';