Ampel / ampel (push) Successful in 1m35s
Vier Punkte des Commanders (01.09.2026):
1. "Ich hab noch den rand ich glaube der kommt aber von windows selbst"
Stimmt. Ein rahmenloses Fenster behaelt unter Windows 11 den DWM-Rand
— eine feine Linie, die der Fenstermanager zeichnet, nicht die Seite;
mit CSS ist da nichts zu holen. Neu fensterrand.ts:
DwmSetWindowAttribute mit DWMWA_BORDER_COLOR = DWMWA_COLOR_NONE.
Gemessen: HRESULT 0, Windows nimmt es an. Runde Ecken bleiben (sie
sind unter Windows 11 das Uebliche); ein Schalter fuer kantig ist da.
Aeltere Windows antworten mit Fehler — der wird als Wert
zurueckgegeben und protokolliert, nicht verschluckt (R4); dort gibt es
den Rand schlicht nicht.
Das ist der DRITTE erlaubte koffi-Ort. Waechter R1 kennt jetzt drei
statt zwei Dateien; die Regel selbst ("Win32 nur an benannten Orten")
bleibt und haelt alles andere weiter dicht.
2. "Die alte Version konnte automatisch anhand der gescannten hardware
encoder das beste preset waehlen."
Das KANN diese auch — presetEmpfehlung() waehlt nach den gemessenen
Backends (VCN -> NVENC -> QSV -> CPU) und laeuft seit dem Kino-Mix.
Man sah es nur nirgends ausser als Wort "automatisch:" in einem
Auswahlfeld. Die Erst-Einrichtung zeigt jetzt die gemessenen Encoder
als Badges und was daraus fuer DVD, Blu-ray und 4K wird — plus eine
ehrliche Warnung, wenn nur CPU-Encoder da sind.
3. Feld "Update-Adresse" ist raus. Der Schluessel updateUrl gilt
weiterhin (Datenbank), Paragraph 6.8 prueft ihn wie zuvor.
4. Erst-Einrichtung geprueft. Gefunden und behoben:
* "Dieses Setup stellt vier Dinge ein" — es stellte ZWEI ein.
* Der Ablage-Schritt zeigte eine LEERE Zeile, wenn nichts gewaehlt
war. Es gibt aber eine Vorgabe (Videos\Rippy im Benutzerprofil) —
die steht jetzt da.
* Kein Wort zum TMDb-Schluessel, obwohl Schritt 1 "Rippy erkennt, was
drauf ist" verspricht. Ohne Schluessel heissen Filme wie das
Disc-Label. Jetzt ein eigener Schritt mit Eingabefeld.
* Rohe HTML-Checkbox statt des Schalters aus dem Kino-Mix.
* Kein Hinweis, dass Rippy im Infobereich weiterlebt.
Neu sechs Schritte, drei Fragen — und die Zahl im Text stimmt.
Dazu: #einrichtung als Beweis-Anker (RIPPY_START_BEREICH), sonst ist
der Wizard nach dem ersten Start nicht mehr zu fotografieren und
damit nicht mehr pruefbar. Beweis: beweise/ui-515-wizard.png
237 Tests gruen, Typpruefung sauber. Version 5.1.5.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
119 lines
4.8 KiB
TypeScript
119 lines
4.8 KiB
TypeScript
// Die Wächter — mechanische Prüfung der Regeln aus KONZEPT-WINDOWS.md § 4.3.
|
|
// Sie LESEN Quelltext, sie führen nichts aus. Jede Regel ist aus einem
|
|
// bezahlten Fehler abgeleitet; die Fundstelle steht jeweils dabei.
|
|
import { readdirSync, readFileSync, statSync } from 'node:fs'
|
|
import { join, relative, sep } from 'node:path'
|
|
import { describe, expect, it } from 'vitest'
|
|
|
|
const WURZEL = join(__dirname, '..')
|
|
const SRC = join(WURZEL, 'src')
|
|
|
|
function quelldateien(ordner: string): string[] {
|
|
const ergebnis: string[] = []
|
|
for (const name of readdirSync(ordner)) {
|
|
const pfad = join(ordner, name)
|
|
if (statSync(pfad).isDirectory()) {
|
|
ergebnis.push(...quelldateien(pfad))
|
|
} else if (/\.(ts|tsx)$/.test(name)) {
|
|
ergebnis.push(pfad)
|
|
}
|
|
}
|
|
return ergebnis
|
|
}
|
|
|
|
function relativPosix(pfad: string): string {
|
|
return relative(WURZEL, pfad).split(sep).join('/')
|
|
}
|
|
|
|
const dateien = quelldateien(SRC).map((pfad) => ({
|
|
pfad: relativPosix(pfad),
|
|
inhalt: readFileSync(pfad, 'utf8'),
|
|
}))
|
|
|
|
describe('R1 — Win32 lebt nur an den zwei benannten Orten', () => {
|
|
// § 4.3 R1: kein koffi-Import irgendwo sonst. Der zweite Ort ist die
|
|
// Prozess-Leine im Haupt — Begründung im Kopf von src/haupt/leine.ts:
|
|
// Das Job-Handle muss bei dem Prozess liegen, dessen Tod alles mitreißt.
|
|
// Der DRITTE (seit 01.09.2026) ist der Fensterrand: DwmSetWindowAttribute
|
|
// braucht das Fensterhandle und kann deshalb nur im Haupt liegen. Die
|
|
// Regel bleibt, was sie war — „Win32 nur an benannten Orten"; sie zählt
|
|
// jetzt drei statt zwei, und der Test hält alles andere weiterhin dicht.
|
|
const erlaubt = new Set([
|
|
'src/haupt/leine.ts',
|
|
'src/haupt/fensterrand.ts',
|
|
'src/kern/laufwerk/win32.ts',
|
|
])
|
|
|
|
it('koffi wird nirgendwo sonst angefasst', () => {
|
|
const verstoesse = dateien
|
|
.filter((d) => !erlaubt.has(d.pfad))
|
|
.filter((d) => /from\s+['"]koffi['"]|require\(\s*['"]koffi['"]\s*\)/.test(d.inhalt))
|
|
.map((d) => d.pfad)
|
|
expect(verstoesse).toEqual([])
|
|
})
|
|
})
|
|
|
|
describe('R2/R4 — kein Fehlschlag verschwindet still', () => {
|
|
it('kein leerer catch-Block', () => {
|
|
// R4: „Kein stilles catch {}" — ein except-pass hat einmal eine Stunde
|
|
// gekostet (AGENTS.md). Wer einen Fehler bewusst trägt, schreibt hin warum.
|
|
const verstoesse = dateien
|
|
.filter((d) => /catch\s*(\([^)]*\))?\s*\{\s*\}/.test(d.inhalt))
|
|
.map((d) => d.pfad)
|
|
expect(verstoesse).toEqual([])
|
|
})
|
|
|
|
it('kein catch, das leere Listen/Objekte als Antwort erfindet', () => {
|
|
// R2: Fünfmal catch(() => []) im alten UI — jeder verpasste Abruf hieß
|
|
// „es gibt keine Jobs", die Liste leerte sich im Sekundentakt.
|
|
const muster = /catch\s*(\([^)]*\))?\s*\{\s*return\s+(\[\s*\]|\{\s*\})/
|
|
const pfeilMuster = /catch\s*\(\s*\(\s*\)\s*=>\s*(\[\s*\]|\{\s*\})\s*\)/
|
|
const verstoesse = dateien
|
|
.filter((d) => muster.test(d.inhalt) || pfeilMuster.test(d.inhalt))
|
|
.map((d) => d.pfad)
|
|
expect(verstoesse).toEqual([])
|
|
})
|
|
})
|
|
|
|
describe('§ 4.1 — kein HTTP-Server, kein Port, kein localhost', () => {
|
|
it('niemand öffnet einen Server oder lauscht auf einem Port', () => {
|
|
// Ein lokaler Webserver in einer Einzelplatz-Anwendung ist eine offene
|
|
// Tür ohne Gegenwert (§ 4.1/§ 14). ELECTRON_RENDERER_URL (dev-Server von
|
|
// electron-vite) ist Werkzeug, kein Programmteil — der Treffer auf den
|
|
// reinen Variablennamen ist erlaubt.
|
|
const muster = /createServer\s*\(|\.listen\s*\(\s*\d|from\s+['"]express['"]|localhost:\d/
|
|
const verstoesse = dateien.filter((d) => muster.test(d.inhalt)).map((d) => d.pfad)
|
|
expect(verstoesse).toEqual([])
|
|
})
|
|
})
|
|
|
|
describe('Quelltext-Hygiene — keine rohen Steuerbytes', () => {
|
|
it('keine Datei enthält Steuerzeichen außer Tab/LF/CR', () => {
|
|
// Zweimal passiert (30.08.2026): Escape-Sequenzen wie \x00 landeten
|
|
// als ECHTE Bytes in der Datei — grep meldet „Binary file", Diffs
|
|
// leiden, und ein Test sah dadurch anders aus, als sein Quelltext las.
|
|
const testDateien = readdirSync(join(WURZEL, 'test'))
|
|
.filter((name) => /\.(ts|tsx)$/.test(name))
|
|
.map((name) => ({
|
|
pfad: `test/${name}`,
|
|
inhalt: readFileSync(join(WURZEL, 'test', name), 'utf8'),
|
|
}))
|
|
const verstoesse = [...dateien, ...testDateien]
|
|
.filter((d) => /[\x00-\x08\x0b\x0c\x0e-\x1f]/.test(d.inhalt))
|
|
.map((d) => d.pfad)
|
|
expect(verstoesse).toEqual([])
|
|
})
|
|
})
|
|
|
|
describe('§ 6.7 — EINE Quelle für Einstellungen', () => {
|
|
it('node:sqlite wird nur in kern/speicher/db.ts angefasst', () => {
|
|
// Der rc11-Fund: Oberfläche schrieb in die Datenbank, der Betrieb las
|
|
// aus einer Datei, die niemand schrieb. Hier ist die Datenbank EIN Modul.
|
|
const verstoesse = dateien
|
|
.filter((d) => d.pfad !== 'src/kern/speicher/db.ts')
|
|
.filter((d) => /['"]node:sqlite['"]/.test(d.inhalt))
|
|
.map((d) => d.pfad)
|
|
expect(verstoesse).toEqual([])
|
|
})
|
|
})
|