feat(v5): Etappe W-0 — das Geruest steht

Electron 44 + TypeScript, drei Prozesse (haupt / kern als utilityProcess /
fenster mit React), Nachrichten-Schema mit Pruef-Funktionen, node:sqlite
als einzige Datenbank-Stelle (kern/speicher/db.ts, § 6.7), Prozess-Leine
im Haupt (Job Object aus beweise/leine.js — Begruendung fuer den Ort im
Dateikopf), MessagePort direkt Fenster<->Kern, Einzelinstanz-Sperre,
Kern-Neustart-Wache, strikte CSP im gebauten Fenster.

Wächter-Tests nach § 4.3: koffi nur an zwei benannten Orten (R1), kein
leerer catch und kein catch-mit-Leerwert (R2/R4), kein HTTP-Server
(§ 4.1), node:sqlite nur in db.ts (§ 6.7). Dazu Datenbank- und
Schema-Tests: 18/18 gruen, Typpruefung in drei Kontexten.

Der W-0-Beweis (npm run smoke, echtes Programm):
SMOKE: OK — fenster=geladen kern=pid:13676,node:24.18.1 datenbank=ok
leine=gesetzt pong=ok version=5.0.0-w0

Nachgemessen ausserdem: taskkill /F auf den Haupt-Prozess (ohne /T,
der Absturz-Fall aus rc10) — der Kern stirbt mit.

Bau-Fallen dokumentiert (BAUEN.md): npm 11 blockt Electrons
Install-Skript (§ 3.5), plugin-react 6 verlangt Vite 8 (deshalb 5.2),
TypeScript bewusst auf 5.9.3 gepinnt statt tsgo 7.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-08-30 16:53:30 +02:00
co-authored by Claude Fable 5
parent 564bd17f84
commit c539e65cd2
27 changed files with 5207 additions and 0 deletions
+51
View File
@@ -0,0 +1,51 @@
// Die Brücke (Preload) — läuft im Fenster-Prozess mit contextIsolation.
// Sie reicht GENAU DREI Dinge in die Seite, mehr nicht:
// 1. den MessagePort zum Kern (Muster aus der Electron-Doku
// »MessagePorts in Electron«: window.postMessage mit Transfer über die
// Kontextgrenze — contextBridge kann keine Ports übertragen)
// 2. den Haupt-Status (Kanal 'haupt-status')
// 3. rippy.melden(art) für kurze Rückmeldungen ans Haupt
//
// Beides wird GEPUFFERT: Das Haupt schickt Port und Status direkt nach
// did-finish-load — ob die React-Seite ihre Horcher da schon registriert
// hat, ist Timing. Ein Port, der vor dem Horcher eintrifft, wäre sonst
// stumm verloren (R2: nichts verlieren, nur weil eine Nachricht früh kam).
import { contextBridge, ipcRenderer, type IpcRendererEvent } from 'electron'
let seiteBereit = false
const wartendePorts: IpcRendererEvent[] = []
ipcRenderer.on('kern-port', (ereignis) => {
if (seiteBereit) {
window.postMessage({ art: 'kern-port' }, '*', ereignis.ports)
} else {
wartendePorts.push(ereignis)
}
})
let letzterStatus: unknown = null
const statusHorcher = new Set<(status: unknown) => void>()
ipcRenderer.on('haupt-status', (_ereignis, status: unknown) => {
letzterStatus = status
for (const horcher of statusHorcher) horcher(status)
})
contextBridge.exposeInMainWorld('rippy', {
/** Die Seite meldet: Mein 'message'-Horcher steht — gepufferte Ports jetzt. */
bereit(): void {
seiteBereit = true
for (const ereignis of wartendePorts.splice(0)) {
window.postMessage({ art: 'kern-port' }, '*', ereignis.ports)
}
},
/** Haupt-Status abonnieren; der letzte bekannte Stand kommt sofort. */
aufStatus(horcher: (status: unknown) => void): void {
statusHorcher.add(horcher)
if (letzterStatus !== null) horcher(letzterStatus)
},
/** Kurze Rückmeldung ans Haupt (z. B. 'kern-pong' im Smoke-Beweis). */
melden(art: string): void {
ipcRenderer.send('fenster-meldung', String(art))
},
})