Files
Axel HocksandClaude Opus 5 71abb49cf7 Meldungen entschlackt, Bild-Modalbox, neues Muster, kleinere Trade-Schrift, M15 eingeklappt (v=207)
Fuenf User-Vorgaben in einem Durchgang.

(1) HYPERLIQUID + MUSTER + TRAILING AUS DER MELDUNGEN-KARTE. Die drei Divs sind
ENTFERNT, nicht nur versteckt; die Render-Bloecke bleiben stehen (null-sicher),
damit ein Zurueckholen eine reine HTML-Aenderung ist. Backend rechnet
unveraendert weiter (hl_ctx, patterns_tf, trail_info stehen im Snapshot).
Stufe F bekam eine dokumentierte Ausnahmeliste - sonst haette sie drei
Dauer-Treffer gemeldet, und eine Pruefung mit Dauer-Treffern wird ignoriert.

⚠⚠ DABEI ZWEI ECHTE ABSTUERZE GEFUNDEN - und der Weg dahin ist der Punkt.
`hl-note` wurde ueber das Auffang-`$` geholt; nach dem Entfernen des Elements
griffen ZWEI Zweige ungeschuetzt auf `hlEl.classList` zu. Im Browser ein
TypeError mitten im Render = das GESAMTE Dashboard friert auf Altwerten ein.
Die Render-Probe blieb zunaechst GRUEN - ihr eigener blinder Fleck: die
DOM-Attrappe lieferte fuer JEDE ID ein Element, `$streng` konnte darin also NIE
`null` zurueckgeben. Genau der Fall, fuer den es `$streng` gibt, war ungeprueft.
Behoben: die Attrappe kennt jetzt die echten IDs aus index.html
(`getElementById: id => HTML_IDS.has(id) ? el() : null`) - und meldete die
beiden Zweige im selben Lauf. Danach beide Waechter ergaenzt.

(2) BILD-MODALBOX. Klick auf ein Chartmuster oeffnet es gross.
⚠⚠ BEWUSST OHNE BIBLIOTHEK: ein Lightbox-Paket waere 15-40 KB plus ein zweites
Bedienkonzept fuer ~30 Zeilen Code. Das Dashboard laeuft nur ueber WireGuard,
ein CDN ist gar nicht erreichbar; das Projekt hat aus demselben Grund Tailwind
abgelehnt und am 05.08. die vendorte lightweight-charts (164 KB) entfernt.
Delegierter Klick auf `document`, weil die Kacheln bei JEDEM Render neu erzeugt
werden - ein Handler am <img> waere nach dem naechsten Snapshot weg.
Grosse Fassung (1408 px, ~133 KB) liegt getrennt unter muster/gross/ und wird
ERST beim Klick geladen; die Kachel bleibt bei 25 KB. Esc und Klick auf den
Hintergrund schliessen.

(3) NEUES BILD gescannt: "Inverse Tasse+Henkel.jpg". `inv_cup` hatte bis dahin
ausdruecklich KEIN Bild ("lieber keins als ein falsches") - jetzt zugeordnet.
Damit 16 Muster, keins ohne Datei, keine Datei ohne Zuordnung.

(4) SCHRIFT IN DER TRADE-LEISTE um 2 px (bezogen auf die 18-px-Referenz)
verkleinert - 4 Regeln: Ueberschrift, Feldbeschriftungen, Werte/Eingaben,
Knoepfe.
⚠ Erster Versuch nahm alles zwischen `.tb-title{` und `.mg-row` und traf 80
Regeln statt 4 - die halbe Oberflaeche. Zurueckgesetzt und namentlich
aufgezaehlt. Ein Zeilenbereich ist kein Selektor.

(5) M15-KARTE PER DEFAULT EINGEKLAPPT. Das `open` am ersten <details> ist weg.
⚠ Zusaetzlich der localStorage-Schluessel gebumpt (oil_m15_akk -> oil_m15_akk2):
bei jedem, der die Karte je aufgeklappt hatte, stand dort `true`, und das
gewinnt gegen den neuen Default. Ein neuer Schluessel setzt genau EINMAL
zurueck; danach merkt sich die Karte die eigene Wahl wie bisher.

Pipeline A-G gruen, 97 Tests, 14 render*-Funktionen (Fixture UND leerer
Snapshot), deployt und am ausgelieferten HTML/JS verifiziert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 22:27:18 +02:00

135 lines
5.9 KiB
JavaScript

// Render-Probe: laesst ALLE render*-Funktionen gegen ein Minimal-DOM laufen.
//
// ⚠⚠ WOZU. Ein `ReferenceError` mitten im Render bricht den GESAMTEN Durchlauf
// ab: das Dashboard friert auf Altwerten ein, Server und Snapshot sind dabei
// korrekt. `node --check` sieht das NICHT — die Datei ist syntaktisch einwandfrei.
// Real passiert:
// · 01.08. — ein direkter Zugriff auf ein noch nicht ausgeliefertes Element
// · 20.08. — die RSI-Zeile griff in `renderM15(s)` auf `d.market` zu
// · 20.08. — der Trailing-Block landete in `renderKontext(snap)` und las `d.…`
// Der dritte Fall waere durch die ALTE Fassung dieser Probe geschluepft: sie
// schnitt per Regex genau ZWEI Funktionen heraus (`renderM15`, `renderBrk`).
// `renderKontext` und 13 weitere waren nie abgedeckt.
//
// ⚠ Jetzt wird die KOMPLETTE `app.js` in einem VM-Kontext geladen und jede
// gefundene `render*`-Funktion aufgerufen — mit dem Fixture-Snapshot UND mit
// einem leeren Objekt (der Kaltstart-Fall, in dem jedes Feld fehlt).
//
// ⚠ Kein npm, kein jsdom: das DOM ist eine Attrappe von 20 Zeilen. Damit bleibt
// die Pruefumgebung dieselbe Laufzeit wie die Zielumgebung (V8) — genau die
// Divergenz, die beim `.ps1`-BOM und beim pre-commit-Framework zugeschlagen hat.
//
// Aufruf: node tools/render_check.mjs <snapshot.json>
import fs from "node:fs";
import vm from "node:vm";
const pfad = process.argv[2] || "tests/fixtures/snapshot.json";
const snap = JSON.parse(fs.readFileSync(pfad, "utf-8"));
const src = fs.readFileSync("web/app.js", "utf-8");
// Die real vorhandenen Element-IDs aus der ausgelieferten HTML.
const HTML_IDS = new Set(
[...fs.readFileSync("web/index.html", "utf-8").matchAll(/id="([^"]+)"/g)]
.map((m) => m[1]));
if (HTML_IDS.size < 50) {
console.log(` ❌ nur ${HTML_IDS.size} IDs aus index.html gelesen — Probe greift nicht`);
process.exit(1);
}
function el() {
const o = {
textContent: "", innerHTML: "", className: "", title: "", value: "",
disabled: false, checked: false, style: {}, dataset: {},
classList: { add() {}, remove() {}, toggle() {}, contains() { return false; } },
appendChild() {}, removeChild() {}, setAttribute() {}, removeAttribute() {},
addEventListener() {}, focus() {}, blur() {}, remove() {},
getBoundingClientRect() { return { top: 0, left: 0, width: 0, height: 0 }; },
querySelector() { return el(); }, querySelectorAll() { return []; },
children: [], parentNode: null, firstChild: null,
};
return o;
}
const ctx = {
console,
document: {
// ⚠⚠ NUR IDs, die es in `index.html` wirklich gibt (Fix 2026-08-20).
// Vorher lieferte die Attrappe fuer JEDE ID ein Element — damit konnte
// `$streng()` in der Probe NIE `null` zurueckgeben, und genau der Fall,
// fuer den es `$streng` gibt, war ungeprueft. Real aufgefallen beim
// Ausblenden von `#hl-note`: der Hauptzweig griff ungeschuetzt auf
// `hlEl.classList` zu, die Probe blieb gruen, im Browser haette es den
// GESAMTEN Render abgebrochen.
getElementById: (id) => (HTML_IDS.has(id) ? el() : null),
querySelector: () => el(),
querySelectorAll: () => [], createElement: () => el(),
addEventListener() {}, body: el(), documentElement: el(),
activeElement: null, hidden: false, visibilityState: "visible",
},
window: {
addEventListener() {}, setTimeout() {}, clearTimeout() {},
location: { href: "", protocol: "http:", host: "x" },
localStorage: { getItem: () => null, setItem() {}, removeItem() {} },
matchMedia: () => ({ matches: false, addEventListener() {} }),
},
navigator: { userAgent: "node" },
location: { href: "", protocol: "http:", host: "x" },
localStorage: { getItem: () => null, setItem() {}, removeItem() {} },
setInterval: () => 0, clearInterval() {}, setTimeout: () => 0, clearTimeout() {},
requestAnimationFrame: () => 0,
fetch: () => new Promise(() => {}),
WebSocket: function () { return { close() {}, send() {} }; },
AudioContext: function () {
return { createOscillator: () => ({ connect() {}, start() {}, stop() {},
frequency: { setValueAtTime() {} }, type: "" }),
createGain: () => ({ connect() {}, gain: { setValueAtTime() {},
exponentialRampToValueAtTime() {} } }),
currentTime: 0, destination: {}, resume: () => Promise.resolve(),
state: "running" };
},
Notification: function () {},
alert() {}, confirm: () => true, prompt: () => null,
};
ctx.window.document = ctx.document;
ctx.globalThis = ctx;
ctx.self = ctx;
vm.createContext(ctx);
try {
vm.runInContext(src, ctx, { filename: "app.js" });
} catch (e) {
console.log(` ❌ app.js liess sich nicht laden: ${e.constructor.name}: ${e.message}`);
process.exit(1);
}
// Jede render*-Funktion einsammeln (die im Kontext wirklich existiert).
const namen = Object.keys(ctx).filter(
(k) => /^render/.test(k) && typeof ctx[k] === "function");
if (namen.length < 5) {
console.log(` ❌ nur ${namen.length} render*-Funktionen gefunden — Probe greift nicht`);
process.exit(1);
}
// ⚠ Zwei Durchlaeufe. Der LEERE Snapshot ist der wichtigere: er stellt den
// Kaltstart nach, in dem noch kein Feld befuellt ist — dort schlagen fehlende
// Null-Pruefungen zu, nicht bei vollen Daten.
const faelle = [["Fixture", snap], ["leerer Snapshot", {}]];
let fehler = 0;
for (const [lbl, daten] of faelle) {
for (const n of namen) {
try {
// `renderM15` nimmt (setup, snapshot), alle uebrigen nur (snapshot).
if (n === "renderM15") ctx[n](daten.m15_setup || {}, daten);
else ctx[n](daten);
} catch (e) {
fehler++;
console.log(` ❌ ${n}(${lbl}): ${e.constructor.name}: ${e.message}`);
}
}
}
if (fehler) {
console.log(` ${fehler} Fehler in ${namen.length} render*-Funktionen`);
process.exit(1);
}
console.log(` ✅ ${namen.length} render*-Funktionen laufen durch `
+ `(Fixture UND leerer Snapshot)`);