release: 5.0.14 — Command Palette Localization + Klarere Hints

Aus dem 2. Screenshot-Pass (scripts/ui-screenshot2.js — Modals, Hover,
responsive):

- Command Palette war teilweise hardcoded Deutsch: Input-Placeholder
  'Suche Befehl...' + Hint-Zeile 'Up/Down zum Navigieren, Enter zum
  Ausfuehren, Esc zum Schliessen'. Im EN-Mode zeigte das trotzdem
  Deutsch. Jetzt ueber Locale-Keys (commandPaletteSearchPlaceholder +
  commandPaletteHint) verdrahtet — zeigt 'Search command...' /
  'Up/Down to navigate, Enter to run, Esc to close' im EN-Mode.

- Command-Hint von 'Tab' auf 'Open' geaendert — 'Tab' las sich wie eine
  Tastatur-Taste statt 'oeffnet diesen Tab'.

Verifiziert: Trim-Modal, Merge/Clips/Archive/Stats Tabs, alle 5 Themes,
Light-Theme Inputs — alles sauber. minWidth:1200 -> sub-1200px responsive
ist kein realer Fall.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
xRangerDE 2026-05-23 17:07:27 +02:00
parent 951158fe5a
commit 7ef1ce1a6f
8 changed files with 109 additions and 11 deletions

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "twitch-vod-manager", "name": "twitch-vod-manager",
"version": "5.0.13", "version": "5.0.14",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "twitch-vod-manager", "name": "twitch-vod-manager",
"version": "5.0.13", "version": "5.0.14",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"axios": "^1.16.1", "axios": "^1.16.1",

View File

@ -1,6 +1,6 @@
{ {
"name": "twitch-vod-manager", "name": "twitch-vod-manager",
"version": "5.0.13", "version": "5.0.14",
"description": "Twitch VOD Manager - Download Twitch VODs easily", "description": "Twitch VOD Manager - Download Twitch VODs easily",
"main": "dist/main.js", "main": "dist/main.js",
"author": "xRangerDE", "author": "xRangerDE",

90
scripts/ui-screenshot2.js Normal file
View File

@ -0,0 +1,90 @@
// UI-Screenshot Teil 2 — Modals, Hover-States, schmales Fenster, Queue mit
// aktivem Download. Ergaenzt ui-screenshot.js fuer die selteneren UI-Pfade.
const { _electron: electron } = require('playwright');
const fs = require('fs');
const path = require('path');
const OUT_DIR = path.join(process.cwd(), 'tmp_ui_shots');
async function run() {
fs.mkdirSync(OUT_DIR, { recursive: true });
const electronPath = require('electron');
const app = await electron.launch({ executablePath: electronPath, args: ['.'], cwd: process.cwd() });
const win = await app.firstWindow();
await win.setViewportSize({ width: 1280, height: 900 });
await win.waitForTimeout(2500);
const shot = async (name) => {
try { await win.screenshot({ path: path.join(OUT_DIR, `${name}.png`), animations: 'disabled', timeout: 8000 }); process.stdout.write(`shot: ${name}\n`); }
catch (e) { process.stdout.write(`shot FAILED: ${name}${String(e).split('\n')[0]}\n`); }
};
// VODs mit xqc laden fuer Modal-Kontext
await win.evaluate(() => window.showTab('vods'));
await win.evaluate(async () => { await window.selectStreamer('xqc'); });
await win.waitForTimeout(4000);
// 1. Clip/Trim-Dialog Modal (Trim VOD auf erster Card)
const trimBtn = win.locator('.vod-card .vod-btn.secondary').first();
if (await trimBtn.count()) {
await trimBtn.click().catch(() => {});
await win.waitForTimeout(800);
await shot('07-modal-trim');
await win.keyboard.press('Escape');
await win.waitForTimeout(400);
}
// 2. Command Palette (Ctrl+K)
await win.keyboard.press('Control+K');
await win.waitForTimeout(500);
await shot('08-command-palette');
// Tippe etwas zum Filtern
await win.keyboard.type('set');
await win.waitForTimeout(300);
await shot('08-command-palette-filtered');
await win.keyboard.press('Escape');
await win.waitForTimeout(300);
// 3. Template Guide Modal (Settings -> Template-Guide oeffnen falls Button da)
await win.evaluate(() => window.showTab('settings'));
await win.waitForTimeout(500);
const tgOpened = await win.evaluate(() => {
if (typeof window.openTemplateGuide === 'function') { window.openTemplateGuide(); return true; }
return false;
});
if (tgOpened) {
await win.waitForTimeout(700);
await shot('09-template-guide');
await win.keyboard.press('Escape');
await win.waitForTimeout(400);
}
// 4. VOD-Card Hover (Storyboard-Preview)
await win.evaluate(() => window.showTab('vods'));
await win.waitForTimeout(800);
const firstCard = win.locator('.vod-card').first();
if (await firstCard.count()) {
await firstCard.hover();
await win.waitForTimeout(1200); // debounce + storyboard fetch
await shot('10-vod-hover');
}
// 5. Schmales Fenster (responsive) — 760px
await win.setViewportSize({ width: 760, height: 900 });
await win.waitForTimeout(800);
await shot('11-narrow-vods');
await win.evaluate(() => window.showTab('settings'));
await win.waitForTimeout(500);
await shot('11-narrow-settings');
// 6. Sehr schmal — 520px
await win.setViewportSize({ width: 520, height: 800 });
await win.evaluate(() => window.showTab('vods'));
await win.waitForTimeout(800);
await shot('12-tiny-vods');
await app.close();
process.stdout.write(`\nDone part 2.\n`);
}
run().catch((e) => { process.stderr.write(String(e) + '\n'); process.exit(1); });

View File

@ -821,10 +821,10 @@
placeholder="Suche Befehl..." placeholder="Suche Befehl..."
autocomplete="off" autocomplete="off"
spellcheck="false" spellcheck="false"
aria-label="Command Palette Suche" aria-label="Command Palette"
/> />
<ul id="commandPaletteList" class="cp-list" role="listbox" aria-label="Command results"></ul> <ul id="commandPaletteList" class="cp-list" role="listbox" aria-label="Command results"></ul>
<p class="cp-hint">Up/Down zum Navigieren, Enter zum Ausfuhren, Esc zum Schliessen</p> <p class="cp-hint" id="commandPaletteHint">Up/Down zum Navigieren, Enter zum Ausfuehren, Esc zum Schliessen</p>
</div> </div>
</div> </div>

View File

@ -28,13 +28,15 @@ interface PaletteCommand {
return []; return [];
} }
// hint 'Open' statt 'Tab' — 'Tab' las sich wie eine Tastatur-Taste
// ('druecke Tab') statt 'oeffnet diesen Tab'.
const tabs: Array<{ id: string; labels: string[]; hint: string }> = [ const tabs: Array<{ id: string; labels: string[]; hint: string }> = [
{ id: 'vods', labels: ['VODs', 'videos', 'streams'], hint: 'Tab' }, { id: 'vods', labels: ['VODs', 'videos', 'streams'], hint: 'Open' },
{ id: 'queue', labels: ['Queue', 'downloads', 'warteschlange'], hint: 'Tab' }, { id: 'queue', labels: ['Queue', 'downloads', 'warteschlange'], hint: 'Open' },
{ id: 'streamers', labels: ['Streamers', 'channels'], hint: 'Tab' }, { id: 'streamers', labels: ['Streamers', 'channels'], hint: 'Open' },
{ id: 'stats', labels: ['Stats', 'statistiken', 'dashboard'], hint: 'Tab' }, { id: 'stats', labels: ['Stats', 'statistiken', 'dashboard'], hint: 'Open' },
{ id: 'archive', labels: ['Archive', 'archiv'], hint: 'Tab' }, { id: 'archive', labels: ['Archive', 'archiv'], hint: 'Open' },
{ id: 'settings', labels: ['Settings', 'einstellungen', 'config'], hint: 'Tab' }, { id: 'settings', labels: ['Settings', 'einstellungen', 'config'], hint: 'Open' },
]; ];
const tabCommands: PaletteCommand[] = tabs.map(t => ({ const tabCommands: PaletteCommand[] = tabs.map(t => ({

View File

@ -22,6 +22,8 @@ const UI_TEXT_DE = {
cutterSelectTitle: 'Video auswahlen', cutterSelectTitle: 'Video auswahlen',
cutterPreviewPlaceholder: 'Video auswahlen um Vorschau zu sehen', cutterPreviewPlaceholder: 'Video auswahlen um Vorschau zu sehen',
cutterBrowse: 'Durchsuchen', cutterBrowse: 'Durchsuchen',
commandPaletteSearchPlaceholder: 'Befehl suchen...',
commandPaletteHint: 'Up/Down zum Navigieren, Enter zum Ausfuehren, Esc zum Schliessen',
mergeTitle: 'Videos zusammenfugen', mergeTitle: 'Videos zusammenfugen',
mergeDesc: 'Wahle mehrere Videos aus, um sie zu einem Video zusammenzufugen. Die Reihenfolge kann geandert werden.', mergeDesc: 'Wahle mehrere Videos aus, um sie zu einem Video zusammenzufugen. Die Reihenfolge kann geandert werden.',
mergeAdd: '+ Videos hinzufugen', mergeAdd: '+ Videos hinzufugen',

View File

@ -22,6 +22,8 @@ const UI_TEXT_EN = {
cutterSelectTitle: 'Select video', cutterSelectTitle: 'Select video',
cutterPreviewPlaceholder: 'Select a video to see a preview', cutterPreviewPlaceholder: 'Select a video to see a preview',
cutterBrowse: 'Browse', cutterBrowse: 'Browse',
commandPaletteSearchPlaceholder: 'Search command...',
commandPaletteHint: 'Up/Down to navigate, Enter to run, Esc to close',
mergeTitle: 'Merge videos', mergeTitle: 'Merge videos',
mergeDesc: 'Select multiple videos to merge into one file. You can change the order before merging.', mergeDesc: 'Select multiple videos to merge into one file. You can change the order before merging.',
mergeAdd: '+ Add videos', mergeAdd: '+ Add videos',

View File

@ -120,6 +120,8 @@ function applyLanguageToStaticUI(): void {
setText('cutterSelectTitle', UI_TEXT.static.cutterSelectTitle); setText('cutterSelectTitle', UI_TEXT.static.cutterSelectTitle);
setText('cutterPreviewPlaceholder', UI_TEXT.static.cutterPreviewPlaceholder); setText('cutterPreviewPlaceholder', UI_TEXT.static.cutterPreviewPlaceholder);
setText('cutterBrowseBtn', UI_TEXT.static.cutterBrowse); setText('cutterBrowseBtn', UI_TEXT.static.cutterBrowse);
setPlaceholder('commandPaletteInput', UI_TEXT.static.commandPaletteSearchPlaceholder);
setText('commandPaletteHint', UI_TEXT.static.commandPaletteHint);
setText('cutterInfoDurationLabel', UI_TEXT.cutter.infoDuration); setText('cutterInfoDurationLabel', UI_TEXT.cutter.infoDuration);
setText('cutterInfoResolutionLabel', UI_TEXT.cutter.infoResolution); setText('cutterInfoResolutionLabel', UI_TEXT.cutter.infoResolution);
setText('cutterInfoFpsLabel', UI_TEXT.cutter.infoFps); setText('cutterInfoFpsLabel', UI_TEXT.cutter.infoFps);