From 002faafbd78256856382feb97109840984098232 Mon Sep 17 00:00:00 2001 From: 4gray <4gray@users.noreply.github.com> Date: Sun, 6 Sep 2026 00:44:08 +0200 Subject: [PATCH] fix(xtream): improve sync overlay contrast in both themes (#1546) --- .changes/xtream-sync-overlay-contrast.md | 6 ++ .../electron-backend-e2e/src/providers.e2e.ts | 50 ++++++++++++++++ docs/architecture/iptvnator-ui-guidelines.md | 11 ++++ ...kspace-shell-import-overlay.component.scss | 58 +++++++++++++------ 4 files changed, 106 insertions(+), 19 deletions(-) create mode 100644 .changes/xtream-sync-overlay-contrast.md diff --git a/.changes/xtream-sync-overlay-contrast.md b/.changes/xtream-sync-overlay-contrast.md new file mode 100644 index 000000000..52d3a31ad --- /dev/null +++ b/.changes/xtream-sync-overlay-contrast.md @@ -0,0 +1,6 @@ +--- +type: fix +area: xtream +--- + +The Xtream sync panel now keeps its status text, labels and Stop sync button clearly readable in both light and dark themes. diff --git a/apps/electron-backend-e2e/src/providers.e2e.ts b/apps/electron-backend-e2e/src/providers.e2e.ts index 2fb590abc..e414cf818 100644 --- a/apps/electron-backend-e2e/src/providers.e2e.ts +++ b/apps/electron-backend-e2e/src/providers.e2e.ts @@ -1,4 +1,5 @@ import type { Page } from '@playwright/test'; +import { applyTheme, expectTextContrast } from './theme-contrast'; import { addStalkerPortal, @@ -20,6 +21,55 @@ import { } from './electron-test-fixtures'; test.describe('Electron Provider Smoke Tests', () => { + test('@xtream @theme @electron keeps sync overlay text readable in both themes', async ({ + dataDir, + request, + }) => { + await resetMockServers(request, ['xtream']); + const app = await launchElectronApp(dataDir); + try { + // Hold the cache read so the real local-library overlay stays + // mounted while its theme and Material interaction states change. + await app.electronApp.evaluate(({ ipcMain }) => { + ipcMain.removeHandler('DB_GET_CATEGORIES'); + ipcMain.handle( + 'DB_GET_CATEGORIES', + () => new Promise(() => { + // Intentionally pending until this isolated app closes. + }) + ); + }); + await addXtreamPortal(app.mainWindow); + const overlay = app.mainWindow.locator( + 'app-workspace-shell-import-overlay' + ); + await expect(overlay).toContainText('Loading the saved catalog'); + const action = overlay.getByRole('button', { name: 'Stop sync' }); + for (const theme of ['light', 'dark', 'light'] as const) { + await applyTheme(app.mainWindow, theme); + for (const selector of [ + 'h3', + '.workspace-loading-overlay__badge', + '.workspace-loading-overlay__phase', + '.workspace-loading-overlay__detail', + ]) { + await expectTextContrast(overlay.locator(selector)); + } + // Leave headroom for Material's translucent hover/focus layer. + await expectTextContrast(action, 6); + await action.hover(); + await expectTextContrast(action); + await action.focus(); + await expectTextContrast(action); + await app.mainWindow.screenshot({ + path: test.info().outputPath(`sync-overlay-${theme}.png`), + }); + } + } finally { + await closeElectronApp(app); + } + }); + test('@xtream @electron loads Xtream content through the Electron IPC path', async ({ dataDir, request, diff --git a/docs/architecture/iptvnator-ui-guidelines.md b/docs/architecture/iptvnator-ui-guidelines.md index 1e0c02958..8a168d498 100644 --- a/docs/architecture/iptvnator-ui-guidelines.md +++ b/docs/architecture/iptvnator-ui-guidelines.md @@ -325,6 +325,17 @@ remain local when the meaning is explicit. - Use a solid or near-solid backing surface - Do not let it overlap or cover player controls +## Workspace Xtream Sync Overlay + +The import/refresh card pairs a near-opaque `--app-widget-bg` surface with +app-owned text colors in both themes. Blur belongs to the backdrop; card text +must not depend on an unprovided Material system surface token. Phase text uses +the primary foreground, and explanatory/progress copy uses a readable blend of +primary text and the widget surface instead of the decorative muted token. +Local and remote badges, and the outlined cancel button, resolve their text, +surfaces and interaction colors together. Electron provider E2E coverage holds +a cache read open and checks text contrast across live theme changes. + ## Progress Bars Channel preview progress and EPG current-program progress should stay visually aligned. diff --git a/libs/workspace/shell/feature/src/lib/workspace-shell/components/workspace-shell-import-overlay/workspace-shell-import-overlay.component.scss b/libs/workspace/shell/feature/src/lib/workspace-shell/components/workspace-shell-import-overlay/workspace-shell-import-overlay.component.scss index 2976f683a..4b5e993a3 100644 --- a/libs/workspace/shell/feature/src/lib/workspace-shell/components/workspace-shell-import-overlay/workspace-shell-import-overlay.component.scss +++ b/libs/workspace/shell/feature/src/lib/workspace-shell/components/workspace-shell-import-overlay/workspace-shell-import-overlay.component.scss @@ -1,3 +1,5 @@ +@use '@angular/material' as mat; + :host { display: contents; } @@ -21,19 +23,26 @@ } .workspace-loading-overlay__card { + --sync-secondary: color-mix( + in srgb, + var(--app-on-surface) 78%, + var(--app-widget-bg) + ); + --sync-accent: color-mix( + in srgb, + var(--app-selection-color) 60%, + var(--app-on-surface) + ); + width: min(440px, 100%); display: grid; gap: 14px; padding: 24px 24px 20px; border-radius: 24px; - border: 1px solid var(--app-separator, rgba(255, 255, 255, 0.12)); - background: - linear-gradient( - 180deg, - rgba(255, 255, 255, 0.06), - rgba(255, 255, 255, 0) - ), - var(--mat-sys-surface-container-high); + border: 1px solid var(--app-separator); + // Keep a bounded glass tint without letting the scrim own text contrast. + background: color-mix(in srgb, var(--app-widget-bg) 96%, transparent); + color: var(--app-on-surface); box-shadow: 0 24px 64px rgba(0, 0, 0, 0.32), 0 1px 0 rgba(255, 255, 255, 0.05) inset; @@ -45,7 +54,7 @@ font-size: 1.55rem; font-weight: 650; letter-spacing: -0.03em; - color: var(--app-heading-color, var(--mat-sys-on-surface)); + color: var(--app-heading-color); } } @@ -61,9 +70,9 @@ justify-content: center; padding: 6px 12px; border-radius: 999px; - border: 1px solid rgba(120, 220, 255, 0.24); - background: rgba(120, 220, 255, 0.08); - color: rgba(164, 233, 255, 1); + border: 1px solid var(--app-separator); + background: var(--app-hover-overlay); + color: var(--sync-secondary); font-size: 0.72rem; font-weight: 700; letter-spacing: 0.12em; @@ -71,9 +80,9 @@ } .workspace-loading-overlay__badge--remote { - border-color: rgba(105, 195, 255, 0.34); - background: rgba(83, 158, 255, 0.12); - color: rgba(202, 230, 255, 1); + border-color: var(--app-selection-border); + background: var(--app-selection-surface); + color: var(--sync-accent); } .workspace-loading-overlay__phase, @@ -84,14 +93,14 @@ .workspace-loading-overlay__phase { font-size: 1rem; - font-weight: 560; - color: var(--app-body-color, var(--mat-sys-on-surface)); + font-weight: 600; + color: var(--app-on-surface); } .workspace-loading-overlay__detail { max-width: 32ch; justify-self: center; - color: var(--app-muted-color, var(--mat-sys-on-surface-variant)); + color: var(--sync-secondary); line-height: 1.45; } @@ -100,11 +109,22 @@ justify-self: center; line-height: 1.45; font-variant-numeric: tabular-nums; - color: var(--app-muted-color, var(--mat-sys-on-surface-variant)); + color: var(--sync-secondary); text-wrap: balance; } .workspace-loading-overlay__action { + @include mat.button-overrides( + ( + outlined-label-text-color: var(--sync-accent), + outlined-label-text-weight: 600, + outlined-outline-color: var(--sync-secondary), + outlined-state-layer-color: var(--app-on-surface), + outlined-disabled-label-text-color: var(--sync-secondary), + outlined-disabled-outline-color: var(--app-separator), + ) + ); + justify-self: center; min-width: 180px; border-radius: 999px;