Files
iptvnator/libs/workspace/dashboard/feature
4grayandClaude Opus 5.5 7d7d691ce4 fix(ui): skeletons that match the M3U header and dashboard rails (#1887)
* fix(ui): skeletons that match the M3U header and dashboard rails

Loading skeletons now take their boxes from the code that draws the
content, so the content lands where the skeleton was.

- M3U channel list: the loading state's headers, divider, groups rail
  and first row share `styles/_channel-list-layout.scss` with the All
  channels, context and groups views. The All channels list moved down
  by 19px when a playlist loaded; the group header was 79px against the
  real 69px (the panel header's 52px minimum plus the padding that
  `.sidebar .sidebar-header` gives it, now one mixin both include).
  Group rows start at the list's top-left like the virtual-scroll rows.
- Dashboard rails: `lib-dashboard-rail-skeleton` replaces six copies of
  the skeleton markup and takes the rail's layout and aspect ratio
  (channel cards for live favorites, 16:9 for sources, posters with a
  title and a meta line otherwise). Its header, track, cards and text
  line boxes come from `_dashboard-rail-geometry.scss`, which the rail
  itself now uses. The skeleton was 328px against the 337.8px poster
  rail and a 2:3 poster in front of the 83.5px channel cards.
- One shimmer in `libs/ui/styles/_skeleton-shimmer.scss`, mixed from
  `--app-on-surface`: the rail sweep between the widget surfaces was
  1.04:1 on the light theme. The dashboard hero and the detail
  skeletons (through a forward) use it too.
- Electron E2E `skeleton-geometry.e2e.ts` holds the IPC channel behind
  each loading state, compares skeleton and content boxes to 0.5px at
  1280x700 and caps the dashboard layout-shift score at 0.01.

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

* fix(dashboard): keep rail heights when a skeleton stands for a label-less or live rail

Review follow-up (Greptile, local):
- Rails that replace a skeleton (Continue Watching, sources, Xtream
  recently added, TMDB) keep an empty meta line under cards without
  labels (`reserveMetaLine`), as the skeleton always draws one; a
  Continue Watching rail of movies with no known duration came in
  shorter.
- The recent-content skeleton follows the recent rail it waits for:
  posters while Continue Watching is still loading with no cards,
  channel cards otherwise.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 08:16:38 +02:00
..

Workspace Dashboard Feature

This library owns the workspace dashboard rails UI. The dashboard is a read-only surface over existing playlist, recent, favorites, EPG, and Xtream catalog data; it should not introduce Electron IPC, SQLite schema, or route contracts on its own.

Dashboard Surfaces

The dashboard renders a surface only when the matching setting is enabled. Data rails also require the underlying data slice to have at least one item.

  • hero shows the full-width cinematic banner that rotates between the newest unfinished title, a favourite (or recent) channel with a programme on air, and favourite / recently added titles. Contract: docs/architecture/workspace-dashboard.md#cinematic-hero.
  • continueWatching shows recent movies and series the user has not finished, from DashboardDataService.continueWatchingItems(), using cover cards with playback progress when a saved resume position is available. A movie leaves the rail once its position reaches PORTAL_WATCHED_PROGRESS_PERCENT (90%, so the end credits count as watched). A series whose newest episode is watched stays with the episode it continues with (the first one not watched after the one watched last, from the portal's episode list) and leaves once none follows it. Extras (season 0) never change that. Finished titles stay in Global Recent. Contract: docs/architecture/workspace-dashboard.md#data-flow.
  • liveFavorites shows favorited live TV channels from DashboardDataService.globalFavoriteLiveItems() using the channel layout with EPG title, time range, and progress when XMLTV data is available.
  • recentlyWatchedLive shows recently watched live TV channels from DashboardDataService.globalRecentLiveItems() using the same channel layout.
  • favoriteMoviesAndSeries shows favorited movies and series from DashboardDataService.globalFavoriteItems(), excluding live favorites, using cover cards.
  • recentSources shows recently used playlist/source entries.
  • xtreamRecentlyAdded shows recently added Xtream catalog items.
  • tmdbRecommendations shows "Because you watched X" — TMDB recommendations seeded from recently watched movies/series, kept to titles that exist in an imported Xtream library. Needs the TMDB opt-in and the Electron DB worker (hidden in the PWA), and hides itself below five matched cards. Data: DashboardRecommendationsService in workspace/dashboard/data-access.
  • tmdbTrending shows TMDB's weekly trending titles, matched against the imported Xtream libraries. Same TMDB/Electron gating; unmatched cards open the global search prefilled. Data: DashboardTrendingService.

Settings

Per-surface visibility lives on Settings.dashboardRails as a DashboardRailsSettings object. Every surface defaults to enabled. Stored settings are deep-merged with DEFAULT_DASHBOARD_RAILS_SETTINGS by SettingsStore, so existing users and older partial settings keep newly added surfaces visible unless they explicitly turn them off.

The global showDashboard flag remains a top-level Settings property because workspace startup and route guards already depend on that contract. The Settings UI groups showDashboard and the per-surface checkboxes in the Dashboard section. When showDashboard is off, the per-surface checkboxes are disabled because the dashboard route itself is hidden.

Navigation

Rail cards use the navigation state provided by DashboardDataService for the underlying item. Rail "See all" links may also pass router state:

  • live rails open the relevant collection on Live TV.
  • cover rails for movies/series open Global Recent or Global Favorites on Movies when movie items are present, otherwise on Series.

This keeps the global collection pages from defaulting to Live TV when a dashboard rail is clearly about movies or series.