docs: Implementierungsplan für TV-App D-Pad Fixes
6 Tasks mit exakten Zeilennummern und Code-Snippets: 1. Alphabet-Sidebar nur verfügbare Buchstaben 2. FocusManager Sidebar-Navigation 3. Episoden-Cards vergrößern 4. Gesehen-Button fokussierbar 5. Serie-als-gesehen Button 6. Finaler Test Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
efbda20e42
commit
26adabe55c
1 changed files with 531 additions and 0 deletions
531
docs/superpowers/plans/2026-03-16-tv-dpad-fixes.md
Normal file
531
docs/superpowers/plans/2026-03-16-tv-dpad-fixes.md
Normal file
|
|
@ -0,0 +1,531 @@
|
|||
# TV-App D-Pad & Usability Fixes - Implementierungsplan
|
||||
|
||||
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 5 Fernbedienungs-/Usability-Probleme der TV-App auf Samsung Tizen beheben.
|
||||
|
||||
**Architecture:** Reine Frontend-Änderungen an 4 Dateien — JavaScript (FocusManager + Template-Scripts), CSS (Episode-Grid, Mark-Button, neuer Button), HTML (Templates). Kein Backend-Change. Bestehender i18n-Key `status.mark_series` wird wiederverwendet.
|
||||
|
||||
**Tech Stack:** Vanilla JavaScript, CSS3, Jinja2-Templates, aiohttp
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-03-16-tv-dpad-fixes-design.md`
|
||||
|
||||
---
|
||||
|
||||
## Dateiübersicht
|
||||
|
||||
| Datei | Änderung |
|
||||
|-------|----------|
|
||||
| `video-konverter/app/static/tv/js/tv.js` | FocusManager: Sidebar-Navigation, focusin-Handler |
|
||||
| `video-konverter/app/static/tv/css/tv.css` | Episode-Grid größer, Mark-Button sichtbar, neuer Button-Style |
|
||||
| `video-konverter/app/templates/tv/series.html` | Alphabet-IIFE: entfernen statt dimmen |
|
||||
| `video-konverter/app/templates/tv/series_detail.html` | Mark-Button fokussierbar, "Serie als gesehen"-Button + JS |
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Alphabet-Sidebar — nur verfügbare Buchstaben anzeigen
|
||||
|
||||
**Files:**
|
||||
- Modify: `video-konverter/app/templates/tv/series.html:243-253`
|
||||
|
||||
- [ ] **Step 1: IIFE ändern — `el.remove()` statt `el.classList.add('dimmed')`**
|
||||
|
||||
In `series.html`, Zeile 243-253, das bestehende IIFE anpassen:
|
||||
|
||||
```javascript
|
||||
// Buchstaben ohne Treffer komplett entfernen (statt nur dimmen)
|
||||
(function() {
|
||||
var avail = {};
|
||||
document.querySelectorAll('.tv-view-grid [data-letter], .tv-view-list [data-letter], .tv-view-detail [data-letter]').forEach(function(item) {
|
||||
var raw = item.dataset.letter;
|
||||
avail[/^[A-Z]$/.test(raw) ? raw : '#'] = true;
|
||||
});
|
||||
document.querySelectorAll('.tv-alpha-letter').forEach(function(el) {
|
||||
if (!avail[el.dataset.letter]) el.remove();
|
||||
});
|
||||
})();
|
||||
```
|
||||
|
||||
Konkret: Zeile 251 ändern von:
|
||||
```javascript
|
||||
if (!avail[el.dataset.letter]) el.classList.add('dimmed');
|
||||
```
|
||||
zu:
|
||||
```javascript
|
||||
if (!avail[el.dataset.letter]) el.remove();
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Manuell testen**
|
||||
|
||||
Browser öffnen → TV-Serien-Seite → prüfen dass nur Buchstaben angezeigt werden, die tatsächlich Serien haben.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add video-konverter/app/templates/tv/series.html
|
||||
git commit -m "fix(tv): Alphabet-Sidebar zeigt nur verfügbare Buchstaben"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: FocusManager — Sidebar per D-Pad erreichbar
|
||||
|
||||
**Files:**
|
||||
- Modify: `video-konverter/app/static/tv/js/tv.js:38-43` (focusin-Handler)
|
||||
- Modify: `video-konverter/app/static/tv/js/tv.js:230` (nach ArrowUp-Nav-Block, vor Nearest-Neighbor)
|
||||
- Modify: `video-konverter/app/static/tv/js/tv.js:240-243` (searchEls-Filter)
|
||||
|
||||
**WICHTIG:** Alle 3 Änderungen sind atomar — zusammen einfügen!
|
||||
|
||||
- [ ] **Step 1: focusin-Handler anpassen — Sidebar aus Content-Tracking ausschließen**
|
||||
|
||||
In `tv.js`, Zeile 38-43, die Bedingung erweitern. Aktuell:
|
||||
|
||||
```javascript
|
||||
if (e.target && e.target.hasAttribute && e.target.hasAttribute("data-focusable")) {
|
||||
if (!e.target.closest("#tv-nav")) {
|
||||
// Nur echte Content-Elemente merken (nicht Filter/Controls)
|
||||
if (e.target.closest(".tv-grid, .tv-list-compact, .tv-detail-list, .tv-folder-view, .tv-row, .tv-episode-list, .tv-episode-grid, .tv-tabs, .tv-detail-actions, .tv-alpha-sidebar, .tv-view-switch, .tv-filter-bar, .tv-season-actions, .profiles-grid")) {
|
||||
this._lastContentFocus = e.target;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Ändern zu (`.tv-alpha-sidebar` aus der Content-Areas-Liste entfernen UND Sidebar explizit in äußerer Bedingung ausschließen):
|
||||
|
||||
```javascript
|
||||
if (e.target && e.target.hasAttribute && e.target.hasAttribute("data-focusable")) {
|
||||
if (!e.target.closest("#tv-nav") && !e.target.closest(".tv-alpha-sidebar")) {
|
||||
// Nur echte Content-Elemente merken (nicht Nav/Sidebar)
|
||||
if (e.target.closest(".tv-grid, .tv-list-compact, .tv-detail-list, .tv-folder-view, .tv-row, .tv-episode-list, .tv-episode-grid, .tv-tabs, .tv-detail-actions, .tv-view-switch, .tv-filter-bar, .tv-season-actions, .profiles-grid")) {
|
||||
this._lastContentFocus = e.target;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Sidebar-Navigationslogik einfügen — nach ArrowUp-Block (Zeile 230)**
|
||||
|
||||
In `tv.js`, nach Zeile 230 (dem Ende des `if (!inNav && direction === "ArrowUp")` Blocks), VOR dem Nearest-Neighbor-Block (Zeile 232: `const currentRect = ...`), folgenden Code einfügen:
|
||||
|
||||
```javascript
|
||||
// ===== Alphabet-Sidebar Navigation =====
|
||||
const inSidebar = active.closest(".tv-alpha-sidebar");
|
||||
|
||||
if (inSidebar) {
|
||||
if (direction === "ArrowLeft") {
|
||||
// Zurueck zum Content
|
||||
if (this._lastContentFocus && document.contains(this._lastContentFocus)) {
|
||||
this._lastContentFocus.focus();
|
||||
} else {
|
||||
const firstCard = document.querySelector(".tv-grid [data-focusable], .tv-card[data-focusable]");
|
||||
if (firstCard) firstCard.focus();
|
||||
}
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (direction === "ArrowUp" || direction === "ArrowDown") {
|
||||
// Sequentiell durch Buchstaben
|
||||
const letters = Array.from(document.querySelectorAll(".tv-alpha-letter[data-focusable]"))
|
||||
.filter(el => el.offsetHeight > 0);
|
||||
const idx = letters.indexOf(active);
|
||||
const next = direction === "ArrowDown" ? idx + 1 : idx - 1;
|
||||
if (next >= 0 && next < letters.length) {
|
||||
letters[next].focus();
|
||||
letters[next].scrollIntoView({ block: "nearest", behavior: "smooth" });
|
||||
}
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// ArrowRight am rechten Grid-Rand -> Sidebar
|
||||
if (!inNav && !inSidebar && direction === "ArrowRight") {
|
||||
const sidebar = document.getElementById("alpha-sidebar");
|
||||
if (sidebar && sidebar.offsetHeight > 0) {
|
||||
// Pruefen ob es noch ein Element rechts im Content gibt
|
||||
const contentEls = focusables.filter(el => !el.closest("#tv-nav") && !el.closest(".tv-alpha-sidebar"));
|
||||
const currentRect_r = active.getBoundingClientRect();
|
||||
const rightNeighbor = contentEls.some(el => {
|
||||
const r = el.getBoundingClientRect();
|
||||
return r.left > currentRect_r.right + 5 && Math.abs(r.top - currentRect_r.top) < 100;
|
||||
});
|
||||
if (!rightNeighbor) {
|
||||
// Kein Content rechts -> zur Sidebar springen
|
||||
const sidebarLetters = Array.from(sidebar.querySelectorAll("[data-focusable]"))
|
||||
.filter(el => el.offsetHeight > 0);
|
||||
if (sidebarLetters.length > 0) {
|
||||
// Naechstgelegenen Buchstaben vertikal finden
|
||||
const cy_r = currentRect_r.top + currentRect_r.height / 2;
|
||||
let best = sidebarLetters[0];
|
||||
let bestDist_r = Infinity;
|
||||
sidebarLetters.forEach(l => {
|
||||
const lr = l.getBoundingClientRect();
|
||||
const d = Math.abs(lr.top + lr.height / 2 - cy_r);
|
||||
if (d < bestDist_r) { bestDist_r = d; best = l; }
|
||||
});
|
||||
this._lastContentFocus = active;
|
||||
best.focus();
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Nearest-Neighbor searchEls-Filter erweitern — Sidebar ausschließen**
|
||||
|
||||
In `tv.js`, die Zeilen mit dem `searchEls`-Filter (ca. Zeile 240-243, nach Einfügung verschoben) ändern von:
|
||||
|
||||
```javascript
|
||||
const searchEls = inNav
|
||||
? focusables.filter(el => el.closest("#tv-nav"))
|
||||
: focusables.filter(el => !el.closest("#tv-nav"));
|
||||
```
|
||||
|
||||
zu:
|
||||
|
||||
```javascript
|
||||
const searchEls = inNav
|
||||
? focusables.filter(el => el.closest("#tv-nav"))
|
||||
: focusables.filter(el => !el.closest("#tv-nav") && !el.closest(".tv-alpha-sidebar"));
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Manuell testen**
|
||||
|
||||
Browser/Tizen-Emulator → Serien-Seite:
|
||||
1. D-Pad rechts am Grid-Rand → Sidebar bekommt Focus
|
||||
2. D-Pad hoch/runter in Sidebar → sequentiell durch Buchstaben
|
||||
3. D-Pad links in Sidebar → zurück zur letzten Card
|
||||
4. Enter in Sidebar → filtert Serien nach Buchstabe
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add video-konverter/app/static/tv/js/tv.js
|
||||
git commit -m "fix(tv): Alphabet-Sidebar per D-Pad/Fernbedienung navigierbar"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Episoden-Cards vergrößern + Laufzeit-Badge
|
||||
|
||||
**Files:**
|
||||
- Modify: `video-konverter/app/static/tv/css/tv.css:1817-1821` (Episode-Grid Basis)
|
||||
- Modify: `video-konverter/app/static/tv/css/tv.css:2019-2020` (1200px Breakpoint)
|
||||
|
||||
- [ ] **Step 1: Basis-Grid vergrößern**
|
||||
|
||||
In `tv.css`, Zeile 1819 ändern von:
|
||||
|
||||
```css
|
||||
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
||||
```
|
||||
|
||||
zu:
|
||||
|
||||
```css
|
||||
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
||||
```
|
||||
|
||||
Zeile 1820 ändern von:
|
||||
|
||||
```css
|
||||
gap: 0.8rem;
|
||||
```
|
||||
|
||||
zu:
|
||||
|
||||
```css
|
||||
gap: 1rem;
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 1200px-Breakpoint anpassen**
|
||||
|
||||
In `tv.css`, Zeile 2020 ändern von:
|
||||
|
||||
```css
|
||||
.tv-episode-grid { grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); }
|
||||
```
|
||||
|
||||
zu:
|
||||
|
||||
```css
|
||||
.tv-episode-grid { grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); }
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Laufzeit-Badge anpassen**
|
||||
|
||||
In `tv.css`, die `.tv-ep-duration`-Regel finden (Zeile 452-461) und `white-space: nowrap;` hinzufügen sowie Font/Padding anpassen:
|
||||
|
||||
Aktuell:
|
||||
```css
|
||||
.tv-ep-duration {
|
||||
position: absolute;
|
||||
bottom: 6px;
|
||||
right: 6px;
|
||||
background: rgba(0,0,0,0.75);
|
||||
color: #fff;
|
||||
font-size: 0.7rem;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
```
|
||||
|
||||
Ändern zu:
|
||||
```css
|
||||
.tv-ep-duration {
|
||||
position: absolute;
|
||||
bottom: 6px;
|
||||
right: 6px;
|
||||
background: rgba(0,0,0,0.75);
|
||||
color: #fff;
|
||||
font-size: 0.8rem;
|
||||
padding: 3px 8px;
|
||||
border-radius: 3px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Manuell testen**
|
||||
|
||||
Browser → Serien-Detail-Seite → prüfen dass Episoden-Cards größer sind und die Laufzeit vollständig sichtbar ist.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add video-konverter/app/static/tv/css/tv.css
|
||||
git commit -m "fix(tv): Episoden-Cards vergrößert, Laufzeit-Badge vollständig sichtbar"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: "Gesehen"-Button auf Episode-Cards fokussierbar machen
|
||||
|
||||
**Files:**
|
||||
- Modify: `video-konverter/app/templates/tv/series_detail.html:129-132` (HTML)
|
||||
- Modify: `video-konverter/app/static/tv/css/tv.css:1864-1899` (CSS)
|
||||
|
||||
- [ ] **Step 1: HTML — `tabindex="-1"` entfernen, `data-focusable` hinzufügen**
|
||||
|
||||
In `series_detail.html`, Zeile 129-132 ändern von:
|
||||
|
||||
```html
|
||||
<button class="tv-ep-tile-mark {% if ep.progress_pct >= watched_threshold_pct|default(90) %}active{% endif %}"
|
||||
tabindex="-1"
|
||||
onclick="event.stopPropagation(); toggleWatched({{ ep.id }}, this)">
|
||||
✓
|
||||
</button>
|
||||
```
|
||||
|
||||
zu:
|
||||
|
||||
```html
|
||||
<button class="tv-ep-tile-mark {% if ep.progress_pct >= watched_threshold_pct|default(90) %}active{% endif %}"
|
||||
data-focusable
|
||||
onclick="event.stopPropagation(); toggleWatched({{ ep.id }}, this)">
|
||||
✓
|
||||
</button>
|
||||
```
|
||||
|
||||
- [ ] **Step 2: CSS — Mark-Button auf TV sichtbar machen (hover:none Media-Query)**
|
||||
|
||||
In `tv.css`, nach der bestehenden `.tv-ep-tile-mark`-Regel (Zeile 1882, nach `z-index: 2;` und vor der hover/focus-Regel), folgende Media-Query einfügen:
|
||||
|
||||
```css
|
||||
/* TV-Geraete (kein Hover): Mark-Button immer leicht sichtbar */
|
||||
@media (hover: none) {
|
||||
.tv-ep-tile-mark {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: CSS — Focus-Ring für Mark-Button**
|
||||
|
||||
Die bestehende Regel in `tv.css`, Zeile 1894-1899 ist bereits vorhanden:
|
||||
|
||||
```css
|
||||
.tv-ep-tile-mark:hover, .tv-ep-tile-mark:focus {
|
||||
border-color: var(--accent);
|
||||
color: var(--accent);
|
||||
outline: none;
|
||||
opacity: 1;
|
||||
}
|
||||
```
|
||||
|
||||
Diese enthält `outline: none` — für D-Pad-Navigation brauchen wir stattdessen einen sichtbaren Focus-Ring. Ändern zu:
|
||||
|
||||
```css
|
||||
.tv-ep-tile-mark:hover, .tv-ep-tile-mark:focus {
|
||||
border-color: var(--accent);
|
||||
color: var(--accent);
|
||||
opacity: 1;
|
||||
}
|
||||
.tv-ep-tile-mark:focus {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Manuell testen**
|
||||
|
||||
Browser → Serien-Detail → D-Pad auf Episode-Card → dann D-Pad hoch/links zum Mark-Button → Enter drückt "Gesehen". Prüfen:
|
||||
1. Button ist leicht sichtbar (auf TV/touch)
|
||||
2. Button bekommt Focus-Ring bei D-Pad-Navigation
|
||||
3. Enter toggled gesehen/ungesehen
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add video-konverter/app/templates/tv/series_detail.html video-konverter/app/static/tv/css/tv.css
|
||||
git commit -m "fix(tv): Gesehen-Button per D-Pad fokussierbar, auf TV sichtbar"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: "Serie als gesehen markieren"-Button
|
||||
|
||||
**Files:**
|
||||
- Modify: `video-konverter/app/templates/tv/series_detail.html:57-66` (HTML)
|
||||
- Modify: `video-konverter/app/templates/tv/series_detail.html` (JS am Ende des Script-Blocks)
|
||||
- Modify: `video-konverter/app/static/tv/css/tv.css` (neues Styling)
|
||||
|
||||
- [ ] **Step 1: HTML — Button im Header-Aktionsbereich einfügen**
|
||||
|
||||
In `series_detail.html`, nach dem Watchlist-Button (Zeile 65, vor `</div>` auf Zeile 66), einfügen:
|
||||
|
||||
```html
|
||||
<!-- Serie als gesehen markieren -->
|
||||
<button class="tv-mark-series-btn"
|
||||
id="btn-mark-series"
|
||||
data-focusable
|
||||
data-series-id="{{ series.id }}"
|
||||
onclick="markSeriesWatched(this)">
|
||||
<span class="mark-series-icon">✓</span>
|
||||
<span class="mark-series-text">{{ t('status.mark_series') }}</span>
|
||||
</button>
|
||||
```
|
||||
|
||||
- [ ] **Step 2: JavaScript — `markSeriesWatched()` Funktion hinzufügen**
|
||||
|
||||
In `series_detail.html`, im `<script>`-Block nach der `toggleWatched()`-Funktion (nach Zeile 255), einfügen:
|
||||
|
||||
```javascript
|
||||
function markSeriesWatched(btn) {
|
||||
// Alle ungesehenen Episoden aus ALLEN Staffeln sammeln
|
||||
const allCards = document.querySelectorAll('.tv-episode-tile:not(.tv-ep-seen)');
|
||||
const ids = [];
|
||||
allCards.forEach(function(card) {
|
||||
var vid = card.dataset.videoId;
|
||||
if (vid) ids.push(parseInt(vid));
|
||||
});
|
||||
if (ids.length === 0) return;
|
||||
|
||||
// Batch-Request an API (gleiche Methode wie markSeasonWatched)
|
||||
Promise.all(ids.map(function(id) {
|
||||
return fetch('/tv/api/watch-progress', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ video_id: id, position_sec: 100, duration_sec: 100 }),
|
||||
});
|
||||
})).then(function() {
|
||||
// Alle Episoden-Cards als gesehen markieren
|
||||
document.querySelectorAll('.tv-episode-tile').forEach(function(card) {
|
||||
card.classList.add('tv-ep-seen');
|
||||
var markBtn = card.querySelector('.tv-ep-tile-mark');
|
||||
if (markBtn) markBtn.classList.add('active');
|
||||
var thumb = card.querySelector('.tv-ep-thumb');
|
||||
if (thumb && !thumb.querySelector('.tv-ep-watched')) {
|
||||
var check = document.createElement('div');
|
||||
check.className = 'tv-ep-watched';
|
||||
check.innerHTML = '✓';
|
||||
thumb.appendChild(check);
|
||||
}
|
||||
});
|
||||
// Alle Staffel-Tabs als komplett markieren
|
||||
document.querySelectorAll('.tv-tab').forEach(function(tab) {
|
||||
if (!tab.classList.contains('tv-tab-complete')) {
|
||||
tab.classList.add('tv-tab-complete');
|
||||
if (!tab.querySelector('.tv-tab-check')) {
|
||||
var check = document.createElement('span');
|
||||
check.className = 'tv-tab-check';
|
||||
check.innerHTML = ' ✓';
|
||||
tab.appendChild(check);
|
||||
}
|
||||
}
|
||||
});
|
||||
// Button-Zustand aendern
|
||||
btn.classList.add('active');
|
||||
}).catch(function() {});
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: CSS — Styling für `.tv-mark-series-btn`**
|
||||
|
||||
In `tv.css`, am Ende der Serien-Detail-Styles (vor den Episode-Grid-Styles, ca. Zeile 1815), einfügen:
|
||||
|
||||
```css
|
||||
/* Serie als gesehen markieren - Button */
|
||||
.tv-mark-series-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
padding: 0.5rem 1rem;
|
||||
background: transparent;
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text);
|
||||
border-radius: var(--radius);
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
transition: background 0.2s, border-color 0.2s;
|
||||
}
|
||||
.tv-mark-series-btn:hover,
|
||||
.tv-mark-series-btn:focus {
|
||||
background: var(--bg-hover);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
.tv-mark-series-btn.active {
|
||||
background: var(--accent);
|
||||
color: #000;
|
||||
border-color: var(--accent);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Manuell testen**
|
||||
|
||||
Browser → Serien-Detail-Seite:
|
||||
1. Button "Serie als gesehen" ist im Header sichtbar
|
||||
2. D-Pad kann den Button fokussieren
|
||||
3. Enter markiert alle Episoden aller Staffeln als gesehen
|
||||
4. Alle Staffel-Tabs zeigen Häkchen
|
||||
5. Button ändert Farbe zu accent
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add video-konverter/app/templates/tv/series_detail.html video-konverter/app/static/tv/css/tv.css
|
||||
git commit -m "feat(tv): Serie als gesehen markieren - Button im Header"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 6: Finaler Test & Gesamtcommit
|
||||
|
||||
- [ ] **Step 1: Alle Änderungen zusammen testen**
|
||||
|
||||
Checkliste:
|
||||
- [ ] Serien-Seite: Alphabet zeigt nur verfügbare Buchstaben
|
||||
- [ ] Serien-Seite: D-Pad rechts → Sidebar, links → zurück, Enter → filtert
|
||||
- [ ] Serien-Detail: Episoden-Cards größer, Laufzeit vollständig sichtbar
|
||||
- [ ] Serien-Detail: D-Pad kann Gesehen-Button auf Cards fokussieren + Enter togglen
|
||||
- [ ] Serien-Detail: "Serie als gesehen"-Button funktioniert
|
||||
|
||||
- [ ] **Step 2: Docker-Image bauen und taggen**
|
||||
|
||||
```bash
|
||||
cd "/mnt/17 - Entwicklungen/20 - Projekte/VideoKonverter"
|
||||
PUID=1000 PGID=1000 docker compose --profile cpu build
|
||||
```
|
||||
Loading…
Reference in a new issue