docs(ui): document live panel ownership

This commit is contained in:
4gray committed 2026-07-31 09:49:04 +02:00
1 parent 7d7dbe25ad
commit fae19c2041
4 files changed
+98 -51

No files matched your search

+10
View File
@@ -0,0 +1,10 @@
---
type: feature
area: ui
issues: [1118]
---
Live TV now remembers Groups and Channels visibility independently across M3U,
Xtream, Stalker, Favorites, and Recently Viewed. Consistent accessible controls
restore each panel, while Cmd/Ctrl+B temporarily clears the viewing area
without overwriting those choices. Guide toggles now appear only when usable.
+17
View File
@@ -929,6 +929,23 @@ engine` (restart required) or
- Keyboard shortcuts: ArrowUp/ArrowDown (volume), M (mute)
- Component: `libs/ui/playback/src/lib/audio-player/audio-player.component.ts`
**Live Panel Controls**:
- Groups and Channels keep independent persisted intent through
`LiveLayoutPanelStateService` in `@iptvnator/portal/shared/data-access`;
missing new values migrate per panel from the legacy
`live-sidebar-state`.
- M3U, Xtream, Stalker, Favorites, and Recently Viewed use the same
header-hide/boundary-restore pattern with retained `inert` panels, ARIA
relationships, focus transfer, and 40px minimum targets.
- `Cmd/Ctrl+B` temporarily suppresses applicable left panels without changing
their saved combination. Responsive suppression also preserves intent and
omits unfulfillable restore controls.
- Guide disclosure appears only beside a working inline player. External
MPV/VLC keeps a static EPG heading, and radio has no Guide panel.
- Canonical UX and ownership contract:
`docs/architecture/iptvnator-ui-guidelines.md` ("Live Panel Disclosures").
**EPG (Electronic Program Guide)**:
- XMLTV format support
+61 -41
View File
@@ -214,48 +214,68 @@ remain local when the meaning is explicit.
- Keep the EPG content mounted while collapsed so current-program state can
continue updating.
### Collapsible Live Sidebar
### Live Panel Disclosures
- M3U, Xtream, and Stalker live layouts share a single sidebar collapse toggle
that hides the channels rail to give the player and EPG full width.
- Xtream Live TV's root view (`/live` with no selected category) follows the
same paginated `All Items` shell as VOD and Series: a widget header with the
total channel count, page-size controls, and page navigation above the shared
`app-grid-list`. Use the grid list's logo-oriented live variant so channel
logos stay contained in 16:9 thumbnails instead of being cropped like
VOD/series posters. Selecting a channel from that root grid starts playback,
selects the channel's category, highlights the active category and channel,
and scrolls the category rail plus virtual channels list to the selected rows
when those rails are visible.
- In Xtream and Stalker live TV, the same toggle also collapses the workspace
shell context sidebar (the "Live Categories" rail rendered by
`WorkspaceShellContextSidebarComponent`), matching M3U's "everything quiets"
behaviour. The shell categories rail only collapses when the active section
is `live` (Xtream) or `itv`/`radio` (Stalker); movies, series, favorites,
and recent routes leave it untouched.
- Collapsed state is owned by `LiveLayoutSidebarStateService`
(`providedIn: 'root'`) in `@iptvnator/portal/shared/util`. Every surface that
participates injects the service and reads `isCollapsed`; any toggle calls
`service.toggle()`. Persistence delegates to the existing
`live-sidebar-state` helpers, so the localStorage key stays unchanged and
missing/invalid values restore to expanded.
- A `mat-icon-button` with `chevron_left` lives in the sidebar header and
toggles state. While collapsed, a floating `chevron_right` mini-fab appears
at the left edge of `.content-container` to restore the rail (and the
categories rail, in Xtream/Stalker live).
- Keyboard shortcut: `Cmd/Ctrl+B`. The handler ignores events that originate
inside `<input>`, `<textarea>`, `<select>`, or content-editable elements via
the shared `isTypingInInput` helper.
- The CSS class `.sidebar-collapsed` (channels rail) and
`.context-panel--collapsed` (workspace shell categories rail) both override
the inline width set by the `appResizable` directive with
`width: 0 !important; min-width: 0 !important`. The directive's persisted
width is preserved so uncollapsing restores the user's previous resized
width. Both rails share the same 180 ms width transition so motion stays in
lockstep.
- Below 600 px viewport, the M3U layout's mobile bottom-drawer rule overrides
the desktop collapse to `height: 0` instead of `width: 0`, and the floating
restore handle is hidden.
Live layouts treat Groups, Channels, and Guide as separate capabilities. Never
derive one panel's visibility from another panel's state, and never render a
disclosure control for an action the current layout cannot perform.
| Panel | Applicable surfaces | UI owner |
| -------- | --------------------------------------------------------------------------------------- | ------------------------------------------------ |
| Groups | M3U Groups; Xtream Live TV; Stalker TV/Radio | M3U groups view or the workspace context sidebar |
| Channels | M3U All/Groups; selected Xtream/Stalker live category; Favorites/Recent live collection | The route's channel-list or collection header |
| Guide | EPG timeline/list beside a working inline player | `app-epg-timeline` or `app-epg-list-view` |
`LiveLayoutPanelStateService` in
`@iptvnator/portal/shared/data-access` owns only persisted user intent for the
two left panels:
- `live-groups-panel-state`
- `live-channels-panel-state`
Both values are independently `expanded` or `collapsed`. On first use, each
missing or invalid new value is seeded from a valid legacy
`live-sidebar-state`; otherwise it defaults to expanded. Migration writes the
new keys and never rewrites or removes the legacy key.
Routes resolve effective visibility from intent plus local applicability,
responsive suppression, and the service's non-persisted master suppression.
That distinction is intentional: hiding a panel updates its own persisted
intent, while `Cmd/Ctrl+B` temporarily suppresses all applicable left panels
without overwriting either choice. Pressing the shortcut again restores the
saved combination. A direct panel action clears master suppression and then
applies the requested intent. Shortcut handlers ignore input, textarea,
select, and content-editable targets.
Disclosure controls follow one placement and accessibility contract:
- Expanded panels put a labelled Hide action in their own header.
- Collapsed panels put a labelled Show action on the boundary where the panel
will return. If Groups and Channels are both collapsed, the restore rails
stay in spatial panel order.
- Favorites and Recently Viewed keep their single Channels action in the
collection header; do not duplicate it inside loading, empty, or zero-result
content.
- The controlled panel stays mounted with `aria-hidden="true"` and `inert`
while effectively hidden. The control uses `aria-controls`,
`aria-expanded`, an action-oriented translated label, and a minimum 40px
target. Move focus to the corresponding restore/hide control after a direct
state change.
- Loading, empty-category, and zero-search-results states keep every applicable
control operable. Applicability, not item count, decides whether a panel can
be disclosed.
Responsive suppression must not manufacture no-op controls. The workspace
Groups panel is suppressed at 1023px and below; the M3U Groups panel is
suppressed below 600px. In both cases its persisted intent remains unchanged
and no Groups restore control appears. The M3U Channels bottom drawer remains
independently restorable on mobile.
Guide disclosure is a separate EPG capability. It is interactive only beside a
working inline player. External MPV/VLC layouts keep their full static EPG
heading without a Guide toggle, and radio layouts render no Guide panel. This
contract does not make the external-player right area content-aware; that is a
separate layout concern.
### EPG Card
@@ -656,14 +656,14 @@ git commit -m "feat(i18n): translate live panel controls"
- Reuse: `apps/electron-backend-e2e/src/electron-test-fixtures.ts`
- Reuse: `apps/electron-backend-e2e/src/portal-mock-fixtures.ts`
- [ ] **Step 1: Write the M3U Electron flow**
- [x] **Step 1: Write the M3U Electron flow**
Import the mock M3U fixture, enter Groups, independently hide/restore Groups and
Channels, verify focus/ARIA/inert/width reclamation, exercise `Control+B`, seed
legacy storage before reload, and check the mobile Channels restore path at a
narrow viewport.
- [ ] **Step 2: Run the M3U test and verify RED if a gap remains**
- [x] **Step 2: Run the M3U test and verify RED if a gap remains**
```bash
pnpm nx run electron-backend-e2e:e2e-ci--src/live-panel-toggles.e2e.ts \
@@ -673,14 +673,14 @@ pnpm nx run electron-backend-e2e:e2e-ci--src/live-panel-toggles.e2e.ts \
Expected after implementation: PASS. Any failure must identify a functional or
layout gap before adding provider flows.
- [ ] **Step 3: Add Xtream and Stalker flows**
- [x] **Step 3: Add Xtream and Stalker flows**
Use the existing mock-server add helpers. Verify root Groups-only placement,
category Groups+Channels independence, persistence across navigation,
Groups-then-Channels restore ordering, zero-search-result controls, and no
Guide toggle for external player/radio.
- [ ] **Step 4: Run the full atomized target**
- [x] **Step 4: Run the full atomized target**
```bash
pnpm nx run electron-backend-e2e:e2e-ci--src/live-panel-toggles.e2e.ts
@@ -688,7 +688,7 @@ pnpm nx run electron-backend-e2e:e2e-ci--src/live-panel-toggles.e2e.ts
Expected: all `@live-panels` tests pass.
- [ ] **Step 5: Lint and commit**
- [x] **Step 5: Lint and commit**
```bash
pnpm nx lint electron-backend-e2e
@@ -704,19 +704,19 @@ git commit -m "test(e2e): cover live panel toggles"
- Modify: `CLAUDE.md`
- Create: `.changes/ui-live-panel-toggles.md`
- [ ] **Step 1: Replace the obsolete shared-sidebar documentation**
- [x] **Step 1: Replace the obsolete shared-sidebar documentation**
Document independent Groups/Channels storage keys, data-access ownership,
intent versus effective state, boundary controls, master suppression,
responsive Groups-first suppression, collection-header exception, and Guide
capability. Preserve the channel-row responsive contract from #1312 verbatim.
- [ ] **Step 2: Update `CLAUDE.md` where it describes live sidebar state**
- [x] **Step 2: Update `CLAUDE.md` where it describes live sidebar state**
Mirror the canonical names, service location, and shortcut semantics without
adding the future content-aware right-region behavior.
- [ ] **Step 3: Add the user-facing release note**
- [x] **Step 3: Add the user-facing release note**
```md
---
@@ -730,7 +730,7 @@ reliable restore controls across providers and screen sizes, and hides Guide
toggles when they cannot work.
```
- [ ] **Step 4: Validate documentation and release note**
- [x] **Step 4: Validate documentation and release note**
```bash
git diff --check
@@ -739,7 +739,7 @@ pnpm run release:notes:validate
Expected: both commands exit 0.
- [ ] **Step 5: Commit**
- [x] **Step 5: Commit**
```bash
git add docs/architecture/iptvnator-ui-guidelines.md CLAUDE.md \