Commit Graph
40 Commits
Author SHA1 Message Date
4grayandClaude Opus 5.5 d24e89a5e1 fix(sources): match a MAC from the URL in any letter case
Only the source's own `macAddress` was compared caselessly, so an M3U
URL carrying `?mac=…` (nested ones too) left the MAC visible in a name
written in another case. Any MAC-shaped secret, and every form of the
source's own MAC, is now compared lower-cased; passwords keep their case.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 03:04:05 +02:00
4grayandClaude Opus 5.5 c6d7b241d3 fix(sources): compare passwords in their own case; only a MAC caselessly
Matching every secret lower-cased hid any name holding the password word
in another case: the Electron E2E source "Stress Xtream Portal" (password
`stress`) showed its host, and the deletion test could not find its row.
A password is case-sensitive, so it is compared as written; the text is
still read fully decoded, so `%3a` escapes count as `:`. A MAC is the
same address in either letter case and is compared lower-cased.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 02:51:31 +02:00
4grayandClaude Opus 5.5 ea3dacb052 fix(sources): hand over only to an idle source; hide names that are a short secret
- Removing the active source could hand over to a neighbour a refresh
  was still clearing, opening it and starting its import early.
  `SourceRemovalService.canTakeOver` (not leaving, not deleting, not held
  in SourceActivityService) now picks the replacement in both lists and
  gates it in the service.
- Secrets under three characters were dropped from the check entirely,
  so a name that was just a two-letter password showed it. Short secrets
  now count as the whole text (trimmed, any case); longer ones still
  match inside a text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 01:45:55 +02:00
4grayandClaude Opus 5.5 bfce3bd04e fix(sources): read a bracketed key as parameters unless it is JSON
The name check returned early for any value starting with `{` or `[`,
so `[password]=…` (not JSON) skipped the parameter check and its
password stayed in the name. It now returns early only when the value
parses as a JSON object or array.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 01:14:45 +02:00
4grayandClaude Opus 5.5 a06dd988be fix(sources): hold Xtream sources through re-import; match secrets in any case
- An Xtream refresh released its hold when navigation ended, while the
  re-import ran on from the route session, so Remove came back during
  the import's writes. The route session now takes the hold synchronously
  on NavigationEnd and keeps it until its sync, and the import it awaits,
  settle; the store also holds the source for every content
  initialization, retries included.
- Secret matching was case-sensitive against encodeURIComponent's
  upper-case escapes, so `%3a` or a lower-case MAC slipped through. Text
  and secrets are now compared lower-cased, the text also fully decoded.
- The Xtream `· username` label is left out when it is, or holds, the
  password.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 01:13:01 +02:00
4grayandClaude Opus 5.5 bb854a9621 fix(sources): drop JSON configs from shown URLs; read their secrets
A nested value such as `?config={"password":"…"}` was neither a URL nor
a parameter list, and the quoted key slipped past the free-text check.

- A nested value that parses as a JSON object or array is dropped from
  the shown URL (JSON can spell a key so no pattern reads it, such as
  `w`); brackets that are not JSON, like `[1080p]`, stay.
- The free-text check accepts a quoted key (`"token": …`).
- The name check parses JSON values and collects what sits under
  sensitive keys, and follows URLs in its other strings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 00:55:15 +02:00
4grayandClaude Opus 5.5 fc879801a1 fix(sources): read an Xtream path both whole-segment and split
Reading the path only as decoded parts misplaced the account when the
user name itself held an encoded slash (`/live/joe%2Fx/hunter2/1.ts`):
the password segment stayed on screen and in logs. `findXtreamAccount`
now scans both readings, each raw segment decoded whole and the decoded
path split on every separator, and drops what either finds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 00:45:01 +02:00
4grayandClaude Opus 5.5 64271ba50f fix(sources): read Xtream paths decoded; keep short decoded forms out
- An Xtream account could hide behind encoded separators
  (`live%2Fjoe%2Fhunter2%2F1.ts` is one raw segment). `findXtreamAccount`
  reads the path as decoded parts, maps them back to raw segments, and is
  the one scanner for the log redactor, the on-screen URL and the name
  check. Account segments go whole, joined ones included.
- The name check required a minimum length only of the raw secret, so a
  decoded form as short as one letter (`%2542` is `B`) hid ordinary
  names. Each form must now be long enough on its own.
- The decoding helpers move to `url-credential-parts.ts`, which keeps the
  redactor under the line limit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 00:31:37 +02:00
4grayandClaude Opus 5.5 3e4cc0185d fix(sources): read keys, markers and secrets through every encoding layer
Each check decoded its input once, so one more encoding layer slipped a
secret past it: `?%2570assword=…` kept its value, and `/%256Cive/…`
kept its account segments, both on screen and in logs.

`decodeFully` (shared logging) undoes up to four percent-encoding layers
and is now the one form every name check reads: the redactor's key
normaliser and Xtream marker check, the display key predicate and path
check, and the name check's secret values. Logs and the Sources lists
gain the fix together.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 00:15:54 +02:00
4grayandClaude Opus 5.5 94faaa3fc4 fix(sources): name check hides values under short account keys
The name check used the log redactor's key list, which leaves out the
short keys `pass`, `pas`, `user` and `usr` that the URL stripper drops.
Both now share `isDisplaySensitiveKey`, so a name holding `?pass=…`
collapses to the host; `user`/`usr` are account names like `username`
and stay shown at the top level, as by design.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:54:51 +02:00
4grayandClaude Opus 5.5 a861ef94ac fix(sources): clean URL-shaped fragments; one registry for busy sources
- A fragment is cleaned whatever its shape: `#https://joe:pw@…` loses its
  userinfo, an unclassifiable secret is dropped, and the name check reads
  a URL in the fragment as well as a parameter list.
- Every refresh holds its source in the root SourceActivityService, which
  deletes and the startup auto-refresh already use: Sources-row refreshes,
  the switcher's M3U refresh and the shared Xtream flow. The switcher no
  longer offers Refresh or Remove for a source the page is refreshing, the
  removal service ignores a held source, a second refresh from either
  entry point is skipped, and the page disables Remove while it is held.
- The long-input timing spec allows 3 s: the inputs take about 0.25 s,
  a backtracking pattern takes minutes, and a loaded runner took 1.6 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:18:58 +02:00
4grayandClaude Opus 5.5 d2b0325138 fix(sources): decode nested URLs before classifying; narrow Xtream paths
- A URL nested in a parameter or path segment is percent-decoded one
  round at a time until it reads as a URL or parameter list, so one
  encoded twice is cleaned too; a value still encoded after the last
  round is dropped. The name check unwraps nested values the same way.
- The two segments after `/live/` and its siblings count as an account
  only when a stream follows them, so a playlist path such as
  `/live/news/list.m3u` keeps its file name in the URL and the title.
- Adding a playlist from the phone drawer closes the drawer first, so
  two focus-trapped modals never stack.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:04:07 +02:00
4grayandClaude Opus 5.5 ea5bca7e96 fix(sources): search URLs nested in path segments for name secrets
A proxy URL can carry a whole source URL, or a token pair, in one path
segment. The display stripper already cleans such segments; the name
check now collects their secrets too, so a name holding them collapses
to the host.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 22:49:03 +02:00
4grayandClaude Opus 5.5 8323314853 fix(sources): case-insensitive Xtream path markers; no removal during a refresh
- Strip and redact the account segments after `/LIVE/`, `/Movie/` or an
  encoded marker such as `/%6Cive/`, as after `/live/`. The display-name
  check also treats an Xtream path password as a URL secret.
- A source cannot be removed while a switcher refresh clears and refills
  it: the removal service ignores the request, the switcher disables
  Remove, and the Sources page shows the refresh as busy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 22:46:45 +02:00
4grayandClaude Opus 5.5 5500cc8dbb fix(sources): linear URL-cleaning patterns; keep the stripper off the startup path
PR #1932 CI:

- CodeQL (polynomial regular expression on uncontrolled data): the
  embedded-URL and keyed-pair patterns in stripUrlCredentials could retry
  from every position of a long run of letters. Matches now start only
  where a name can start (lookbehinds) and every name part is bounded, as
  is the bare-userinfo pattern; a spec times 50,000-character runs. The
  preceding "wip: linear URL-cleaning regexes" commit holds that change.
- Initial bytes ratchet (+135 B over after master's #1928 lowered the
  baseline): stripUrlCredentials sat behind the shared logging barrel,
  which startup code imports, so esbuild placed it in an initial chunk
  although only the lazy Sources page and switcher use it. It now lives
  beside its only consumer in playlist/shared/util source-display, and the
  logging barrel exports the two redaction helpers it needs. Measured
  locally on the merge with master: 1,537,497 bytes, within 1,538,700.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:58:14 +02:00
4grayandClaude Opus 5.5 a48d0b913f fix(sources): keep fragment and nested URL secrets out of source names
Greptile bot (PR review, P1 security): an unnamed import of
https://host.test/list.m3u#api_key=secret123 stored list.m3u#api_key=…
as its title. isUrlDerivedName only knew names ending in a query, and
sourceSecrets only read the top-level query, so the row and the toolbar
showed the secret while the displayed URL hid it.

A name that ends the URL with a query or a fragment is now URL-derived,
and shows the host. The secrets a typed name is checked against now
include sensitive fragment parameters and, as stripUrlCredentials cleans
them, the credentials of URLs and parameter lists nested in parameters
(nested account names included).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 20:27:24 +02:00
4grayandClaude Opus 5.5 80a9cd946a fix(sources): never hand over to a leaving source; browser checks per connection
Local review round 4 (Codex + Greptile):

- Removing the active source handed over to its neighbour even when that
  neighbour's own delete was still running ("Removing…"), activating a
  source about to disappear. replacementAfterRemoval (playlist shared
  util) picks the nearest row that is not leaving, on the Sources page and
  in the switcher; SourceRemovalService also refuses a leaving replacement.
- In the browser build a "Check again" for an account the user then
  edited could finish last and overwrite the new account's status under
  the same playlist ID. Portal statuses are now keyed by playlist and
  connection fingerprint, so the old check lands under the old key.
- The switcher scrolled the highlighted option into view through a
  selector built from the playlist ID, which a restored ID with "." or
  ":" broke (or made throw). It now finds the row by its exact DOM ID.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 18:37:48 +02:00
4grayandClaude Opus 5.5 85e6cb3816 fix(sources): sort by the shown name; start the benchmark's Undo window in its trigger
Local review round 3 (Greptile; Codex was clean):

- Name order compared the stored title, while the rows show a URL-named
  or credential-bearing source by its host: a source stored as
  get.php?username=… sorted by its hidden account details. sortPlaylists
  takes the list's name function, and Sources and the switcher pass
  sourceSortName, the name they display.
- The Xtream delete benchmark clicked Remove while preparing, which starts
  the four-second Undo window; capture setup before the trigger could
  outlast it, so the delete committed on its own before the measurement
  and the close button was gone. Preparation now stops at the open menu,
  and the trigger clicks Remove and then the toast's close button, with
  the trigger time taken right before the commit click as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 18:15:09 +02:00
4grayandClaude Opus 5.5 9feab4f83b feat(switcher): rebuild the playlist switcher as an overlay popover
The switcher's mat-menu becomes a CDK connected overlay
(app-playlist-switcher-panel), 440px wide, with a dimmed, blurred
backdrop; only its list scrolls and row menus open above it.

- Header with the source count, Add playlist and All sources.
- Search by name or host, autofocused; the type filter is one segmented
  control (All / M3U / Xtream / Stalker with counts) instead of three
  checkboxes, remembered between openings (the old checkbox setting is
  migrated). The search is no longer kept between openings.
- "Recent" (the three most recently opened, kept across restarts) above
  "All" in the Sources page order, as compact source rows: the current
  one tinted with a check. Each row's ⋯ menu holds Refresh, Playlist
  info, Account info, Check again and Remove (with Undo); the footer's
  info, account and add actions moved there and to the header.
- Keyboard: a listbox driven by aria-activedescendant from the search
  field. Arrow keys move and wrap, Enter opens, Shift+F10 opens the
  row's menu, Escape closes and returns focus to the trigger. Hints in
  the footer.
- Deleting from a row menu moves focus to the toast's Undo, since the
  row (and its focus) left the page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 22:34:22 +02:00
4grayandClaude Opus 5.5 401295c092 feat(sources): rebuild the Sources page on the shared source row
- Side list (UI-kit side list): title with the playlist count and an Add
  button, a Type group, and on desktop a Status group (Any / Responding /
  Not responding). The two filters combine and persist; each group counts
  within the other's choice. The chosen row uses one selected-row token
  (soft tint, outlined in light theme).
- Page: a 28px title with the visible count instead of the bordered
  header bar; Clean up and the sort menu are secondary buttons. Clean up
  shows how many sources do not respond. Sort adds Last updated, Most
  channels and (desktop) Not responding first, and keeps Name Z-A and
  Custom order.
- List: app-source-row with hover actions (Refresh, Edit, ⋯); a failing
  row keeps Retry in view. "No matches" offers Reset filters. Dragging in
  custom order no longer reuses positions of rows a filter hides.
- Delete with Undo: no confirmation; the row leaves at once and the delete
  runs when the toast closes (its close button commits at once). Deleting
  the active source switches to the next row. A large Xtream delete shows
  its row as "Removing…" with progress and Cancel, as before.
- The list's refresh flows moved into SourceListRefreshService; E2E and
  performance drivers follow the new markup and delete flow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 22:34:22 +02:00
4grayandClaude Opus 5.5 028a3db880 feat(sources): add the shared source row and credential-free formatting
One app-source-row (regular for the Sources page, compact for the
playlist switcher) with an app-source-tile, built on pure helpers:

- resolvePlaylistSourceKind: one provider rule for icons, filters and
  counts (the list filter and the panel counts used to disagree).
- sourceDisplayName / sourceDisplayUrl: a name or URL never shows a
  password, token or MAC. URL imports that stored
  "get.php?username=…&password=…" as the title now show the host.
- stripUrlCredentials (shared/logging): drops what the log redactor
  would mask, plus short user/pass keys.
- formatSourceDate / sourceMetaParts / sourceStatusTag: one meta line
  order and Intl dates and durations in the UI language.
- SourceStatusService + SourceHealthHistoryService: one status per
  source (desktop health check, browser Xtream portal status) and the
  last conclusive result kept across restarts, so "not responding for
  3 days" survives a relaunch. No URL or credential is stored.

Also fixes playlistNameHasCredentials answering differently on repeated
calls (global regex lastIndex), and adds the UI-kit button, menu and
segmented-control mixins (libs/ui/styles/_app-controls.scss); menus opt
in with class="app-menu", styled by the components that use them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 22:34:22 +02:00
650da4a1d3 ci(test): type-check Jest spec programs and gate it in CI (#1705)
* build(test): make spec tsconfigs resolve what Jest resolves

Lib spec tsconfigs used module: commonjs with node10 resolution, which cannot
see Angular's exports-only secondary entry points, and dropped global.d.ts, so
tsc reported thousands of resolution errors and no window.electron typing.
Switch them to module: preserve with bundler resolution (ts-jest still forces
CommonJS emit outside ESM mode), add global.d.ts to every spec program, type
jest.unstable_mockModule for the ESM workspace, include the ui-epg and
ui-playback specs that jest.web-esm.workspace.ts runs under the web spec
config, and drop the snack-bar stub that shadowed the real Material types.

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

* ci(test): gate spec type-checking with typecheck:spec

Add tools/typecheck/spec-typecheck.mjs, which runs tsc --noEmit over every
tsconfig.spec.json with a small pool and fails on any diagnostic, wire it into
the unit-and-typecheck job after typecheck:ci, and document the gate and the
spec tsconfig conventions in the validation map. Also bring the non-Tier-A
spec configs (remote-control-web, ui-remote-control, stalker-mock-server) to
the same conventions so the gate covers the whole workspace.

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

* test: fix the spec type errors surfaced by typecheck:spec

With the spec programs resolving modules and ambient typings correctly,
tsc reported 432 genuine errors across the Tier A projects: read-only
capability flags assigned on Partial<> doubles, signal-store values used as
types, fixtures missing required fields, index-signature property access,
partial bridge doubles cast through incompatible shapes, and deferred
resolvers narrowed to never. Type the doubles instead of casting to any:
writable mapped types for capability flags, InstanceType<typeof StalkerStore>,
typed jest.fn signatures, protectedState: false on test signal stores, and
completed fixtures. Production changes are limited to bracket access for
index-signature properties under the libs' noPropertyAccessFromIndexSignature
setting and two narrowing guards in the global favorites loader.

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

* test(playback): use the ESM setup's jest global in the controls fixtures

The fixture imported jest from @jest/globals, which is not a direct
dependency. Jest provides that module at runtime, so tests passed, but on a
clean pnpm install tsc cannot resolve it and typecheck:spec failed in CI.
The ESM test setup already installs import.meta.jest as the global, typed
by @types/jest, as the other ESM specs use it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test: type the parental lock doubles merged since the gate was written

The parental lock feature (#1601) and the Stalker actor route landed on master
with spec doubles declared as zero-argument jest.fn()s that the tests then
drive with the real arguments, plus a copy of the ResizableDirective override
imported from a library that does not export it. Give the doubles the lock
service's real signatures, drop the dead override as in the sibling layout
specs, use bracket access for the actor route's personId param, and keep the
Stalker layout spec within the 1200-line limit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: 4gray <fourgray@proton.me>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-27 20:54:27 +02:00
4grayandClaude Fable 5.1 8ebb7e3424 perf(ci): run Tier A coverage concurrently with isolatedModules ts-jest (#1701)
Tier A coverage runs projects a few at a time (largest first, bounded Jest workers, buffered output, fail-fast kept) and ts-jest transpiles with isolatedModules instead of type-checking per process; five type re-exports become export type, two decorated inputs use import type. Unit Tests and Typechecks job: 26 min -> 9 min (Tier A step 23 min -> 6.5 min).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 23:05:26 +02:00
4gray e9eca1c386 chore(deps): upgrade Angular to 22.1 and Nx to 23.2 (#1603)
* chore(deps): upgrade Angular to 22.1 and Nx to 23.2

* fix(deps): complete Angular migrations after rebasing on master

* fix(ci): use the Node pin for Windows runtime refresh

* docs(deps): synchronize the workspace-shell Node requirements
2026-09-14 19:02:40 +02:00
4grayandClaude Fable 5 1a6af75761 feat(settings): per-section pages with unsaved-changes bar (#1384)
* feat(settings): split settings into per-section pages with an unsaved-changes bar

Replace the single scrolling settings page with routed section pages
(/workspace/settings/:section): the context-panel rail links each section,
only the active section renders, and unknown or capability-gated sections
redirect to General. The shared form lives on the parent component, so
staged edits survive section switches; a floating unsaved-changes bar
(Save/Discard) replaces the always-visible footer Save button. Rail links
navigate with replaceUrl so Back still leaves settings in one step.

Along the way:
- delete the unreachable settings dialog mode and the dead
  AppPortalNavigationActionsService with both of its never-injected DI
  tokens (PORTAL_NAVIGATION_ACTIONS, PLAYLIST_PLAYER_ACTIONS)
- delete the scroll-spy directive and pendingScrollTarget plumbing
- revive the EPG panel's "Open EPG settings" empty-state button as a deep
  link to /workspace/settings/epg; the M3U player now reports
  m3u-needs-setup only when the channel has no programmes and no EPG
  source exists in settings or on the playlist itself
- load TMDB cache stats when the Metadata page opens (the section
  component now only exists while its page is open)
- add SETTINGS.UNSAVED_CHANGES / SETTINGS.DISCARD_CHANGES to all 19 locales

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

* feat(settings): confirm before leaving with unsaved changes

Add settingsUnsavedChangesGuard (canDeactivate on the :section route) with
a three-action dialog: save and leave, leave without saving, keep editing.
The guard only intercepts leaving the settings AREA — section switches
share the one settings form and pass unconditionally, so the dialog can
never nag while moving between pages. A failed save cancels the navigation
instead of silently dropping the edits it promised to keep; leaving
without saving also reverts the live theme preview. Save-and-leave is
disabled while the form is invalid, with a hint explaining why.

New SETTINGS.UNSAVED_DIALOG_* keys in all 19 locales.

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

* fix(settings): stage cover size and EPG view mode; adapt e2e to section pages

Cover size and EPG view mode were the only two controls that persisted
eagerly on click, which made Discard (and leave-without-saving) unable to
revert them: hydrateFromStore() faithfully reloaded the just-persisted
edit. They now stage in the form like every other setting and reach the
store on Save. Review finding by Greptile (P1) and Codex.

E2E suites that walk through settings are updated for one-section-page
rendering (epg, backup-roundtrip, xtream-epg, remote-control) and for the
staged cover size (downloads asserts the dataset after Save); the EPG icon
fallback test saves before leaving settings so the new unsaved-changes
dialog does not block its navigation.

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-09 09:34:03 +02:00
4grayandClaude Opus 5 f80eb4d1b9 fix(playlist): open playlists handed over by the OS (#1299)
Opening an .m3u/.m3u8 file from the command line or a file association did
nothing. The renderer parsed `process.argv` and sent an `OPEN_FILE` IPC event
that had no `ipcMain` handler and no preload channel, so `sendIpcEvent` logged
it as an unknown type and dropped it.

The path now belongs to the main process, which is where the OS actually
delivers it:

- argv is parsed on first launch (skipping the executable and Chromium
  switches) and normalized to an absolute path;
- macOS gets an `open-file` listener registered before `whenReady`, since
  Launch Services never puts the path in argv;
- the single-instance guard forwards a second launch's argv and working
  directory instead of discarding them, so opening a playlist against a
  running app works too.

Requests are queued in the main process until the renderer subscribes to the
`OPEN_FILE` push and drains the queue, which closes the startup race. The
import itself reuses the existing file path, so persistence, playlist-scoped
EPG and the navigation to the new playlist behave exactly like a dialog
import; a failed open now surfaces a snackbar instead of silence.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 20:28:29 +02:00
4gray 61cfe64951 refactor(playlist): use runtime capability for xtream sections 2026-05-22 12:35:43 +03:00
4gray 820fbef121 refactor(playlists): move refresh action to ui boundary 2026-05-22 11:27:04 +03:00
4gray d24c77a143 chore(nx): enforce scoped workspace boundaries (#942) 2026-05-15 09:59:06 +02:00
4gray 2efdfea0c1 fix: preserve file paths for local playlist refresh
Refs https://github.com/4gray/iptvnator/issues/891

Entire-Checkpoint: f957cd9849e0
2026-05-04 10:06:39 +02:00
4gray d0f590d8fe refactor: improve Xtream refresh functionality with localization support
- Introduced new localization keys for Xtream refresh actions in multiple languages (ar, ary, by, de, el, en, es, fr, it, ja, ko, nl, pl, pt, ru, tr, zh, zhtw).
- Implemented refresh preparation state management in PlaylistRefreshActionService.
- Enhanced WorkspaceShellXtreamImportService to handle refresh preparation states and display appropriate labels.
- Updated tests to cover new refresh preparation scenarios and ensure correct overlay display during refresh operations.

Entire-Checkpoint: f957cd9849e0
2026-05-04 00:51:29 +02:00
4gray a6e886116c style(dialog): increase dialog width from 300px to 400px for better visibility
Entire-Checkpoint: c2cea9c530e6
2026-04-30 13:19:38 +02:00
4gray b1d519183f style(empty-state): enhance empty state component with new types and responsive design
- Expanded EmptyStateType to include 'welcome-dashboard', 'welcome-sources', 'no-data'.
- Introduced FeatureCard and SourceCard interfaces for better structure.
- Added FEATURE_CARDS and SOURCE_CARDS constants for feature and source card configurations.
- Updated empty-state.component.ts to handle new types and actions.
- Created responsive styles in empty-state.responsive.scss for better mobile support.
- Added theme styles in empty-state.themes.scss for light mode.
- Developed specific styles for welcome dashboard and sources in empty-state.welcome-dashboard.scss and empty-state.welcome-sources.scss.
- Updated recent-playlists.component.html to utilize new empty state types.
- Modified recent-playlists.component.ts to support new playlist actions.
- Introduced PlaylistFileImportService for handling playlist file imports.
- Updated downloads.component.html and downloads.component.ts to integrate new empty state handling.
- Enhanced unified-collection-page.component.html and unified-collection-page.component.ts to utilize empty state for no results.

Entire-Checkpoint: c6e522b4276c
2026-04-25 22:18:13 +02:00
4gray c8c93798f9 feat: enhance playlist backup export functionality and UI feedback
Entire-Checkpoint: c6e522b4276c
2026-04-21 08:54:03 +02:00
4gray 0f72efb749 refactor(m3u): add loading state for channel list and improve playlist handling
- Implemented a loading state for channel lists in various components, enhancing user experience during data fetching.
- Added `ChannelListLoadingStateComponent` to display loading skeletons for channels and groups.
- Updated `M3uWorkspaceRouteSession` to manage loading states more effectively when fetching playlists.
- Modified `VideoPlayerComponent`, `SidebarComponent`, and `ChannelListContainerComponent` to utilize the new loading state.
- Enhanced tests to cover new loading behaviors and ensure proper dispatching of loading actions.
2026-04-11 13:20:22 +02:00
4gray 4e74ac2546 feat: enhance M3U collection route component with playlist ID handling and add tests for playlist switcher and context facade 2026-04-06 12:57:17 +02:00
4gray 02c773170e feat: add Jest configuration and tests for playlist shared utility 2026-04-04 20:41:07 +02:00
4gray 91cd650c1a feat: enhance EPG and portal features with error suppression and status resolution
- Added suppressErrorLog option to EPG API calls in withEpg feature.
- Introduced resolvePortalStatus function to streamline portal status checks in withPortal feature.
- Updated checkPortalStatus to return PortalStatusType instead of void.
- Enhanced XtreamStore to preserve cancelled content initialization block.
- Expanded XtreamState to include contentInitBlockReason for better state management.
- Added XtreamContentGateComponent to manage content initialization states.
- Implemented tests for XtreamContentGateComponent to ensure correct rendering based on contentInitBlockReason.
- Created PlaylistRefreshService to handle playlist refresh operations and events.
- Introduced new IPC commands for playlist refresh functionality.
- Added interfaces for playlist refresh events and payloads to improve type safety.
2026-04-04 13:40:34 +02:00
4gray 6c9a40d831 refactor: migrate from NgRx store to PlaylistContextFacade for playlist management
- Updated PlaylistErrorViewComponent to use PlaylistContextFacade for accessing the current playlist.
- Refactored StalkerSearchComponent to utilize PlaylistContextFacade for retrieving the active playlist.
- Modified StalkerWorkspaceRouteSession to sync route context using PlaylistContextFacade.
- Adjusted XtreamStore to remove NgRx store dependencies and use PlaylistContextFacade for current playlist access.
- Updated ChannelListContainerComponent to resolve playlist ID using PlaylistContextFacade.
- Refactored SidebarComponent and ToolbarComponent to eliminate NgRx store usage for playlist-related data.
- Enhanced unit tests in with-content.feature to ensure proper loading and cancellation of content imports.
2026-03-28 21:43:33 +01:00
4gray 7ac2fd66b8 refactor: update nx monorepo structure 2026-03-09 23:24:34 +01:00