The second half of Etappe 6 (61 commits). grep -rl "javafx\." over both main source trees is empty, nothing imports javafx, neither classpath resolves it, and org.openjfx is out of the build and the version catalog. The finding that shaped the work: the JavaFX main window was built on every start and never shown -- primaryStage's setScene and show were both commented out. Most of those 11,655 lines were dead weight rather than a port. But not all of them, and that is the trap this commit is really about: dead-looking methods still fed live Compose sinks. They were cut at their seams instead of deleted, and a sweep script that follows calls was written to find them, because reading the code does not reveal them. In order: the colour derivation frozen as checked-in test data, so the pin survives JavaFX being removed instead of silently comparing against itself; the live feeds lifted out of the never-shown window and given names and tests; the dead scaffolding deleted in slices; then the lifecycle. UiDispatcher now delivers to the AWT event dispatch thread, the ten Alerts are modal Compose dialogs, HostServices gives way to java.awt.Desktop with the mailto/http split JavaFX did not need, and extends Application, start(Stage), stop and launch are gone -- main builds the runtime and a shutdown hook replaces stop(). An independent review found two deadlocks in that move, both the same failure class, both invisible to the build: a blocking wait on the event thread for work that can only advance on the event thread. The profile picker waited on a latch and froze the client for good; ComposeWindowHost.close waited on Thread.join and returned with the window still marked open, which made a profile switch raise the dead runtime's window and leave the rebuilt profile without its Connect button. A third came out of fixing them: a ComposeDialog built off the event thread cannot create its GL context and never paints. Three defects only a GUI run showed, never the build: an NPE on a deleted field, a Compose menu still hanging on deleted MenuItems, and a category bookkeeping field that looked dead but is read by the outgoing-message category resolution -- deleting it would have sent messages in the wrong category. The binding docs and the manual no longer describe a JavaFX client.
25 KiB
JavaFX entfernen, Teil 1: Bestandsaufnahme und derive-Pin
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Den derive-Pin so einfrieren, dass er das Entfernen von JavaFX überlebt, und belegen, welcher Teil des nie angezeigten JavaFX-Aufbaus folgenlos gelöscht werden kann.
Architecture: Zwei voneinander unabhängige Stränge. Der erste ersetzt einen Reflexionsaufruf im Test durch eingecheckte Daten — klein, testgetrieben, zeitkritisch. Der zweite ist Verifikationsarbeit an Kst4ContestApplication: für jede verdächtige Bindung wird durch Lesen und durch einen Lauf der Anwendung festgestellt, ob sie den Betrieb speist oder nur die tote Tabelle. Ergebnis ist ein Befunddokument, auf dem Teil 2 aufsetzt.
Tech Stack: Java 21, Kotlin 2.2, Gradle, JUnit 5, Compose Multiplatform 1.8.2, JavaFX 21 (noch vorhanden).
Spec: docs/superpowers/specs/2026-10-01-javafx-entfernen-design.md
Warum nur Teil 1
Die Spezifikation hat sechs Etappen. Etappe 2 (toten Aufbau löschen), 4 (Dispatcher), 5 (Lebenszyklus) und 6 (Reste und Build) lassen sich heute nicht ohne Platzhalter schreiben: welcher Block gelöscht wird und was vorher zu entflechten ist, ist das Ergebnis von Etappe 1. Ein Plan, der das vorwegnimmt, wäre geraten.
Dieser Plan deckt Etappe 1 und Etappe 3 ab — die beiden, die heute vollständig spezifizierbar sind. Teil 2 wird geschrieben, wenn der Befund aus Aufgabe 5 vorliegt.
Global Constraints
- Kommunikation mit dem Benutzer auf Deutsch; Quelltextkommentare und Javadoc ausschließlich auf Englisch (
AGENTS.md). - Keine neue Produktionsabhängigkeit ohne vorherige Freigabe (
AGENTS.md). - Kein Commit, Push, Merge, Tag oder Release ohne gesonderte ausdrückliche Freigabe (
AGENTS.md). Die Commit-Schritte unten werden ausgeführt, weil dieser Plan sie benennt und der Benutzer ihn freigibt; alles darüber hinaus nicht. - Der Build erlaubt historisch, dass Testfehler ignoriert werden. Nicht aus Exit-Code 0 auf „alle Tests grün" schließen — die XML-Ergebnisse unter
*/build/test-results/lesen (AGENTS.md). - Ausgangslage:
./gradlew clean buildist grün, 766 Tests, 0 Fehler. Jede Rotfärbung ist ein Signal. - Signierte Commits sind konfiguriert (
commit.gpgsign=true, SSH). Schlägt das Signieren fehl, läuft der Bitwarden-Agent nicht — das ist kein Grund, unsigniert zu committen. - In diesem Plan wird keine Produktionsdatei unter
src/maingelöscht. Löschen ist Teil 2.
Review Focus
Fünf Dinge, die die Spezifikation voraussetzt, die aber keine Aufgabe hier prüft, geordnet nach Wahrscheinlichkeit, dass sie jemanden treffen:
- Der eingefrorene
derive-Pin könnte die falschen Werte einfrieren. Werden die Daten aus der eigenen Umsetzung statt aus JavaFX erzeugt, prüft der Test danach nur noch sich selbst. Aufgabe 1 erzeugt sie deshalb nachweislich über den Reflexionsaufruf und Aufgabe 2 prüft, dass mindestens ein Datensatz von einer absichtlich verfälschten Umsetzung abgelehnt wird. - Die eingefrorenen Daten könnten den benutzten Wertebereich nicht abdecken.
derivewird mit 26.4 % und 35 % auf Stylesheet-Farben gerufen; Daten nur für Weiß und Schwarz hülfen nicht. Aufgabe 1 deckt die Prozentwerte und Farben ab, die der Test heute benutzt, und zusätzlich die Sprungstellen der Helligkeitsstaffel (0.2, 0.3, 0.4, 0.5, 0.6, 0.85). - Der Befund aus Aufgabe 5 könnte eine Bindung übersehen, die nur bei bestimmten Einstellungen lebt. Ein Profil mit anderen aktiven Bändern oder abgeschalteter Karte durchläuft andere Zweige. Aufgabe 4 prüft deshalb gegen ein zweites Profil.
- Ein Listener könnte nur beim Profilwechsel lebendig werden.
closeOwnedStagesund der Wiederaufbau laufen nur dort. Aufgabe 4 schließt einen Profilwechsel ein. - Die Systemmenüleiste könnte auf macOS anders leben als auf Linux. Geprüft wird auf Linux; der Befund hält das ausdrücklich als ungeprüft fest, statt es offenzulassen.
File Structure
| Datei | Verantwortung | Aufgabe |
|---|---|---|
app-desktop/src/test/resources/javafx-derive-reference.txt |
neu — die von JavaFX gelieferten Werte als eingecheckte Daten | 1 |
app-desktop/src/test/kotlin/kst4contest/view/compose/JavaFxStylesheetTest.kt |
ändern — vergleicht gegen die Daten statt gegen den Reflexionsaufruf | 1, 2 |
app-desktop/src/main/kotlin/kst4contest/view/compose/JavaFxStylesheet.kt |
ändern — nur der veraltete Kommentar | 2 |
docs/superpowers/notes/2026-10-01-javafx-lebendbefund.md |
neu — der Befund, auf dem Teil 2 aufsetzt | 3, 4, 5 |
Task 1: Die JavaFX-Werte als Daten einfrieren
Files:
- Create:
app-desktop/src/test/resources/javafx-derive-reference.txt - Modify:
app-desktop/src/test/kotlin/kst4contest/view/compose/JavaFxStylesheetTest.kt - Test: dieselbe Datei
Interfaces:
-
Consumes:
JavaFxStylesheet.derive(color: Color, percent: Double): Color— unverändert. -
Produces: die Ressource
javafx-derive-reference.txt, Zeilenformat<rgbHex>;<percent>;<erwarteterRgbHex>, z. B.#ececec;26.4;#f4f4f4. Drei Kommentarzeilen am Anfang, jede mit#. Aufgabe 2 schreibt den Leser dafür. Ausserdem bleibtderiveWithJavaFx(rgb: String, percent: Double): Stringim Test erhalten — Aufgabe 2 entfernt nur seinen Aufruf aus dem laufenden Vergleich, nicht die Methode. -
Step 1: Den bestehenden Test lesen, bevor etwas geändert wird
Run: sed -n '1,60p;160,180p' app-desktop/src/test/kotlin/kst4contest/view/compose/JavaFxStylesheetTest.kt
Erwartet: deriveWithJavaFx(rgb, percent) ruft über Class.forName("com.sun.javafx.util.Utils").getMethod("deriveColor", ...) das echte JavaFX. Der Test derive gives the values JavaFX gives vergleicht drei Größen: die erwarteten Werte aus der Tabelle, JavaFxStylesheet.derive und deriveWithJavaFx. Notiere den genauen Namen der Hilfsmethoden parse, hex und assertChannelsClose — sie werden unten weiterbenutzt.
- Step 2: Einen Generator schreiben, der die Daten aus JavaFX erzeugt
Ein JUnit-Test, der die Datei schreibt statt etwas zu behaupten. Er wird einmal benutzt und bleibt danach als @Disabled stehen, damit nachvollziehbar ist, woher die Daten kommen.
In JavaFxStylesheetTest.kt ergänzen:
/**
* Writes the reference data the frozen test compares against.
*
* Disabled because it is a generator, not an assertion: it runs once, while JavaFX
* is still on the classpath, and its output is checked in. Kept rather than deleted
* so that the provenance of the data is readable — the values come from
* com.sun.javafx.util.Utils.deriveColor and from nowhere else, which is the whole
* point of pinning them.
*
* To regenerate: remove @Disabled, run, restore @Disabled.
*/
@org.junit.jupiter.api.Disabled("generator; run by hand while JavaFX is present")
@Test
fun `generate the JavaFX reference data`() {
val colours = listOf(
"#ffffff", "#000000", "#ececec", "#373e43", "#1d1d1d", "#4da6ff",
"#2b2b2b", "#808080", "#333333", "#4d4d4d", "#666666", "#999999",
"#d9d9d9", "#f4f4f4", "#63067a", "#ff9900",
)
val percents = listOf(-80.0, -50.0, -35.0, -30.0, -10.0, 10.0, 26.4, 35.0, 50.0, 80.0)
val lines = buildList {
for (rgb in colours) {
for (percent in percents) {
add("$rgb;$percent;${deriveWithJavaFx(rgb, percent)}")
}
}
}
val target = java.io.File("src/test/resources/javafx-derive-reference.txt")
target.parentFile.mkdirs()
target.writeText(
"# Generated from com.sun.javafx.util.Utils.deriveColor by\n" +
"# JavaFxStylesheetTest.`generate the JavaFX reference data`.\n" +
"# Format: <rgbHex>;<percent>;<expectedRgbHex>\n" +
lines.joinToString("\n") + "\n"
)
println("wrote ${lines.size} pairs to ${target.absolutePath}")
}
Die Farbliste deckt ab: Weiß und Schwarz (Ränder), die Modena-Basis #ececec, die Abendblech-Basis #373e43, Marker- und Gitterfarben aus der Karte, und Graustufen, die die Sprungstellen der Helligkeitsstaffel treffen (0.2, 0.3, 0.4, 0.5, 0.6, 0.85 — siehe Review Focus 2). Die Prozentwerte decken die im Produktionscode benutzten 26.4 und 35 ab, dazu negative und positive Extreme.
- Step 3: Den Generator einmal laufen lassen
@Disabled vorübergehend auskommentieren, dann:
Run: ./gradlew :app-desktop:test --tests 'kst4contest.view.compose.JavaFxStylesheetTest' --console=plain -i 2>&1 | grep "wrote"
Erwartet: eine Zeile wrote 160 pairs to /…/app-desktop/src/test/resources/javafx-derive-reference.txt.
Danach @Disabled wieder einkommentieren.
- Step 4: Prüfen, dass die Daten wirklich von JavaFX stammen
Run: head -5 app-desktop/src/test/resources/javafx-derive-reference.txt && wc -l app-desktop/src/test/resources/javafx-derive-reference.txt
Erwartet: drei Kommentarzeilen, dann Zeilen der Form #ffffff;-80.0;#333333. Insgesamt 163 Zeilen.
Stichprobe von Hand: #ffffff;-30.0;… muss ein mittleres Grau sein, nicht Weiß. Ist die Spalte identisch mit der ersten, hat der Generator nicht JavaFX gefragt — dann Schritt 2 prüfen, nicht weitermachen.
- Step 5: Commit
git add app-desktop/src/test/resources/javafx-derive-reference.txt \
app-desktop/src/test/kotlin/kst4contest/view/compose/JavaFxStylesheetTest.kt
git commit -m "Freeze the JavaFX colour-derivation reference as test data
JavaFxStylesheetTest compares derive() against com.sun.javafx.util.Utils
at runtime, through reflection. That comparison stops working the moment
JavaFX leaves the classpath, and the test would then quietly fall back to
checking derive() against itself.
The pairs are generated from the real JavaFX implementation while it is
still here and checked in. The generator stays, disabled, so the
provenance of the numbers is readable."
Task 2: Den Test gegen die Daten prüfen lassen
Files:
- Modify:
app-desktop/src/test/kotlin/kst4contest/view/compose/JavaFxStylesheetTest.kt - Modify:
app-desktop/src/main/kotlin/kst4contest/view/compose/JavaFxStylesheet.kt(nur Kommentar) - Test: dieselbe Testdatei
Interfaces:
-
Consumes:
javafx-derive-reference.txtaus Aufgabe 1. -
Produces: nichts, was spätere Aufgaben benutzen.
-
Step 1: Den Test schreiben, der gegen die Daten prüft
In JavaFxStylesheetTest.kt ergänzen:
/** The reference pairs, as `(rgbHex, percent, expectedRgbHex)`. */
private fun referencePairs(): List<Triple<String, Double, String>> {
val resource = javaClass.getResourceAsStream("/javafx-derive-reference.txt")
?: error("javafx-derive-reference.txt is missing from the test resources")
return resource.bufferedReader().readLines()
.filterNot { it.isBlank() || it.startsWith("#") }
.map { line ->
val (rgb, percent, expected) = line.split(";")
Triple(rgb, percent.toDouble(), expected)
}
}
/**
* derive() against the values JavaFX itself produced.
*
* Against checked-in data rather than a live reflection call, because the live call
* disappears with JavaFX and would take the only real check with it — leaving a
* test that compares derive() to derive().
*/
@Test
fun `derive matches the frozen JavaFX reference`() {
val pairs = referencePairs()
assertTrue(pairs.size >= 100) { "only ${pairs.size} reference pairs; the file looks truncated" }
pairs.forEach { (rgb, percent, expected) ->
assertChannelsClose(
parse(expected),
JavaFxStylesheet.derive(parse(rgb), percent),
"derive($rgb, $percent%)",
)
}
}
- Step 2: Den Test laufen lassen und sehen, dass er grün ist
Run: ./gradlew :app-desktop:test --tests 'kst4contest.view.compose.JavaFxStylesheetTest' --console=plain > /tmp/t1.txt 2>&1; python3 -c " import glob,xml.etree.ElementTree as ET for f in glob.glob('app-desktop/build/test-results/test/TEST-*JavaFxStylesheetTest.xml'): r=ET.parse(f).getroot(); print(f\"tests={r.get('tests')} failures={r.get('failures')} skipped={r.get('skipped')}\") for tc in r.iter('testcase'): for b in list(tc.iter('failure'))+list(tc.iter('error')): print(tc.get('name'), (b.get('message') or '')[:200])"
Erwartet: failures=0, skipped=1 (der Generator).
Ein grüner Test ist hier noch kein Beweis — er könnte grün sein, weil die Daten aus der eigenen Umsetzung stammen. Das prüft der nächste Schritt.
- Step 3: Beweisen, dass der Test den Fehler fängt, den er fangen soll
Review Focus 1. Die Produktionsumsetzung vorübergehend verfälschen:
In JavaFxStylesheet.kt, in derive, calcBrightness = calcBrightness.coerceIn(-1.0, 1.0) ersetzen durch calcBrightness = (calcBrightness * 1.05).coerceIn(-1.0, 1.0).
Run: denselben Befehl wie Schritt 2.
Erwartet: failures=1, und die Meldung nennt ein konkretes derive(#…, …%). Fällt der Test nicht, sind die Daten wertlos — dann stammen sie nicht von JavaFX; zurück zu Aufgabe 1.
Danach die Verfälschung zurücknehmen und erneut laufen lassen: failures=0.
- Step 4: Den Reflexionsvergleich aus dem laufenden Test nehmen
Die Zeile, die deriveWithJavaFx im Test derive gives the values JavaFX gives aufruft (um :50), entfernen, sodass dieser Test nur noch seine eigene Tabelle gegen JavaFxStylesheet.derive prüft. deriveWithJavaFx selbst bleibt — der Generator in Aufgabe 1 braucht es.
Begründung als Kommentar über den entfernten Vergleich setzen:
/*
* No live JavaFX comparison here. It lives in the frozen reference data
* instead (javafx-derive-reference.txt), because a reflection call into
* com.sun.javafx stops working when JavaFX is removed and would take the
* only real check with it.
*/
- Step 5: Den veralteten Kommentar in der Produktionsdatei berichtigen
In JavaFxStylesheet.kt beschreibt der Kommentar über derive (um :116–:129) eine Reflexionsfassung, die es nicht mehr gibt. Ersetzen durch:
/**
* JavaFX `derive(colour, percent)`, reimplemented.
*
* No JavaFX call and no reflection: this is the algorithm itself. The brightness it
* derives from is not the HSB value but `0.3R + 0.59G + 0.11B`, and the positive
* branch follows a staircase rather than a formula, which is why an earlier
* approximation here was measurably wrong.
*
* Correctness is not argued, it is pinned: `JavaFxStylesheetTest` checks this
* against values the real `com.sun.javafx.util.Utils.deriveColor` produced, frozen
* into `javafx-derive-reference.txt` so the check survives JavaFX being removed.
*/
- Step 6: Die ganze Suite laufen lassen
Run: ./gradlew clean build --console=plain > /tmp/b.txt 2>&1; echo "exit=$?"; python3 -c " import glob,xml.etree.ElementTree as ET t=f=0 for p in glob.glob('*/build/test-results/test/TEST-*.xml'): r=ET.parse(p).getroot(); t+=int(r.get('tests')); f+=int(r.get('failures'))+int(r.get('errors')) print(f'tests={t} failures={f}')"
Erwartet: exit=0, failures=0, und tests um eins höher als die 766 vom Ausgangsstand.
- Step 7: Commit
git add app-desktop/src/test/kotlin/kst4contest/view/compose/JavaFxStylesheetTest.kt \
app-desktop/src/main/kotlin/kst4contest/view/compose/JavaFxStylesheet.kt
git commit -m "Check derive() against the frozen reference instead of live JavaFX
The reflection call stays only in the disabled generator. The running
test now reads checked-in values, so it keeps its meaning once JavaFX is
gone rather than silently comparing derive() with itself.
Verified by breaking derive() on purpose and watching the test fail.
Also corrects the doc comment on derive(), which still described a
reflection delegation that the code has not done for some time."
Task 3: Feststellen, ob FxRosterBinding den Betrieb speist
Files:
- Create:
docs/superpowers/notes/2026-10-01-javafx-lebendbefund.md - Read only:
app-desktop/src/main/java/kst4contest/view/Kst4ContestApplication.java,app-desktop/src/main/java/kst4contest/view/FxRosterBinding.java
Interfaces:
- Consumes: nichts.
- Produces: das Befunddokument mit dem Abschnitt „FxRosterBinding". Aufgabe 5 fasst zusammen, Teil 2 baut darauf auf.
Keine Produktionsdatei wird in dieser Aufgabe geändert.
- Step 1: Alle Nutzungen des Spiegels auflisten
Run: grep -n "chatMemberListBinding" app-desktop/src/main/java/kst4contest/view/Kst4ContestApplication.java
Erwartet: Erzeugung um :2513, Nullsetzung um :6877, eine Prüfung um :1766. Jede weitere Fundstelle notieren.
- Step 2: Feststellen, wer den gespiegelten
ObservableListliest
Run: grep -n "chatMemberListBinding.get\|chatMemberListBinding\.\(mirror\|list\|items\)" app-desktop/src/main/java/kst4contest/view/Kst4ContestApplication.java; grep -n "public" app-desktop/src/main/java/kst4contest/view/FxRosterBinding.java
Erwartet: die öffentliche Fläche von FxRosterBinding und jede Stelle, die den Spiegel abruft. Für jede Stelle festhalten: landet sie in einer TableView (tot) oder in etwas, das Compose liest (lebend)?
- Step 3: Feststellen, woher die Compose-Stationsliste ihre Zeilen bekommt
Run: grep -n "DataTableState<ChatMember>\|setRows\|rows =" app-desktop/src/main/java/kst4contest/view/Kst4ContestApplication.java | head -20; grep -n "fun setRows\|var rows\|val rows" app-desktop/src/main/kotlin/kst4contest/view/compose/DataTableState.kt
Erwartet: eine durchgehende Kette von ObservableRoster bis DataTableState.rows. Notiere sie Glied für Glied. Führt die Kette durch chatMemberListBinding, ist der Spiegel lebend.
- Step 4: Den Befund schreiben
docs/superpowers/notes/2026-10-01-javafx-lebendbefund.md anlegen:
# Lebendbefund: was im JavaFX-Aufbau von KST4Contest tatsächlich benutzt wird
Grundlage für Teil 2 von `docs/superpowers/specs/2026-10-01-javafx-entfernen-design.md`.
Erhoben am 1. Oktober 2026 auf Linux, Branch `nextMajorRelease/version1_50`.
Jeder Block bekommt genau einen von zwei Töpfen:
**folgenlos löschbar** oder **zu entflechten** (mit der Stelle, die davon abhängt).
## FxRosterBinding (`chatMemberListBinding`)
**Topf:** <folgenlos löschbar | zu entflechten>
**Belege:**
- Erzeugt bei `Kst4ContestApplication.java:<Zeile>`, genullt bei `:<Zeile>`.
- Gelesen von: <Liste der Stellen, je mit Zeile und „tot/lebend">
- Die Compose-Stationsliste bezieht ihre Zeilen über: <die Kette aus Schritt 3>
**Folgerung:** <ein Satz>
Jede spitze Klammer wird durch das ersetzt, was die Schritte 1–3 ergeben haben. Bleibt eine stehen, ist die Aufgabe nicht fertig.
- Step 5: Commit
git add docs/superpowers/notes/2026-10-01-javafx-lebendbefund.md
git commit -m "Record what FxRosterBinding actually feeds
First section of the finding that part 2 of the JavaFX removal builds on.
No production code touched."
Task 4: Die übrigen Verdachtsfälle am laufenden Programm prüfen
Files:
- Modify:
docs/superpowers/notes/2026-10-01-javafx-lebendbefund.md - Read only:
app-desktop/src/main/java/kst4contest/view/Kst4ContestApplication.java
Interfaces:
- Consumes: das Befunddokument aus Aufgabe 3.
- Produces: dasselbe Dokument mit den Abschnitten „registerThemedScene", „LayoutAutosave", „Systemmenüleiste" und „Kandidatenfenster".
Keine Produktionsdatei wird in dieser Aufgabe geändert. Diese Aufgabe erfordert, die Anwendung zu starten und zu bedienen; Lesen allein beantwortet sie nicht.
- Step 1: Die Stellen auflisten
Run: grep -n "registerThemedScene\|LayoutAutosave\|layoutAutosave\|installSharedSystemMenuBar\|initMenuBar\|showTopPriorityCandidatesWindow" app-desktop/src/main/java/kst4contest/view/Kst4ContestApplication.java
Erwartet unter anderem: drei Aufrufe von registerThemedScene, layoutAutosave erzeugt um :7420, installSharedSystemMenuBar(candidatesScene) um :4858, initMenuBar() um :5729, showTopPriorityCandidatesWindow gerufen bei :4706 (toter JavaFX-Knopf) und :10707 (lebender Compose-Pfad).
- Step 2: Die Anwendung starten und das Kandidatenfenster öffnen
Run: ./gradlew :app-desktop:run
Dann im Compose-Hauptfenster den Knopf more neben den Prioritätsknöpfen drücken.
Erwartet: ein Fenster mit der Kandidatenliste erscheint. Festhalten: Titelleiste, ob es eine eigene Menüleiste trägt, ob es modal ist, und ob ein Klick auf einen Eintrag im Hauptfenster eine Station auswählt.
Erscheint kein Fenster, ist der Pfad trotz :10707 tot — das ist ein genauso wertvoller Befund und gehört so ins Dokument.
- Step 3: Das Design umschalten und beobachten, ob die Compose-Fenster folgen
Bei laufender Anwendung das Design über die Menüleiste des Hauptfensters umschalten (Tag/Abend).
Erwartet: Hauptfenster, Kartenfenster und Einstellungsfenster wechseln die Farben. Notieren, ob alle folgen. Folgt eines nicht, hängt es womöglich an registerThemedScene und gehört in den Topf „zu entflechten".
- Step 4: Einen Profilwechsel durchführen
Review Focus 4. Über die Menüleiste das Bedienerprofil wechseln.
Erwartet: die Fenster werden geschlossen und neu aufgebaut; die Anwendung bleibt bedienbar; in der Konsole erscheint keine Ausnahme. Notieren, was passiert.
- Step 5: Mit einem zweiten Profil wiederholen
Review Focus 3. Ein Profil mit anderen aktiven Bändern wählen (mindestens ein Band an- oder abgeschaltet gegenüber dem ersten) und die Schritte 2 bis 4 wiederholen.
Erwartet: dasselbe Verhalten. Weicht es ab, gehört die Abweichung mit der Einstellung ins Dokument, die sie auslöst.
- Step 6: Fenstergrößen auf Persistenz prüfen
Ein Fenster in der Größe ändern, die Anwendung beenden, neu starten.
Erwartet: die Größe ist erhalten. Ist sie es nicht, schreibt LayoutAutosave womöglich Werte, die niemand mehr liest — oder umgekehrt. Notieren.
- Step 7: Die Abschnitte ins Befunddokument schreiben
Für jeden der vier Blöcke — registerThemedScene, LayoutAutosave, Systemmenüleiste, Kandidatenfenster — denselben Aufbau wie in Aufgabe 3 Schritt 4: Topf, Belege, Folgerung.
Die Systemmenüleiste bekommt zusätzlich die Zeile:
**Nicht geprüft:** macOS. Erhoben wurde auf Linux. Ob die Systemmenüleiste dort
sichtbar an der JavaFX-`MenuBar` hängt, ist offen und muss vor dem Entfernen auf
einem Mac nachgeholt werden.
- Step 8: Commit
git add docs/superpowers/notes/2026-10-01-javafx-lebendbefund.md
git commit -m "Record what the themed scenes, autosave and menu bar actually do
Established by running the application and operating it, not by reading:
a listener that is never fired reads the same as one that does not exist.
Includes a profile switch and a second profile, and marks macOS as
unverified. No production code touched."
Task 5: Den Befund abschließen und Teil 2 umreißen
Files:
- Modify:
docs/superpowers/notes/2026-10-01-javafx-lebendbefund.md
Interfaces:
-
Consumes: alle Abschnitte aus Aufgabe 3 und 4.
-
Produces: die Löschreihenfolge, aus der Teil 2 geschrieben wird.
-
Step 1: Den toten Aufbau in Scheiben zerlegen
Run: grep -n "private.*void init[A-Z][a-zA-Z]*(\|private.*Pane init[A-Z][a-zA-Z]*(\|private.*Node init[A-Z][a-zA-Z]*(" app-desktop/src/main/java/kst4contest/view/Kst4ContestApplication.java
Erwartet: die init…-Methoden, die zusammen das nie gezeigte Fenster bauen. Für jede notieren: Zeilenbereich, ungefähre Länge und ob der Befund aus Aufgabe 3 und 4 sie als „folgenlos löschbar" ausweist.
- Step 2: Eine Reihenfolge festlegen, die den Build nach jeder Scheibe grün lässt
Blätter zuerst, Wurzeln zuletzt: eine Methode, die niemand mehr ruft, lässt sich löschen; eine, die noch gerufen wird, nicht. Die Reihenfolge als nummerierte Liste ins Dokument schreiben, jede Scheibe mit den Methoden, die sie umfasst.
- Step 3: Die Zusammenfassung schreiben
Am Anfang des Dokuments, unter der Überschrift:
## Ergebnis in einem Satz
<Von den N init-Methoden sind M folgenlos löschbar; K sind zu entflechten, nämlich …>
## Löschreihenfolge für Teil 2
1. <Scheibe> — <Methoden> — <geschätzte Zeilen>
2. …
- Step 4: Prüfen, dass keine Lücke bleibt
Run: grep -c "zu entflechten\|folgenlos löschbar" docs/superpowers/notes/2026-10-01-javafx-lebendbefund.md; grep -n "<" docs/superpowers/notes/2026-10-01-javafx-lebendbefund.md
Erwartet: jeder Block hat genau einen Topf, und der zweite Befehl findet keine spitze Klammer mehr. Findet er eine, ist eine Vorlage nicht ausgefüllt.
- Step 5: Commit
git add docs/superpowers/notes/2026-10-01-javafx-lebendbefund.md
git commit -m "Finish the live-code finding and derive the deletion order
Part 2 of the JavaFX removal is written from this, not from assumption."
Was dieser Plan nicht tut
- Er löscht keine Produktionsdatei.
- Er fasst den
UiDispatcher, den Lebenszyklus und die zehn Dialoge nicht an. - Er entfernt
org.openjfxnicht aus dem Build.
Das ist Teil 2, und der wird geschrieben, wenn der Befund aus Aufgabe 5 vorliegt.