Commit Graph
31 Commits
Author SHA1 Message Date
4grayandClaude Fable 5.1 7d1503fd31 feat(epg): rebuild the programme guide for M3U playlists (#1560)
* docs(epg): add programme guide redesign spec for the M3U host

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs(epg): add programme guide implementation plan

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat(epg): add window-scoped guide programme queries

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(epg): harden guide query scoping, caps and row mapping

- Scoped guide programme/coverage queries now include legacy
  (unsourced) rows via source_url IN (...) OR IS NULL OR '',
  mirroring EpgQueryService's legacy fallback.
- getProgramsForChannels/getProgramCoverage build their result from
  the normalized, capped window.channelIds instead of the raw
  request, so a key cut by the cap is absent rather than [] — an
  invalid window now returns {}. Truncation logs counts only.
- Split the 100-channel guide cap from a new 2000-key coverage cap,
  and cap sourceUrls at 50; normalizeGuideWindow takes the cap as a
  parameter and moved (with guideWindowOverlapSqlText) into
  epg-guide-window.util.ts.
- Extracted shared row mapping (toEpgProgramFromRow/isValidEpgProgram)
  into epg-program-row.util.ts, used by both EpgQueryService and
  EpgGuideQueryService so invalid start/stop rows are dropped
  identically in both.
- Added a real-SQLite-backed test for the overlap predicate's exact
  text, plus per-key array copies in the response.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(epg): render the guide predicate in tests and document its scope

Correct the guide query's JSDoc: it runs one query accepting the union
of requested-source and unsourced legacy rows, unlike EpgQueryService's
two-query scoped-then-legacy fallback. Replace the hand-maintained
plain-SQL twin of the Drizzle overlap predicate with a rendered copy of
the real predicate (SQLiteSyncDialect().sqlToQuery) in the spec, add a
source-scoping case, and drop the now-redundant operator-sequence test.
warnIfTruncated reuses uniqueTrimmedStrings and names which read
(programme/coverage) was truncated. Rename epg-query.service.ts's local
EpgProgramRow to EpgProgramSelectRow so it isn't confused with the
shared EpgProgramRow type, and document getProgramCoverage like its
sibling.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat(epg): expose guide programme and coverage reads over the bridge

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs(epg): separate coverage chunk size in the guide plan

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat(epg): add guide source contract, day layout maths and preferences

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(epg): key guide IPC answers by trimmed, present keys only

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs(epg): guide search hits carry a row id

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(epg): make guide geometry DST-safe and tighten the contract

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat(epg): cache guide programmes per day with batched loading

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat(epg): add guide keyboard navigation controller

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(epg): make guide programme cache robust to first-run effects and coverage failures

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat(epg): add the programme guide grid components

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat(epg): add a Guide button to the timeline toolbar

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat(m3u): adapt the playlist channel list to the guide contract

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(m3u): guard the guide's initial group scope and track language changes

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat(m3u): open the programme guide in place with a docked player

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(epg): scope guide keys to the grid, clip the now-line and re-measure on resize

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* refactor(epg): remove the multi-EPG overlay and the channel-range IPC

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs(epg): document the programme guide and its release note

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* i18n(epg): translate the programme guide

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(m3u): let the guide own the keyboard and gate its entry points

While the programme guide is open the docked player carries
`data-player-shortcuts-suspended`, which `ControlsShortcuts` now honours
alongside `[inert]` — the arrows moved the player's volume instead of the
guide's row focus. The external-player strip loses its Collapse toggle
(nothing to reveal, no preference to write), the header action and its
palette command report `disabled` when the guide cannot open, the docked
strip derives its programme from the active channel's own schedule instead
of the retained NgRx value, switching playlists closes the guide, and the
collapsed strip can reach 48 px on phones.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(m3u): keep the sidebar mounted while the guide is open

Guide mode wrapped the sidebar in `@if (!guideOpen())`, so opening the
guide destroyed `app-channel-list-container`, whose `ngOnDestroy`
dispatches `resetActiveChannel()`. That cleared the active channel, which
unmounted the block hosting `app-epg-guide` and tripped the
`!canOpenGuide()` effect into closing the guide again: the guide never
appeared and the page dropped to "Please select a channel".

The sidebar now stays mounted and is hidden with
`.sidebar--guide-hidden` plus `inert`, so it is neither focusable nor read
by assistive technology while the guide owns the layout. Hiding also
preserves the channel list's scroll position across guide toggles.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test(e2e): cover the programme guide flow

Imports a two-channel playlist with XMLTV, opens the guide from the
timeline toolbar and asserts the row list, the "Only with EPG" filter, a
channel switch that keeps the guide open, the hidden-but-mounted sidebar,
and that the player element survives both the mode and channel switches.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* chore(epg): tidy guide docs, palette gating and the unbound output

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(epg): match guide favorites by channel URL and skip re-activating the playing row

Favorites are persisted by channel URL (FavoritesActions.updateFavorites),
so the Favorites scope compared the wrong key; the id stays as a legacy
fallback. A double-click arrives as click, click, dblclick and each
activate restarts playback, so the guide now leaves the already-playing row
alone and the commit path only closes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* perf(epg): let the guide window predicate use the programme time index

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(m3u): stabilise guide row identity, seed the sidebar group and provide translations in every player fixture

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* refactor(epg): split the guide shell, add a roving focus model and offset-aware search times

The shell component now owns rows, focus and the viewport only: the day,
zoom, density, filters, clock and day geometry move to EpgGuideViewState,
and every programme-dialog entry point to EpgGuideDialogController.

Keyboard navigation is reachable by assistive technology: exactly one grid
cell carries tabindex="0" (the focused cell, else the playing row's channel
cell, else the first row's), the guide moves DOM focus with it after each
handled key, a click hands the roving index to the clicked cell, and the
viewport, rows and cells expose grid/row/gridcell roles.

Search results were formatting raw provider instants, so they ignored the
EPG display offset; they go through getProgramTimeMs like every other time
the guide renders.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(m3u): make guide row ids collision-proof and gate the G shortcut

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(epg): keep guide keys on the grid, reconcile focus with filtered rows and wrap the toolbar

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs(epg): describe guide row ids as scope-local

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(epg): clear guide search on scope change, match the active duplicate by url, keep failed coverage unknown

Search hits carry scope-local row ids, so a scope change drops them.
Two playlist entries can share an id but not a stream, so the active row
is matched by id + url before falling back to the id. A failed coverage
query now rejects instead of answering an empty set, which the guide
already treats as "coverage unknown" (every row stays visible).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(epg): tell duplicate guide rows apart by group, keep G out of dialogs, use prototype-safe answers

The store spreads the selected channel, so the active row is matched by
id, url, group and name before widening; G no longer closes the guide from
a dialog or menu; guide answers use null-prototype records so a key named
__proto__ stays an own property.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(epg): let coverage reject on lookup failures and compare whole entries for the active guide row

EpgQueryService.getChannelMetadata swallowed database errors into {}, so the
guide's coverage read could publish an empty set after a transient failure;
the guide now uses the strict resolveChannelMetadata (getChannelMetadata is
the fail-soft wrapper around it). The active guide row is matched on the
whole channel entry (all fields except the reducer-rewritten epgParams)
before widening to url and id, so copies that differ only in playback
headers or logo are told apart.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(epg): let the guide return catch-up to live and normalise programme-search rows

The guide source contract gains an optional livePlayback signal: while the
host plays a catch-up URL, the active row may be activated again, which is
how the M3U host returns to live. EPG_DB_SEARCH_PROGRAMS now maps the raw
snake_case rows to the EpgProgram shape the bridge promises (plus the joined
channel name), so search hits resolve their channel and keep descriptions.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(epg): name search hits, keep guide coverage strict on mapping failures

- Search results and the unresolved programme dialog show the channel's
  display name (playlist row name, else the XMLTV display name the search
  joined in) instead of the raw XMLTV id.
- The guide coverage read resolves manual mappings through a strict variant
  that rejects on database failure, so a mapped channel can never be reported
  as uncovered and hidden by "Only with EPG".
- Architecture doc describes the tiered active-row resolution.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(epg): offer the Guide action in the list view too

The EPG list view mirrors the timeline's input/output contract, but the Guide
action was bound only in the timeline branch, so Settings → EPG → Guide view =
List lost the in-panel entry point. The list toolbar now carries the same
icon-only Guide button behind `guideAvailable`/`openGuide`, and the M3U
host binds it in both branches.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 18:47:25 +02:00
4gray eb602db5fc fix(ui): restore channel and detail keyboard scrolling (#1542)
* fix(ui): restore channel and detail keyboard scrolling

* test(ui): drag below the Windows scrollbar arrow
2026-09-05 15:02:57 +02:00
4gray bfad82c26c fix(settings): stop settings silently reverting on restart (#1272)
Settings live in the renderer's IndexedDB, and two failure modes made them look
saved while nothing reached disk.

A second app instance sharing the same userData directory cannot take the
Chromium storage lock, so its renderer reads defaults and every write is
dropped. The app now holds a single-instance lock and focuses the running window
instead of starting a rival copy. The lock is requested after the userData
override so E2E runs with their own data dir keep independent locks, and after
Squirrel event handling. IPTVNATOR_ALLOW_MULTIPLE_INSTANCES=1 opts out for local
CDP debugging.

updateSettings() patches in-memory state before persisting and the submit path
had no rejection handler, so a failed write produced an unhandled rejection and
no user-visible feedback. SettingsStore now records which half of the round trip
failed, and the settings page surfaces it through a dismissible error snackbar;
the dialog stays open on failure so the save can be retried.

Two follow-ups from review, both wider than the report:

- a second launch now re-creates the main window when the lock owner has none
  left, so closing the last window on macOS no longer leaves a second launch
  quitting silently with nothing on screen
- App.onMainWindowCreated() re-runs window-owned bindings for every rebuilt
  window, so the downloads broadcaster stops holding a destroyed window. This
  also fixes the same bug on the pre-existing dock `activate` path.

Closes #1156
Closes #102
2026-07-27 23:14:30 +02:00
4grayandClaude Fable 5 a51c537bb2 fix(theme): make scrollbars follow the app theme instead of the OS color scheme (#1179)
On Windows with a light OS theme, scrollbars rendered light even when the
app was switched to dark. Two combined causes:

- The page never declared `color-scheme`, so Chromium colored native
  scrollbars from the OS preference. Declare `color-scheme: light` on html
  and flip it to `dark` via `html:has(> body.dark-theme)` plus the
  `.dark-theme` block itself.
- Scrollbar styling referenced `--mat-sys-*` tokens, which are never
  emitted by the current Material theme setup (mat.define-theme +
  all-component-themes does not produce system tokens). Those
  `scrollbar-color` declarations computed to `auto`, falling back to the
  native (light) scrollbar. Switch scrollbar styling to the `--app-muted-color`
  design token (defined for both themes), replace hardcoded white
  `rgba(255,255,255,.08)` thumbs, and add an explicit `scrollbar-color`
  where only `scrollbar-width: thin` was set.

Verified live in Electron via CDP in both themes: scrollbar-color resolves
and scrollbars render dark in dark theme regardless of the OS setting.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 17:24:49 +02:00
4grayandClaude Fable 5 1c710d69ef chore(cleanup): delete nine dead components, orphaned i18n keys and unused deps (#1116)
* chore(cleanup): delete nine dead components, orphaned i18n keys and unused deps

Removes verified-dead components (0 class/selector references outside
their own files): EpgListComponent (+ epg-list-item), EpgViewComponent,
LiveEpgPanelComponent, StalkerCollectionChannelsListComponent,
NavigationComponent, FilterSortMenuComponent, video-player
ToolbarComponent, PortalCollectionShellComponent and
LoadingOverlayComponent, together with their barrel exports.

Alive code extracted from the deleted trees:
- LiveEpgPanelSummary -> libs/ui/shared-portals/src/lib/live-epg-panel-summary.ts
- EpgProgramActivationEvent -> libs/ui/epg/src/lib/epg-program-activation-event.ts
- epg-list.utils.ts trimmed to the three timeline-used helpers and moved
  to libs/ui/epg/src/lib/epg-program.utils.ts
- epg-item-description/ moved up out of the deleted epg-list/ folder

Also removes 18 i18n keys now unused (from all 18 locales), dead CSS
selectors targeting the deleted elements, and unused dependencies:
lodash (+ @types/lodash), semver, @ngrx/component-store and
@videojs/http-streaming (videojs-quality-selector-hls declares no peer
dependency on it; video.js 8 bundles VHS).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore(coverage): move shared-portals to Tier C, fix stale doc references

The Tier A gate failed in CI because deleting the dead epg-view and
live-epg-panel components removed the only specs in libs/ui/shared-portals.
The lib now contains a single type-only interface (LiveEpgPanelSummary),
so there is no runtime code to unit test; reclassify it to Tier C with a
documented reason, matching the gate's own guidance.

Also update remaining doc references to the deleted components in
docs/architecture/stalker-epg.md, iptvnator-ui-guidelines.md and
CLAUDE.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 11:09:50 +02:00
4gray 89916af9d8 feat(search): add workspace global search with M3U support
Moves global search into a routed workspace view, adds M3U live/radio results, lazy pagination, and DB-backed matching improvements.
2026-06-20 23:05:48 +02:00
4grayandClaude Fable 5 8e0abe6feb feat(ui): custom title bar with window controls for Windows and Linux (#1042)
* feat(ui): add custom title bar window controls for Windows and Linux

Hide the native title bar on win32/linux (titleBarStyle: 'hidden', frame
untouched so native resize borders and snapping keep working) and render
minimize / maximize-restore / close buttons in the renderer, mirroring the
existing macOS traffic-light setup.

- New WINDOW:* IPC contract (minimize, toggle-maximize, close, get-state)
  handled in window.events.ts, resolved from the sender WebContents;
  close goes through win.close() so window-bounds persistence still runs.
- WINDOW:STATE_CHANGED pushed on maximize/unmaximize/fullscreen so the
  maximize/restore glyph stays correct for OS-triggered changes; controls
  hide while fullscreen.
- WindowControlsComponent mounts once in app-root as a manual popover so
  it stays in the browser top layer above CDK overlays (dialogs,
  multi-EPG) - same behavior as macOS traffic lights.
- Theme-aware via CSS vars (--app-on-surface, --app-hover-overlay);
  Windows-red close hover. Drag regions get right padding through a
  body-level frameless-platform class.
- Gated by RuntimeCapabilitiesService.usesCustomWindowControls; PWA and
  macOS never mount the controls.

Includes unit specs for the component and IPC handlers, an Electron E2E
suite (window-controls.e2e.ts), and a window-chrome section in
docs/architecture/workspace-shell.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(linux): upgrade Electron to 41 for frameless window decorations on Wayland

With the native title bar hidden, Linux windows lost the WM-drawn shadow
and rounded corners. Electron draws client-side decorations only on
native Wayland, and frameless-window CSD (GTK drop shadow + extended
resize boundaries) landed in Electron 41 - before that, frameless
windows render as plain rectangles.

- electron ^39.8.5 -> ^41.7.2 (Wayland auto-detected since 38.2; X11
  sessions remain undecorated, matching other frameless Electron apps;
  Windows keeps its DWM shadow and rounded corners).
- better-sqlite3 pinned to exactly 12.9.0: the last release shipping
  prebuilt binaries for both Node 20 (ABI 115, Jest) and Electron 41
  (ABI 145, runtime). 12.10.0 dropped the Node 20 prebuilds, forcing a
  from-source build that fails without a C++ toolchain.
- pnpm override node-abi 3.85.0 -> 3.92.0 so electron-builder
  install-app-deps can map Electron 41 to ABI 145.

Reviewed Electron 40/41 breaking changes: only the renderer clipboard
deprecation, which this app does not use.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(e2e): address review feedback and window-managerless Linux CI

- Skip the three window-manager-dependent E2E assertions (maximize
  toggle, main-process state sync, minimize) on Linux CI: GitHub's
  ubuntu runners drive Electron under xvfb without a window manager, so
  maximize/minimize state never materializes there. Windows CI and
  local Linux/macOS runs keep the coverage.
- WINDOW:TOGGLE_MAXIMIZE now returns the requested state instead of
  re-reading isMaximized() right after the call, which races on Linux
  window managers where maximize()/unmaximize() complete
  asynchronously; the WINDOW:STATE_CHANGED push stays authoritative.
- Skip attaching window-state push listeners on macOS, where the
  custom controls never mount and the IPC traffic had no subscriber.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(ui): gate custom window controls on the full bridge surface

Include getWindowState and onWindowStateChange in the
usesCustomWindowControls capability check — the controls rely on both
for initial state and for keeping the maximize/restore glyph in sync
with OS-triggered changes, so a partial bridge should not mount them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 12:03:27 +02:00
4gray 2a769ad44e Merge origin/master into PR 955 2026-05-22 01:34:56 +03:00
4gray b2909a1186 feat(workspace): add keyboard shortcuts help (#957)
* feat(workspace): add keyboard shortcuts help

* style(workspace): polish keyboard shortcuts dialog

* fix(workspace): address shortcuts review feedback

* docs(workspace): clarify desktop shortcut availability

* refactor(workspace): split keyboard shortcut definitions
2026-05-21 19:15:46 +03:00
4grayandClaude Opus 4.7 115911d2a6 refactor(settings): kill triple header, compact theme picker, real footer
Four UX-audit fixes for the Settings page:

1. Header dedupe
   - The page used to stack THREE headers vertically: the workspace rail's
     "Settings" entry, a page-level <h1>Settings</h1> + subtitle, and the
     General section's <h3>General</h3> + subtitle. The page-level and
     section-level subtitles both used SETTINGS.GENERAL_SUBTITLE
     ("Change the configuration of the application"), word-for-word.
   - Drop the visible page-level header entirely. Keep an a11y-only
     <span class="visually-hidden"> for the data-test-id hook so the
     existing settings.component.spec selector still resolves. Left rail
     announces "Settings"; section header is now the page anchor.
   - Add a global .visually-hidden helper to styles.scss for re-use.

2. Theme + Cover-size pickers shrink to a real segmented control
   - .theme-switcher was a 3-column grid of ~78px-tall buttons making
     "Choose theme" look like the most important action in Settings.
     Replaced with a 30px-tall segmented control (track + thumb with
     box-shadow on the selected option), matching the standard Material
     "compact row" cadence everywhere else.
   - Shortened all 18 locales' THEMES.* labels from "Light theme / Dark
     theme / System theme" to "Light / Dark / System". The "theme" word
     duplicated the section's own h3 ("Visual theme") and forced labels
     to wrap into two lines in narrow columns.

3. Save bar becomes a real sticky footer
   - The action bar was a `border-radius:18px; margin-left:auto;
     position:sticky` chip floating bottom-right that clipped the last
     form rows underneath. Now spans the full content column with a
     top border separator — sits flush with the bottom edge while
     still sticky on scroll. The save-button gradient also drops the
     hand-rolled #3b82f6 in favour of var(--app-selection-color) so it
     matches the unified blue primary used everywhere else.

4. Playlist switcher hidden on /settings
   - Workspace shell header gains an isSettingsRoute input that hides
     the playlist switcher block AND the leading actions group
     (+ Add source / header shortcut / bulk action). Settings is a
     global page — those controls were implying that switching
     playlists scopes settings, which it doesn't. Wired through from
     the shell facade's existing isSettingsRoute computed.
   - Mock header in workspace-shell.component.spec gains the matching
     input() declaration so the binding resolves under tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 01:33:40 +02:00
4gray b5a85fdf5f style: add shared count-badge style and update component usage
Entire-Checkpoint: c2cea9c530e6
2026-04-30 20:32:55 +02:00
4gray 4b2076fa53 feat(settings): add cover size settings and implement responsive cover sizing
Entire-Checkpoint: c2cea9c530e6
2026-04-30 18:56:06 +02:00
4grayandClaude Opus 4.7 2db99769f9 fix(scrollbar): make .app-scrollbar override component-scoped overflow
Two issues kept the scrollbar invisible on the global Favorites and
Recently viewed live-tv lists:

1. The wrong files. The visible Favorites/Recent pages render through
   <app-global-favorites-list> from portal/shared/ui (and the
   workspace-shell variant), not the channel-list-container's tabs.
   Apply .app-scrollbar to both of those components.

2. Specificity. Component CSS gets an Angular [_ngcontent-xxx]
   attribute selector, so `.nav-list { overflow-y: auto }` resolves to
   (0,2,0) — same as the doubled `.app-scrollbar.app-scrollbar` and
   wins on source order (component CSS loads after globals). Mark
   overflow-y: scroll !important on the utility so the contract holds:
   applying .app-scrollbar always reserves the gutter.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 8ae011808369
2026-04-29 01:13:58 +02:00
4grayandClaude Opus 4.7 6c0c04bcba fix(scrollbar): force overflow-y: scroll in .app-scrollbar utility
The favorites and recent views had local #id { overflow-y: auto }
which has higher CSS specificity than the global .app-scrollbar class.
On macOS, overflow-y: auto means overlay scrollbars that auto-fade,
so the gutter and themed thumb were never reserved.

Bake overflow-y: scroll into the utility itself (its whole purpose) and
drop the now-redundant overflow-y: auto from the two local rules.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: b2d2124cd992
2026-04-29 01:06:56 +02:00
4grayandClaude Opus 4.7 59beb324b4 refactor(styles): extract .app-scrollbar utility, apply to channel lists
Move the always-visible themed scrollbar from epg-list into a global
.app-scrollbar utility class in styles.scss. Apply it to the All
channels and Groups view virtual-scroll viewports so the channel list
gets the same persistent gutter and themed thumb as the EPG list.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: c470abf78379
2026-04-29 01:00:42 +02:00
4gray 40d2611ef3 style: add settings theme styles and refactor settings component styles
Entire-Checkpoint: c6e522b4276c
2026-04-19 23:39:49 +02:00
4gray e0f6b34aa0 style: update form field styles and dialog components
- Set default appearance for mat-form-field to 'outline' and dynamic subscript sizing in app.config.ts.
- Adjust dialog dimensions in workspace-shell-actions.service.ts for better responsiveness.
- Refactor settings.component.scss to remove specific mat-form-field font size overrides.
- Enhance global styles in m3-theme.scss for outlined inputs, ensuring consistency across the application.
- Modify workspace-command-palette styles to improve visual integration with the overall theme.
- Revamp add-playlist-dialog component to utilize segmented controls for category selection, improving UX.
- Update file-upload component to provide better feedback on file selection and drag-and-drop interactions.
- Clean up text and URL upload components by removing unnecessary placeholders and improving layout.
- Refine stalker-portal-import component by removing placeholder text for clarity.

Entire-Checkpoint: c6e522b4276c
2026-04-17 23:20:17 +02:00
4gray 5fdd999c7e style: enhance settings UI with responsive action bar and snackbar adjustments 2026-04-06 17:56:50 +02:00
4gray a17ed73806 feat(fonts): replace Google Fonts with bundled font imports and update typography settings 2026-03-28 15:45:06 +01:00
4gray 0ad1eeb785 refactor: update nx monorepo structure 2026-03-09 20:28:31 +01:00
4gray 129848c4d4 feat: remove home component, and update workspace UI and routing 2026-03-02 22:31:10 +01:00
4gray 6cae7ade76 style(ui): enhance workspace UI with new styles and layout adjustments 2026-02-24 21:15:34 +01:00
4gray 349fbcea47 style(theme): enhance M3 theme styles and integrate Tailwind CSS configuration 2026-02-24 19:10:02 +01:00
4gray d6f5561e6b feat(header): add global search and recent buttons with functionality 2026-02-16 22:49:09 +01:00
4gray f95ac9c34e refactor: refactor existing vod-details component and implement VOD details route component
- Added `VodDetailsRouteComponent` to handle VOD details with route parameters.
- Refactored existing `VodDetailsComponent` to use a unified structure for Xtream and Stalker portals.
- Enhanced VOD details display with normalized metadata and improved playback position handling.
- Integrated resizable sidebar functionality in main container and live stream layout components.
- Updated routing to use the new VOD details route component.
- Improved favorite toggling and download handling in VOD details.
- Adjusted styles for responsive sidebar and grid layouts.
2026-01-30 20:39:18 +01:00
4gray 61eef5e00d feat(stalker): feat: add auth for stalker portal, expend portal import UI
Refactor stalker-series-view template to guard fields with conditionals
so that only available metadata is displayed. Replace selectedItem() with
displayItem() to use the updated selection accessor. Add checks for
director, actors, year/release date, genre/genres_str, imdb and kinopoisk
ratings to avoid showing empty labels.

Enhance stalker-portal-import component by adding reactive form fields
for serial/device IDs and signatures, importing MatProgressSpinnerModule,
and wiring TranslateService and StalkerSessionService into the component.
Include a small loading-container style. These changes prepare the UI for
improved import UX and safer rendering of optional series metadata.
2026-01-04 19:07:37 +01:00
4gray a5685ef27d feat(ui): re-enable input selection and add context menu
Re-enable user selection for input and textarea elements so users
can select and copy text in form fields. Add an Electron context
menu handler that shows a focused edit menu only for recognized
text input types or editable fields. The menu includes Cut, Copy,
Paste and Select All and respects the editFlags to enable actions.

This restores native editing UX in the renderer while avoiding
global context menus elsewhere in the app.
2025-12-14 17:11:12 +01:00
4gray ccc50573d4 feat(ui): add app-region drag/no-drag and tweak styles
Add app-region: drag to container and header elements and app-region:
no-drag to interactive controls multiple components so the
Tauri window dragging behaves correctly while keeping buttons and form
controls clickable. Expand header search max width from 500px to 800px
to give more room on larger screens. Clean up and restyle video-player
toolbar toolbar primary colors and unify toolbar spacing.

Files changed include navigation, sidebar, header, main containers,
live-stream layout, navigation-bar, stalker container, and video-player
toolbar. These changes improve window drag UX on desktop and refine
toolbar/header appearance.
2025-12-06 21:44:25 +01:00
4gray 9238fc8d86 refactor(header): modernize header styles and remove unused module
Update header component UI and streamline imports.

- Remove MatToolbarModule import and module list entry
  because the toolbar styling is handled by local SCSS and the
  Angular Material toolbar is no longer required.
- Rewrite header SCSS to introduce a .modern-header wrapper with
  consistent sizing, padding, alignment and a subtle bottom border.
- Add layered ambient and depth effects: refined ::before ambient
  gradient (headerAmbience), ::after bottom accent, and adjusted
  keyframes for a gentler motion.
- Refine home-header styles to target .modern-header and tune colors
  and gradients for a cohesive dark theme.
- Rework .brand-block visuals: lighter glass effect, adjusted
  radii, blur, borders, hover transform, and shadowing for a modern
  elevated look.
- Minor cleanup: remove legacy variables and comments; improve
  box-sizing, layout, and pointer-event handling to ensure expected
  interaction regions.

These changes modernize the header's visual design, improve layout
consistency across pages, and remove an unnecessary module import.
2025-12-06 21:44:25 +01:00
4gray e279172dfe fix(ui): correct fonts, semicolons and preload target
- Add missing semicolon in apps/web/src/styles.scss to fix invalid CSS
  that could break style parsing for .scroll-viewport.
- Change preconnect in apps/web/src/index.html from fonts.gstatic.com to
  fonts.googleapis.com and consolidate multiple Google Fonts links into 
  a single stylesheet URL to reduce redundant requests and ensure 
  Material Icons and selected font families load together.
  Remove duplicated @import of Outfit in
  
  libs/ui/components/src/lib/multi-epg/multi-epg-container.component.scss, 
  switch component font-family to Roboto (with fallback) and update 
  button font to match, ensuring consistent typography and avoiding 
  external import duplication across components. ese changes fix a 
  ntax error, optimize font loading, and standardize
typography across the UI.
2025-11-23 18:50:46 +01:00
4gray 99ed5df404 chore: back to electron + nx 2025-10-07 18:02:13 +02:00