* feat(settings): search settings from the header and the command palette
The header search on the Settings page was shown but disabled. It now
searches a shared index of all 56 settings rows by translated title,
description and English synonyms, replaces the section page with ranked
results, and opens a result by scrolling to, focusing and briefly
highlighting its row. Enter opens the best match, and the section
navigation shows per-section match counts.
The command palette gains a "Settings" group that lists the best six
matches for a non-empty query, so any setting is one Ctrl/Cmd+K away.
Rows hidden by the current form state fall back to the control that
reveals them; rows the runtime cannot render are never returned.
The index ships through a new @iptvnator/workspace/shell/util/settings-search
sub-entrypoint so it stays out of the eager bundle, and a registry spec
keeps it in step with the section templates.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(settings): let search reveals win over pending input and gate embedded MPV rows
- A reveal (result click, command palette, Enter) now cancels a search
keystroke still waiting for its debounce, so its q navigation can no
longer supersede the reveal and leave the results open.
- Embedded MPV extra options and auto-reconnect require a lazily probed
embedded MPV capability; frame copy also needs frameCopyAvailable, so
search never offers a row the settings page cannot render.
- Keyboard users keep a focus-visible ring on the revealed row after the
highlight fades.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(workspace): wait for palette probes without Promise.allSettled
The web tsconfig lib predates Promise.allSettled; use Promise.all over
rejection-safe probes instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: 4gray <fourgray@proton.me>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* feat(epg): accept local XMLTV files as EPG sources
Settings → EPG and the playlist dialog accepted `file://` in their form
pattern, but the main process rejected everything except http(s), so a local
XMLTV entry saved fine and then failed on import. Both surfaces now take a
remote link, a `file:` URL, an absolute POSIX path or a Windows drive/UNC
path (`classifyEpgSourceReference` in shared/interfaces), and the settings
section spells out the accepted formats with examples.
The EPG worker opens every source through `openEpgSourceStream`: remote
links keep the validated-redirect client and trust policy, local files are
read from disk behind the signature-sniffing optional gunzip stage, so
.xml, .xml.gz and extension-less gzip all parse. Only hand-typed sources
may be local: `extractM3uEpgUrls` harvests http(s) links only from M3U
headers, since the local branch bypasses `validateRemoteUrl`.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* feat(epg): pick local XMLTV files with a native file dialog
A folder button beside each EPG source row (Settings → EPG and the playlist
dialog) opens the native open-file dialog and writes the chosen absolute
path into the row. New `EPG_OPEN_FILE_DIALOG` IPC behind
`ElectronBridgeApi.openEpgFileDialog`, gated in the renderer by
`RuntimeCapabilitiesService.supportsEpgFilePicker`.
The row's refresh/remove buttons carry `data-test-id`s now, and the EPG
e2e suites address them by id instead of index, since the folder button
became the first button in a row.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(epg): authorize local XMLTV files in the main process
Review follow-up (Greptile P1, Codex P1). The renderer hands source strings
to FETCH_EPG/EPG_FORCE_FETCH unchanged, so the form validator alone could
not enforce the provenance rule: a compromised renderer, or a legacy
`file://` entry an older version stored from an M3U header, could name any
file on disk.
`EpgWorkerService.startFetch` now asks a main-process
`EpgLocalSourceAuthorizer` before a local path reaches the worker: a path
the native picker returned is trusted at once, a hand-typed path is
confirmed once in a native message box the renderer cannot fake, and a
refusal is reported in the progress panel. Allowed paths persist under
TRUSTED_LOCAL_EPG_SOURCES in the main-process config. The worker opens its
local branch only when main set `allowLocalFile`; the service defaults to
deny-all until epg.events installs the persisted authorizer.
`resolvePlaylistEpgSourceState` and `filterPlaylistEpgUrlsForFetch` drop a
stored non-remote entry unless it is also in `manualEpgUrls`.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(epg): fail a refused local EPG fetch instead of resolving it
Review follow-up (Codex P2). A denied native confirmation now rejects the
fetch after reporting the error row, so handleFetchEpg and the renderer's
fetch result cannot claim the file was read. Also restores the unrelated
CLAUDE.md paragraph an earlier formatter pass had reflowed into a list.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(epg): cancel a local source retired during its authorization prompt
Review follow-up (Codex P2). startFetch keeps the request generation
captured before awaiting the native confirmation and rechecks it
afterwards: a source retired meanwhile ends as cancelled instead of
starting an import that a pending clear would then have to await.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* test(epg): leave the local XMLTV e2e with a pristine settings form
The local-file test ended with the EPG source field still dirty, which
arms the main-process close guard: the app then waited for the unsaved
changes dialog instead of closing, the close timeout killed it, and on
Windows the killed process kept iptvnator.db busy (EBUSY on the data-dir
cleanup) and hung the Playwright worker teardown. Discarding the form
before the app closes takes the test from 15 s to 4 s locally.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: 4gray <fourgray@proton.me>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* 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>
* fix(ui): make the workspace usable on phone-sized screens
The shell was half-adapted below 640px: the rail flipped to a horizontal
bar but the link lists inside it kept stacking downwards, so the navigation
was drawn outside the bar and over the header (#1100).
Three resizable rails — the shell context panel, the live-layout channel
sidebar and the M3U channel drawer — kept their persisted desktop width,
which left the content around 50px on a 375px screen. They now span the
full width and stack above the content. The inline width written by
ResizableDirective is why these rules need `!important`.
Found while walking the rest of the UI at 375px and 768px:
- The detail hero kept poster and details side by side, squeezing the
action row below its own labels until "Play" was clipped to its icon.
- The settings section list did not scroll and painted over the footer,
which also affected short desktop windows.
- Hiding the M3U channel list on a phone was one-way: the restore handle
was hidden and only Cmd/Ctrl+B could bring it back.
- The live header drew the channel count and the paginator on top of each
other up to tablet width, because the paginator does not shrink and the
meta collapsed to zero width and overflowed its box.
- The search scope checkbox was pushed off the right edge.
Live TV states a floor for the player instead of a ceiling for the lists,
so the video keeps a usable share of the screen under the categories panel
and the channel list.
Closes#1100
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(ui): address review — keep the palette reachable and the video visible
Two findings from the Codex review on #1326.
Hiding the command-palette trigger on phones removed the only pointer-driven
way to open it: the rail renders route links plus Settings and emits nothing,
so `commandPaletteRequested` had exactly one source. The button stays and its
keyboard-shortcut label is swapped for an icon instead. Doing that exposed a
latent flex trap in the same row — an <input> keeps an intrinsic min-width
from its `size`, and `min-width: auto` honours it, so the field refused to
shrink and pushed the trigger out onto the buttons beside it.
The M3U drawer released the shared player floor, which on a short landscape
phone (600-640px wide) left the content container at half the shell body.
The inline guide inside it is `flex: 0 0 <basis>` and took its full 180px out
of a container that no longer had it, so the video could reach zero height.
The floor is restored and now yields on short viewports, the video states its
own minimum, and the guide is what gives way.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(ui): let the channel list keep its height on a landscape phone
Follow-up to the review: the player floor added in the previous commit was
measured against the viewport, not against what the shell had left. On a
640x360 landscape phone the stacked categories panel already takes 30vh, so
claiming another 50vh here drove the channel sidebar to zero height while it
was still marked expanded — no way to pick another channel — and pushed the
layout past the viewport.
The floor now applies only where the screen can afford it (`min-height:
600px`), the sidebar states a floor of its own so it cannot be squeezed out,
and the collapsed rule clears that floor so hiding the list still works.
Below that height the two panes simply share what is left.
Portrait is unchanged: categories 244px, channel list 220px, player 240px on
a 375x812 screen.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(ui): address review — settings nav on landscape, poster dead space
Two more findings from the Codex review.
The stacked settings context panel capped itself at 30vh, which on a 360px
screen is 108px — less than the panel's own title and footer, so the seven
section rows collapsed to nothing behind an overlapping footer. On short
screens the caption gives way (the rail already labels the page), the footer
sheds its tall-screen padding, and the settings variant gets a slightly
larger cap: unlike the live routes there is no player below competing for
height, only a scrollable form.
The poster kept a 330px minimum from the skeleton fallback at the bottom of
the file — sized for the 220px desktop poster — while the stacked phone hero
renders it 140px wide with a ~210px aspect-ratio height. Every loaded detail
page carried ~120px of empty space between the poster and the title. The
override sits after that rule because it wins on source order, not
specificity.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(ui): let the playlist switcher yield to the search field on narrow phones
Codex review of d6133da9: on a 320px header a route that contributes its
shortcut button left the search field less than its own chrome needs (~74px
of icon, palette trigger, gaps and padding), so the field's contents spilled
onto the buttons beside it.
The switcher is the one header region whose content can ellipsize, so it is
what shrinks — down to an 88px floor — while the field states its chrome as
a minimum. The field's basis moves from auto to zero so the input's intrinsic
size stops counting as content: with basis auto the field claimed its
intrinsic width even when room was ample and squeezed the switcher to ~115px
on a 375px screen that could fit all 140.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(ui): fit the switcher's own chrome inside its phone floor
Follow-up to the Codex note that the trigger's fixed chrome (type icon,
refresh, chevron, gaps, padding) exceeds the 88px floor the shell now allows
the switcher to shrink to. The flagged scenario itself cannot occur — the
Multi-EPG shortcut needs Electron bridge methods the PWA lacks, and Electron
enforces a 900px minimum window width so it never sees the phone breakpoint —
but the floor should hold on its own terms rather than by accident of which
buttons happen to render. Dropping the decorative type icon on phones brings
the fixed chrome under the floor, and the name gets the space instead.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The earlier Settings polish (kill triple header, compact theme picker,
sticky footer, hide playlist switcher) addressed structure. This round
addresses typography and chrome to bring the look in line with the
v0.22 mockup spec captured in redesign-screens-2.jsx.
Three changes:
1. Flat sections — no card chrome
- Each .settings-group was a rounded panel (border-radius:22px,
filled background, box-shadow, .settings-group--active ring +
glow). Combined with the per-section icon-circle next to the
title, every section looked like a feature card, not a settings
region anchor. Strip all of it: the rail's active state on the
left already announces "you are here".
- Hide .settings-group__header-icon via display:none so the icon-
circle markup in each section template stays intact while the
visual chrome goes away — saves touching 6 templates for a
CSS-only change.
2. Promote section titles to a large flat heading
- Was h3 1.08rem 700 weight nested in the card chrome. Now h3
1.5rem (24px) 600 weight with a 1px bottom-border separator,
matching the mockup's 28px h1 + sub anchor pattern (24px is a
reasonable density compromise for the denser app layout). Active-
section variant keeps the blue title accent so users scrolling
the right pane don't lose the anchor.
- .setting-item drops the `margin: 0 18px` card inset so rows
align flush with the section title — flat list rhythm.
3. Quiet mono eyebrow for the left nav title
- .panel-title was a 20px 500 weight h2 ("Settings") competing for
"biggest text on screen" against the section titles on the right.
Switch to mono 11px uppercase 0.1em letter-spaced text-secondary
— quiet rail header per the mockup. Now the section titles on
the right are unambiguously the dominant heading.
Visual confirmation via agent-browser shows a much closer match to the
mockup: subtle SETTINGS eyebrow above the rail list, large flat
"General" anchor with bottom border, compact pickers, full-width sticky
footer.
50/50 settings tests still pass; build clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
- Updated confirmation dialog messages to provide detailed consequences of deleting playlists.
- Added backup hints and progress messages for better user experience during playlist deletion.
- Ensured consistency in language across all supported translations (de, el, en, es, fr, it, ja, ko, nl, pl, pt, ru, tr, zh, zhtw).
Entire-Checkpoint: c6e522b4276c
- 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
feat: Update category view to improve empty state representation
style: Refactor category view styles for better layout and responsiveness
fix: Adjust favorites component navigation logic for better routing
refactor: Improve playlist error view structure and styles for clarity
style: Revamp playlist error view styles for consistency and readability
feat: Update portal channels list to use new channel list item component
style: Clean up portal channels list styles for improved layout
refactor: Optimize portal channels list component logic and structure
feat: Implement recently viewed component navigation improvements
fix: Update global search results component for better routing
test: Add unit tests for search results component functionality
feat: Enhance search results component to handle initial query
fix: Update EPG queue service to manage preview limits effectively
style: Clean up nav list styles for better visual consistency
- Updated GridListComponent to use a skeleton loading UI during data fetching.
- Introduced GridListItem interface for better type safety on items input.
- Improved paginator integration with proper type definitions.
- Refactored loading state handling to utilize computed properties.
feat: Improve portal rail links with accessibility features
- Added aria-current attribute to indicate active links.
- Enhanced styling for active links with new CSS variables for selection colors.
style: Refactor styles for portal rail links and context panels
- Updated SCSS for portal rail links to improve hover and active states.
- Introduced new styles for context skeletons in workspace context panel.
feat: Implement skeleton loaders in stalker main container and workspace context panel
- Replaced ngx-skeleton-loader with custom skeleton components for better control.
- Added skeleton loading states for categories in stalker main container.
style: Refine recently added component styles and interactions
- Enhanced section styling with active state indicators and transitions.
- Improved layout and visual feedback for recently added items.
fix: Correctly handle section activation in recently added component
- Added logic to determine active sections based on selected content type.
refactor: Update XtreamShellComponent to improve import overlay handling
- Changed isImporting check to showImportOverlay for better clarity.
- Refactored section handling to use signals for state management.
style: Introduce new selection colors and effects across the application
- Added new CSS variables for selection colors and effects to enhance UI consistency.
- Updated various components to utilize new selection styles for better user experience.
- Refactored settings component HTML structure for improved layout and accessibility.
- Introduced a sidebar navigation for settings sections with smooth scrolling.
- Added dynamic visibility for settings sections based on the desktop environment.
- Enhanced form controls with better styling and responsiveness.
- Updated settings form validation and error handling for remote control port.
- Improved overall styling with new SCSS variables and layout adjustments.
- Adjusted dialog dimensions in navigation components for better user experience.
Reduce the settings page container height calculation from
calc(100vh - 100px) to calc(100vh - 70px) so the content fits the
visible viewport when the header height is 70px. This prevents
unnecessary vertical overflow and avoids extra whitespace at the
bottom of the settings page.
Add support for displaying local remote-control URLs and QR code in the
Settings UI, including styles for URL list, row layout, monospaced links,
and a QR container. Expose a new global API signature getLocalIpAddresses
for retrieving local IPs.
Fix several UI issues:
- Correct typo in header method name (opedAddPlaylistDialog -> openAddPlaylistDialog).
- Fix malformed GitHub URL and collapse img tag formatting.
- Reformat imports and viewChild usage for readability.
Change local development backend URL to localhost:3000 in the local
environment to point to the running local API during development.
These changes improve developer ergonomics (local backend), add a remote
control discovery UX for users on the same network, and tidy up header
bugs and formatting.