Split WorkspaceShellFacade into focused component-scoped services and keep the facade as the stable template-facing delegation layer.
9.7 KiB
Workspace Shell
This document records the current workspace-first shell contract. It is the stable replacement for the older UI refactor summary.
Related:
Summary
/workspaceis the primary app surface.WorkspaceShellComponentowns the persistent frame: rail, header, optional context panel, content outlet, and external playback footer.- Descendant workspace pages inherit
layout = 'workspace'from the/workspaceroot route. - Provider route trees now bootstrap through route-scoped session providers instead of nested provider shell components.
Core implementation:
apps/web/src/app/app.routes.tslibs/workspace/shell/feature/src/lib/workspace-shell/workspace-shell.component.tslibs/workspace/shell/feature/src/lib/workspace-shell/workspace-shell.component.htmllibs/workspace/shell/feature/src/lib/workspace-shell/services/workspace-shell.facade.tslibs/workspace/shell/feature/src/lib/workspace-shell/services/workspace-shell-route-state.service.tslibs/workspace/shell/feature/src/lib/workspace-shell/services/workspace-shell-search.service.tslibs/workspace/shell/feature/src/lib/workspace-shell/services/workspace-shell-search-sync.service.tslibs/workspace/shell/feature/src/lib/workspace-shell/services/workspace-shell-header.service.tslibs/workspace/shell/feature/src/lib/workspace-shell/services/workspace-shell-command-palette.service.tslibs/workspace/shell/feature/src/lib/workspace-shell/services/workspace-shell-xtream-import.service.tslibs/portal/shared/util/src/lib/navigation/portal-route.utils.tslibs/portal/shared/util/src/lib/navigation/portal-rail-links.tslibs/portal/shared/ui/src/lib/navigation/portal-rail-links.component.ts
Route Contract
Current workspace routes:
/->/workspace/workspace->/workspace/dashboard/workspace/dashboard/workspace/sources/workspace/playlists/:id/:view/workspace/global-favorites/workspace/downloads/workspace/settings/workspace/xtreams/:id/.../workspace/stalker/:id/...
Compatibility redirect:
/settings->/workspace/settings
Provider route integration:
apps/web/src/app/app.routes.tsmarks the/workspaceroot route withdata.layout = 'workspace'.isWorkspaceLayoutRoute(...)treats that layout marker as inherited route state for all descendants.- Xtream and Stalker parent routes attach route-scoped session providers that bootstrap the active playlist, sync provider section state, and clean up provider-local state when the route is destroyed.
- Xtream route bootstrap is DB-first for already imported Electron playlists: if the requested section has persisted categories and content, the route hydrates from SQLite even when the portal status probe reports unavailable, expired, or inactive. Fresh/no-cache Xtream routes still use the status probe to block remote imports before the loading overlay starts.
- Workspace routes no longer rely on nested provider shell components for hidden local chrome.
Shell Structure
The shell is intentionally split into four persistent regions:
- Left rail:
- Static workspace links for dashboard, sources, global favorites, and recently viewed.
- Provider-aware context links derived from the active or current playlist.
- Settings remains a persistent footer shortcut in the rail.
- Top header:
- Playlist switcher.
- Route-aware search input and command palette trigger.
- Add source action.
- Optional playlist refresh and route-specific shortcut actions.
- Downloads shortcut in Electron.
- Main body:
- Optional left context panel.
- Main router outlet content.
- Optional footer:
- External playback session bar when a docked session is visible.
WorkspaceShellComponent binds only to WorkspaceShellFacade. The facade is
kept as a thin template-facing API and delegates ownership to component-scoped
services:
WorkspaceShellRouteStateServiceowns current route parsing, rail links, context-panel state, dashboard startup preference, and playlist source signals.WorkspaceShellSearchServiceowns the route-aware header search capability and public search actions.WorkspaceShellSearchSyncServiceowns the search query signals, debounced application, provider-store synchronization, and query-param sync.WorkspaceShellHeaderServiceowns playlist title/subtitle, account/info actions, refresh action state, and recent-items bulk cleanup.WorkspaceShellCommandPaletteServiceowns command-palette dialog lifecycle and recent-command recording.WorkspaceShellXtreamImportServiceowns Xtream import/refresh overlay state and labels.
When adding shell behavior, prefer placing it in the service that owns the
nearest existing state. Keep WorkspaceShellFacade as a stable re-export layer
for the template unless the template contract itself intentionally changes.
Context Panel Rules
The shell decides which secondary panel to show from the current route:
/workspace/sourcesWorkspaceSourcesFiltersPanelComponent
- Xtream category sections (
live,vod,series)WorkspaceContextPanelComponent
- Stalker category sections (
itv,radio,vod,series)WorkspaceContextPanelComponent
/workspace/settingsWorkspaceSettingsContextPanelComponent
- Downloads sections
WorkspaceCollectionContextPanelComponent
The context panel is part of the shell contract. New workspace-level routes should explicitly decide whether they need one rather than adding local sidebars inside feature pages.
Xtream and Stalker category panels preserve provider/server category order by
default. The panel header exposes a sort menu next to category search with
Server sorting, A-Z, and Z-A; when alphabetical sorting is active,
synthetic "all categories" entries stay pinned before sorted provider
categories.
Search And Navigation Rules
Search is shell-owned and route-aware:
- Disabled on settings routes.
- Enabled on sources routes.
- Enabled for supported Xtream and Stalker content/search views.
- Placeholder text and search handling vary by provider and section.
- Input changes are debounced before route/store updates are applied.
Rail navigation is also shell-owned:
- Workspace-global entries are static.
- Provider entries come from
buildPortalRailLinks(...). - On dashboard, sources, settings, and global favorites, the shell falls back to the currently selected playlist so provider navigation remains available even outside a provider route.
Command palette behavior is shell-owned but view-extensible:
- The shell resolves commands into three groups in fixed order: current view, this playlist, then global.
- Shell-owned commands are derived from route context and current playlist state; empty groups are omitted instead of rendering disabled placeholders.
- Workspace features contribute current-view commands through
WorkspaceViewCommandService. - Header shortcut actions can opt into palette exposure by attaching palette
metadata through
WorkspaceHeaderContextService. - Filtering matches command labels, descriptions, and keywords, and keyboard selection always lands on the first enabled command.
- A "Recently used" section is rendered above the standard groups when the
query is empty and at least one stored id resolves to a visible+enabled
command; ids are persisted via
RecentCommandsService(capped at 5, stored atSTORE_KEY.RecentCommands). Storage is not pruned by route visibility — a navigation command likeOpen sourcesis invisible while the user is on/workspace/sourcesbut the id stays in storage so it reappears in the recent section after navigating away. - Five "Switch player to X" commands are registered globally by
WorkspacePlayerCommandsContributor. The MPV/VLC entries are visible only in Electron, and the entry matching the currentSettingsStore.player()value is disabled. The new player setting applies to the next playback session; an existing stream is not re-mounted.
Keyboard shortcut help is shell-owned:
WorkspaceKeyboardShortcutsServiceis provided byWorkspaceShellComponent. It owns the workspace-scopeddocument:keydownlistener for?/Shift+/.- The listener ignores events from inputs, textareas, selects, and
content-editable elements via
isTypingInInput(...). libs/portal/shared/util/src/lib/keyboard-shortcut-definitions.tsis the metadata registry for shortcuts shown in the help dialog and documented in README.keyboard-shortcuts.tsowns the display transformation and help trigger detection. Shortcuts that only work through the Electron bridge, such as embedded MPV controls, must setelectronOnly: trueso the PWA dialog does not advertise unavailable commands.- New custom shortcuts should be added to that registry when the handler is
added. Do not include native browser/editor behavior such as
Tabor platform text editing shortcuts.
Maintenance Guidance
Use this document as the source of truth when changing workspace shell behavior.
- New top-level user destinations should default to child routes under
/workspace. - Shared provider navigation logic belongs in portal-shared util/UI libraries, not duplicated inside the shell.
- If a provider route changes how playlist/session bootstrap works, update the route-session provider and shell-facing route contract together.
- When adding a non-native keyboard shortcut, update the shared shortcuts registry, the help dialog tests, README, and the closest behavior test.
- Historical migration notes, cleanup lists, and one-off refactor steps should stay out of this file; track them in issues or PR notes instead.