fix(ui): app-prefix detail template directive selectors, build fixes

detailTags/detailMeta/detailActions → appDetailTags/appDetailMeta/
appDetailActions per @angular-eslint/directive-selector; type the
Escape host listener as Event; drop a redundant ?? in season tabs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Fable 5 committed 2026-07-04 18:49:37 +02:00
1 parent 81605d3ded
commit 6e2cc56e2c
11 files changed
+26 -26

No files matched your search

+1 -1
View File
@@ -622,7 +622,7 @@ This project uses modern Angular signal-based APIs and patterns. **ALWAYS** use
- Xtream and Stalker detail pages use the shared `PortalDetailShellComponent` (`libs/ui/components/src/lib/portal-detail-shell/`) with two states: **Browse** (hero with poster/metadata/actions, episodes below) and **Watch** (hero collapses with a ~300ms morph, the inline player takes the full content width, metadata moves to an About block below the episodes)
- Watch state derives from `inlinePlayback() !== null` only; external MPV/VLC playback keeps the browse layout. Esc and the now-playing back button close the inline player back to browse without route navigation
- Hosts pass hero chips/meta/actions as `*detailTags`/`*detailMeta`/`*detailActions` templates; the shell stamps them into both the hero and the About block
- Hosts pass hero chips/meta/actions as `*appDetailTags`/`*appDetailMeta`/`*appDetailActions` templates; the shell stamps them into both the hero and the About block
- Seasons are tabs (`SeasonTabsComponent`, dropdown beyond 6 seasons) with auto-selection (playing episode's season → resume season → first) that fires the same `seasonSelected` lazy-load/enrichment hooks as manual clicks; grid/list episode view toggle persists to localStorage; season descriptions come from `get_series_info` (Xtream) or TMDB (Stalker)
- See `docs/architecture/embedded-inline-playback.md` ("Two-State Detail Layout")
@@ -101,8 +101,8 @@ which owns the page scroll container and two layout states:
Contracts:
- Hosts provide hero chips/meta/actions as `*detailTags` / `*detailMeta` /
`*detailActions` templates; the shell stamps them into the hero and again
- Hosts provide hero chips/meta/actions as `*appDetailTags` / `*appDetailMeta` /
`*appDetailActions` templates; the shell stamps them into the hero and again
into the About block. Degradation stays "missing → not rendered".
- The shell never wraps the `[detail-player]` slot in a conditional; the
host's `@if (inlinePlayback())` is the only creator/destroyer of the
@@ -9,7 +9,7 @@
(closePlayerRequested)="closeInlinePlayer()"
>
<!-- Tags: Year, Genre, Rating -->
<ng-template detailTags>
<ng-template appDetailTags>
@if (serial.info.releasedate) {
<span class="details__tag">{{ serial.info.releasedate }}</span>
}
@@ -29,7 +29,7 @@
</ng-template>
<!-- Metadata Grid -->
<ng-template detailMeta>
<ng-template appDetailMeta>
@if (serial.info.actors || serial.info.tmdb_cast?.length) {
<div class="details__meta-item">
<span class="label">{{ 'XTREAM.ACTORS' | translate }}</span>
@@ -89,7 +89,7 @@
</ng-template>
<!-- Action Buttons -->
<ng-template detailActions>
<ng-template appDetailActions>
@if (quickStartAction(); as action) {
<button
class="play-btn"
@@ -10,7 +10,7 @@
(backClicked)="goBack()"
(closePlayerRequested)="closeInlinePlayer()"
>
<ng-template detailTags>
<ng-template appDetailTags>
@if (info.releaseDate) {
<span class="details__tag">{{
info.releaseDate | slice: 0 : 4
@@ -31,7 +31,7 @@
}
</ng-template>
<ng-template detailMeta>
<ng-template appDetailMeta>
@if (info.cast || info.tmdb_cast?.length) {
<div class="details__meta-item">
<span class="label">{{ 'XTREAM.CAST' | translate }}</span>
@@ -87,7 +87,7 @@
}
</ng-template>
<ng-template detailActions>
<ng-template appDetailActions>
@if (quickStartAction(); as action) {
<button
class="play-btn"
@@ -9,7 +9,7 @@
(backClicked)="goBack()"
(closePlayerRequested)="closeInlinePlayer()"
>
<ng-template detailTags>
<ng-template appDetailTags>
@if (info.releasedate) {
<span class="details__tag">{{
info.releasedate | slice: 0 : 4
@@ -31,7 +31,7 @@
}
</ng-template>
<ng-template detailMeta>
<ng-template appDetailMeta>
@if (info.actors || info.cast || info.tmdb_cast?.length) {
<div class="details__meta-item">
<span class="label">{{ 'XTREAM.ACTORS' | translate }}</span>
@@ -97,7 +97,7 @@
}
</ng-template>
<ng-template detailActions>
<ng-template appDetailActions>
@if (hasPlaybackPosition()) {
<button
class="play-btn play-btn--resume"
@@ -277,7 +277,7 @@
[posterUrl]="fallback.posterUrl"
(backClicked)="goBack()"
>
<ng-template detailTags>
<ng-template appDetailTags>
<span
class="details__tag details__tag--status"
data-testid="xtream-vod-fallback-status"
@@ -300,7 +300,7 @@
}
</ng-template>
<ng-template detailMeta>
<ng-template appDetailMeta>
<div
class="details__fallback-note"
data-testid="xtream-vod-fallback"
@@ -9,7 +9,7 @@ import { TranslateModule } from '@ngx-translate/core';
*
* Degradation rule: anything missing simply is not rendered — no "N/A"
* placeholders. Chips and credits are stamped from the host-provided
* *detailTags / *detailMeta templates, so the host's own @if guards apply.
* *appDetailTags / *appDetailMeta templates, so the host's own @if guards apply.
*/
@Component({
selector: 'app-content-about',
@@ -9,7 +9,7 @@ import { Directive, TemplateRef, inject } from '@angular/core';
* appear once, a TemplateRef can be stamped any number of times.
*/
@Directive({
selector: '[detailTags]',
selector: '[appDetailTags]',
standalone: true,
})
export class DetailTagsTemplateDirective {
@@ -17,7 +17,7 @@ export class DetailTagsTemplateDirective {
}
@Directive({
selector: '[detailMeta]',
selector: '[appDetailMeta]',
standalone: true,
})
export class DetailMetaTemplateDirective {
@@ -25,7 +25,7 @@ export class DetailMetaTemplateDirective {
}
@Directive({
selector: '[detailActions]',
selector: '[appDetailActions]',
standalone: true,
})
export class DetailActionsTemplateDirective {
@@ -24,13 +24,13 @@ import { PortalDetailShellComponent } from './portal-detail-shell.component';
[playbackActive]="playbackActive()"
(closePlayerRequested)="closeRequests = closeRequests + 1"
>
<ng-template detailTags>
<ng-template appDetailTags>
<span class="details__tag">2026</span>
</ng-template>
<ng-template detailMeta>
<ng-template appDetailMeta>
<div class="details__meta-item">Cast entry</div>
</ng-template>
<ng-template detailActions>
<ng-template appDetailActions>
<button class="play-btn">Play</button>
</ng-template>
@if (playbackActive()) {
@@ -76,7 +76,7 @@ export class PortalDetailShellComponent {
});
}
onEscape(event: KeyboardEvent): void {
onEscape(event: Event): void {
if (!this.playbackActive()) return;
if (event.defaultPrevented) return;
// Browser fullscreen owns Escape (exits fullscreen first).
@@ -30,7 +30,7 @@
}}
</span>
<span class="season-tabs__menu-count">
{{ episodeCounts()[seasonKey] ?? 0 }}
{{ episodeCounts()[seasonKey] }}
</span>
@if (isSeasonCompleted(seasonKey)) {
<mat-icon class="season-tabs__done">check</mat-icon>
@@ -10,7 +10,7 @@
(closePlayerRequested)="closeInlinePlayback()"
>
<!-- Tags: Year, Genre, Duration, Rating -->
<ng-template detailTags>
<ng-template appDetailTags>
@if (meta.year) {
<span class="details__tag">{{ meta.year }}</span>
}
@@ -31,7 +31,7 @@
</ng-template>
<!-- Metadata Grid -->
<ng-template detailMeta>
<ng-template appDetailMeta>
@if (meta.actors || meta.tmdbCast?.length) {
<div class="details__meta-item">
<span class="label">{{ 'XTREAM.ACTORS' | translate }}</span>
@@ -90,7 +90,7 @@
</ng-template>
<!-- Action Buttons -->
<ng-template detailActions>
<ng-template appDetailActions>
<!-- Resume/Play buttons -->
@if (hasPlaybackPosition()) {
<button