9.8 KiB
Workspace UI Refactor Summary
Date: 2026-02-22
1) What Was Refactored
The app moved from a setup-first flow (playlist management first, then provider-specific UI) to a workspace shell flow with a persistent app frame:
- Fixed left navigation rail (global + context links).
- Persistent top header (playlist switcher, search, actions, favorites shortcut, sorting where relevant).
- Secondary left panel for:
- Sources filters on
/workspace/sources. - Provider category context for Xtream/Stalker content sections.
- Sources filters on
- Unified workspace routing with dashboard and sources as first-class views.
Core entrypoint:
apps/web/src/app/workspace/workspace-shell.component.tsapps/web/src/app/workspace/workspace-shell.component.html
Shared rail link model:
apps/web/src/app/shared/navigation/portal-rail-links.tsapps/web/src/app/shared/navigation/portal-rail-links.component.ts
2) New Dashboard (Current State)
The dashboard is now a real widget host with persisted layout and customization.
Key parts:
- Widget model and defaults:
libs/workspace/dashboard/data-access/src/lib/dashboard-widget.model.ts
- Persisted layout store with migration support:
libs/workspace/dashboard/data-access/src/lib/dashboard-layout.service.ts
- Widget host:
libs/workspace/dashboard/ui/src/lib/dashboard-widget-host.component.tslibs/workspace/dashboard/ui/src/lib/dashboard-widget-host.component.html
- Dashboard page (view + customize mode):
libs/workspace/dashboard/feature/src/lib/workspace-dashboard.component.tslibs/workspace/dashboard/feature/src/lib/workspace-dashboard.component.html
Active widget set:
- Recent Sources
- Source Statistics
- Continue Watching
- Recently Watched (global, scope-aware)
- Global Favorites (global, scope-aware)
Notes:
- Widget scope settings (provider + playlist filtering) are implemented for scoped widgets.
2.1) Dashboard Idea and Widget Implementation
Product idea
The dashboard is designed as a content-first operational home, not a source setup page.
Goals:
- Show high-value content immediately after app start.
- Reduce context switching between Xtream, Stalker, and M3U flows.
- Keep widgets independent so one data failure does not break the page.
- Allow gradual customization without introducing heavy layout complexity too early.
Design principles:
- Widgets are about user tasks (continue watching, favorites, recent activity), not provider internals.
- Each widget must have explicit loading/empty/error states.
- Widget settings should be persistent and reversible.
- Navigation from widget cards should deep-link directly to the right route and content context.
Implementation shape
Core implementation files:
- Dashboard page and orchestration:
libs/workspace/dashboard/feature/src/lib/workspace-dashboard.component.tslibs/workspace/dashboard/feature/src/lib/workspace-dashboard.component.html
- Widget metadata contract:
libs/workspace/dashboard/data-access/src/lib/dashboard-widget.model.ts
- Layout + settings persistence:
libs/workspace/dashboard/data-access/src/lib/dashboard-layout.service.ts
- Widget rendering host:
libs/workspace/dashboard/ui/src/lib/dashboard-widget-host.component.ts
- Data aggregation and widget inputs:
libs/workspace/dashboard/data-access/src/lib/dashboard-data.service.ts
- Shared activity item renderer (used by multiple widgets):
libs/workspace/dashboard/ui/src/lib/widgets/dashboard-activity-items.component.ts
How widget rendering works
workspace-dashboardreads layout state (widget list, order, enabled state, settings).- For each enabled widget,
dashboard-widget-hostmapswidget.typeto a concrete widget component. - The host passes widget config (
scope,settings, mode flags) into the widget. - Widget requests data through
dashboard-data.serviceand/or existing provider services/stores. - Widget emits navigation actions, which are translated into direct route deep-links.
How persistence works
Layout is stored as a versioned dashboard configuration:
- Widget identity and type
- Visibility (
enabled) - Order
- Scope (
providers, optionalplaylistIds) - Widget-specific
settings
On load:
- Stored layout is validated against current widget registry/defaults.
- Missing or removed widgets are migrated safely.
- Invalid settings fall back to defaults, preventing dashboard hard-fail.
Widget UX behavior implemented
Recently WatchedandGlobal Favorites:- Content-kind chips (channels/vod/series)
- List/grid toggle
- Shared item styles
- Deep-link navigation into details/views
Continue Watching:- Resume-oriented content block
Recent Sources:- Source activity with truncation-safe titles
Source Statistics:- Aggregated per-provider/source counters
Recent Activitywas intentionally removed from scope.
How to add a new widget (current workflow)
- Add a new
typeindashboard-widget.model.ts. - Implement widget component under
libs/workspace/dashboard/ui/src/lib/widgets/. - Register it in
dashboard-widget-host.component.tstype mapping. - Add default config in layout defaults/migration path (
dashboard-layout.service.ts). - Extend
dashboard-data.service.tsor reuse existing feature services for data. - Add explicit loading/empty/error UI states and deep-link behavior.
Current boundaries
- Advanced drag/resize grid and collision engine is not implemented yet.
- External adapters (RSS/sports/news) are intentionally deferred.
3) Route Generalization (Current Routing Topology)
Primary app routing now starts in workspace:
/->/workspace/workspace/dashboard/workspace/sources/workspace/playlists/:id(M3U player in workspace frame)/workspace/xtreams/:id/...(workspace layout applied)/workspace/stalker/:id/...(workspace layout applied)
Defined in:
apps/web/src/app/app.routes.ts
Mechanism:
- Existing provider route trees (
xtreamRoutes,stalkerRoutes) are wrapped bywithWorkspaceLayout(...)and receivedata.layout = 'workspace'. - Provider shells/components use
isWorkspaceLayoutto hide legacy local sidebars/header pieces.
4) Generalized UX Behaviors Added
- Playlist switcher integrated into persistent header and used as cross-context navigator:
libs/ui/components/src/lib/playlist-switcher/playlist-switcher.component.ts
- Header search unified, with debounced apply for route/store updates:
apps/web/src/app/workspace/workspace-shell.component.ts
- Sources filtering moved to dedicated panel:
apps/web/src/app/workspace/workspace-sources-filters-panel.component.ts
- Context categories moved to shared workspace panel:
apps/web/src/app/workspace/workspace-context-panel.component.ts
- Dashboard activity items deep-link directly to content context/details; Xtream live entries auto-play on open.
5) Legacy/Old Parts Still Present
A) Legacy routes kept for compatibility
In apps/web/src/app/app.routes.ts:
/home/playlists/iptv/playlists/:id- Non-workspace provider routes:
/xtreams/:id/.../stalker/:id/...
- Alias
/portals/:id->StalkerMainContainerComponent
B) Legacy Xtream module tree kept as fallback
apps/web/src/app/xtream/*
This is still used by legacyXtreamRouteFallback when !window.electron.
C) Dual-layout branching across provider components
Many provider components still have workspace/non-workspace conditional branches via isWorkspaceLayout (for sidebar/header/body behavior). This is intentional compatibility, but increases maintenance.
Examples:
apps/web/src/app/xtream-electron/xtream-shell.component.htmlapps/web/src/app/stalker/stalker-shell.component.htmlapps/web/src/app/xtream-electron/xtream-main-container.component.htmlapps/web/src/app/stalker/stalker-main-container.component.htmlapps/web/src/app/home/video-player/video-player.component.html
D) Unused placeholder artifact
apps/web/src/app/workspace/dashboard-placeholder.component.tsapps/web/src/app/workspace/dashboard-placeholder.component.htmlapps/web/src/app/workspace/dashboard-placeholder.component.scss
These files are no longer routed.
6) Potential Removals (With Preconditions)
Low risk (can remove soon)
- Remove unused dashboard placeholder files (
dashboard-placeholder.*).
Medium risk (requires product decision)
- Remove
/portals/:idalias route if no external/deeplink dependency remains. - Remove legacy entry routes (
/home,/iptv,/playlists,/playlists/:id) if workspace-only navigation is desired.
High risk (requires platform/support policy decision)
- Remove legacy fallback module
apps/web/src/app/xtream/*andlegacyXtreamRouteFallbackonly if non-Electron fallback is officially dropped. - Remove non-workspace provider routes (
/xtreams/:id/...,/stalker/:id/...) only after verifying all deeplinks/bookmarks/integration paths have migrated to/workspace/....
7) Recommended Cleanup Sequence
- Remove unused placeholder files.
- Decide whether non-workspace URLs must remain public and supported.
- If workspace-only is approved:
- Deprecate legacy routes with redirects.
- Remove non-workspace route trees.
- Remove
isWorkspaceLayoutbranches and simplify provider layouts.
- If non-Electron fallback is not required:
- Remove
legacyXtreamRouteFallback. - Remove
apps/web/src/app/xtream/*.
- Remove
8) Current Plan Items Still Open
From current plan scope, remaining work is primarily:
- Widget settings UX improvements (scope presets, bulk provider toggles).
- Phase 3 dashboard advanced layout system (drag/resize/collision handling).
- Phase 4 external widget framework (RSS/scores/news adapters + reliability controls).