feat(v5): Ein Smoke-Beweis, der KLICKT — Playwright durch das echte Programm
WAS: `npm run klick` startet das ECHTE Rippy (Electron, Haupt, Kern, Fenster, Datenbank) mit eigenem Profil (--profil, --klick-smoke: ohne Tray, Updater, Toasts), einem nachgebauten Laufwerk (RIPPY_FAKE_LAUFWERK) und nachgebauten Werkzeugen (bau/klick/fake-*.cjs) — und klickt sich durch: Rippen → Dialog → Sprachen je Titel (jpn statt deu) → Rollen auf Folge → Staffel 1, ab Folge 3 → Vorschau → Rip → Warteschlange → Kompression → Ablage als S01E03/S01E04 unter Serien\…\Season 01 → Einstellungen/Roh-Dateien → Roh loeschen → Ereignisse. Bilder liegen in beweise/klick/. Die Naht dafuer (kern/werkzeuge/aufruf.ts): Zeigt ein Werkzeug-Pfad auf ein Node-Skript, laeuft es ueber die eigene Laufzeit (ELECTRON_RUN_AS_NODE) — fuer EXE-Pfade aendert sich nichts. WARUM: Beide Fehler vom 01.09.2026 waren Verkabelung; 261 Unit-Tests sahen keinen. Der Klick-Beweis haette beide gefunden. NEBENBEFUND: Die CSP blockte eingebettete Schrift-Teile (data:-Fonts der fontsource-Pakete) — Konsolenfehler im gebauten Fenster, jetzt font-src. Dazu test/messung.spuren.test.ts: HandBrake-Scan einer echten Roh-Datei auf Verlangen (RIPPY_MESSUNG_MKV). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
e3ce8220f1
commit
6dd1b0bc7a
@@ -20,10 +20,12 @@ import {
|
||||
istHauptNachricht,
|
||||
type DbStatus,
|
||||
type DiscInfoStand,
|
||||
type GeraeteInfo,
|
||||
type KernNachricht,
|
||||
type MetaVorschlag,
|
||||
type VorgaengeStand,
|
||||
type VorgangEintrag,
|
||||
type WacheEreignis,
|
||||
} from '../gemeinsam/nachrichten'
|
||||
import { komprimierenUndAblegen, type AblageUmgebung } from './ablauf/ablegen'
|
||||
import { auftragAusJson, auftragZuJson } from './ablauf/auftrag'
|
||||
@@ -260,8 +262,54 @@ async function zuordnungSetzen(id: string, vorschlag: MetaVorschlag): Promise<vo
|
||||
}
|
||||
}
|
||||
|
||||
// Das nachgebaute Laufwerk des Klick-Beweises (bau/klick-smoke.mjs): Ein
|
||||
// Buchstabe, den es nicht gibt (Q) — die Disc-Merkmale finden dann nichts,
|
||||
// und makemkvcon/HandBrake sind Node-Skripte (werkzeuge/aufruf.ts). Leer im
|
||||
// normalen Betrieb; die Win32-Schicht bleibt dann der einzige Weg.
|
||||
const FAKE_LAUFWERK = (process.env['RIPPY_FAKE_LAUFWERK'] ?? '').replace(/:$/, '').toUpperCase()
|
||||
|
||||
function fakeGeraet(): GeraeteInfo {
|
||||
return {
|
||||
id: FAKE_LAUFWERK,
|
||||
name: `Laufwerk ${FAKE_LAUFWERK}:`,
|
||||
typ: 'bluray',
|
||||
pfad: `\\\\.\\${FAKE_LAUFWERK}:`,
|
||||
status: 'ready',
|
||||
modell: 'NACHGEBAUT (KLICK-BEWEIS)',
|
||||
seriennummer: 'KLICK',
|
||||
groesseBytes: 33_759_100_928,
|
||||
grund: '',
|
||||
}
|
||||
}
|
||||
|
||||
function wacheMeldung(meldung: { laufwerke: GeraeteInfo[]; ereignisse: WacheEreignis[] }): void {
|
||||
anHauptUndFenster({ art: 'laufwerke', liste: meldung.laufwerke, ereignisse: meldung.ereignisse })
|
||||
for (const ereignis of meldung.ereignisse) {
|
||||
if (ereignis.art === 'disc-eingelegt') {
|
||||
void discErmitteln(ereignis.geraet.id, ereignis.geraet.pfad, ereignis.geraet.groesseBytes)
|
||||
} else if (ereignis.art === 'disc-entnommen') {
|
||||
discZuordnungen.delete(ereignis.geraet.id)
|
||||
discFingerprints.delete(ereignis.geraet.id)
|
||||
discMerkmale.delete(ereignis.geraet.id)
|
||||
anHauptUndFenster({ art: 'disc-info', info: discInfoStand(ereignis.geraet.id, null) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function wacheFehler(fehler: unknown): void {
|
||||
// R4: Eine gescheiterte Runde ist eine laute Zeile — der letzte
|
||||
// Stand im Fenster bleibt stehen (R2), die Wache läuft weiter.
|
||||
console.error(`[kern] Disc-Wache: Runde fehlgeschlagen: ${String(fehler)}`)
|
||||
anFenster({ art: 'kern-fehler', text: `Disc-Wache: ${String(fehler)}` })
|
||||
}
|
||||
|
||||
const laufwerkeBereit: Promise<void> = (async () => {
|
||||
try {
|
||||
if (FAKE_LAUFWERK.length > 0) {
|
||||
wache = new DiscWache(() => [fakeGeraet()], wacheMeldung, wacheFehler)
|
||||
wache.starten()
|
||||
return
|
||||
}
|
||||
if (process.platform !== 'win32') {
|
||||
throw new Error('Die Laufwerks-Schicht läuft nur unter Windows (KONZEPT § 14).')
|
||||
}
|
||||
@@ -269,25 +317,8 @@ const laufwerkeBereit: Promise<void> = (async () => {
|
||||
laufwerkApi = win32Api()
|
||||
wache = new DiscWache(
|
||||
() => listOptischeLaufwerke(laufwerkApi!).map((g) => geraeteInfo(laufwerkApi!, g)),
|
||||
(meldung) => {
|
||||
anHauptUndFenster({ art: 'laufwerke', liste: meldung.laufwerke, ereignisse: meldung.ereignisse })
|
||||
for (const ereignis of meldung.ereignisse) {
|
||||
if (ereignis.art === 'disc-eingelegt') {
|
||||
void discErmitteln(ereignis.geraet.id, ereignis.geraet.pfad, ereignis.geraet.groesseBytes)
|
||||
} else if (ereignis.art === 'disc-entnommen') {
|
||||
discZuordnungen.delete(ereignis.geraet.id)
|
||||
discFingerprints.delete(ereignis.geraet.id)
|
||||
discMerkmale.delete(ereignis.geraet.id)
|
||||
anHauptUndFenster({ art: 'disc-info', info: discInfoStand(ereignis.geraet.id, null) })
|
||||
}
|
||||
}
|
||||
},
|
||||
(fehler) => {
|
||||
// R4: Eine gescheiterte Runde ist eine laute Zeile — der letzte
|
||||
// Stand im Fenster bleibt stehen (R2), die Wache läuft weiter.
|
||||
console.error(`[kern] Disc-Wache: Runde fehlgeschlagen: ${String(fehler)}`)
|
||||
anFenster({ art: 'kern-fehler', text: `Disc-Wache: ${String(fehler)}` })
|
||||
},
|
||||
wacheMeldung,
|
||||
wacheFehler,
|
||||
)
|
||||
wache.starten()
|
||||
} catch (fehler) {
|
||||
@@ -296,6 +327,14 @@ const laufwerkeBereit: Promise<void> = (async () => {
|
||||
}
|
||||
})()
|
||||
|
||||
/** Der Geräte-Stand zu einer Kennung — echt oder nachgebaut. */
|
||||
function geraetInfoVon(id: string): GeraeteInfo | null {
|
||||
if (FAKE_LAUFWERK.length > 0) return id === FAKE_LAUFWERK ? fakeGeraet() : null
|
||||
const geraet = geraetVon(id)
|
||||
if (geraet === null || laufwerkApi === null) return null
|
||||
return geraeteInfo(laufwerkApi, geraet)
|
||||
}
|
||||
|
||||
// ── Titel der Disc lesen (§ 6.2) ────────────────────────────────────────
|
||||
// Der Info-Lauf dauert 20–120 s — deshalb geschieht er erst auf Zuruf und
|
||||
// nicht schon beim Einlegen (der 30.08.-Fund: „das Erkennen dauert sehr
|
||||
@@ -316,12 +355,12 @@ async function titelLesen(id: string): Promise<void> {
|
||||
anFenster({ art: 'disc-titel', stand: { ...leer, laeuft: false, fehler: 'Auf diesem Laufwerk läuft schon ein Vorgang.' } })
|
||||
return
|
||||
}
|
||||
const geraet = geraetVon(id)
|
||||
if (geraet === null || laufwerkApi === null) {
|
||||
const geraet = geraetInfoVon(id)
|
||||
if (geraet === null) {
|
||||
anFenster({ art: 'disc-titel', stand: { ...leer, laeuft: false, fehler: `Laufwerk ${id}: gibt es nicht.` } })
|
||||
return
|
||||
}
|
||||
const geraetepfad = geraeteInfo(laufwerkApi, geraet).pfad
|
||||
const geraetepfad = geraet.pfad
|
||||
const programm = finden('makemkv', db?.einstellungLesen('werkzeug.makemkv') ?? '')
|
||||
if (programm.length === 0) {
|
||||
anFenster({
|
||||
@@ -751,11 +790,7 @@ const ripVerwaltung = new Pipeline({
|
||||
if (laufwerkApi === null || geraet === null) throw new Error(`Laufwerk ${id}: gibt es nicht`)
|
||||
return audioSpurLesen(laufwerkApi, geraet, start, sektoren, schreiben, fortschritt)
|
||||
},
|
||||
geraet: (id) => {
|
||||
if (laufwerkApi === null) return null
|
||||
const geraet = listOptischeLaufwerke(laufwerkApi).find((g) => kennung(g) === id)
|
||||
return geraet === undefined ? null : geraeteInfo(laufwerkApi, geraet)
|
||||
},
|
||||
geraet: geraetInfoVon,
|
||||
ablage: ablageOrdner,
|
||||
einstellungen: einstellungenLaden,
|
||||
melden: (status) => {
|
||||
|
||||
Reference in New Issue
Block a user