Folge von #117. Dort entschieden: Plugins werden vor dem Projekt geladen, und ob ein Plugin läuft, entscheidet das Projekt. Der serverweite Schalter aus #114 wird damit ersetzt.
Warum beides ein Issue ist
Beides greift in denselben Startweg (src/editor/App.ts:44: erst editor.boot(), dann startPlugins). Welche Plugins an sind, steht künftig in der Projektdatei — die muss also gelesen sein, bevor ein Plugin geladen wird. Getrennt gebaut würde der Startweg zweimal umgebaut.
Der Startweg, wie er werden soll
Plugin-Liste holen (GET ?r=plugins) und die Projektdatei als rohes JSON holen.
Aus dem rohen JSON die aktiven Plugins lesen (neues Feld, siehe unten).
Diese Plugins laden und activate rufen.
Erst danachdeserializeProject und den Editor bauen.
Schritt 4 nach 3 ist der Punkt: normalizeGraph (src/core/store/serialize.ts:337) und die Typprüfung der Assets (serialize.ts:85) werfen beim Deserialisieren weg, was sie nicht kennen. Was ein Plugin anmeldet, muss vorher dastehen — sonst sind eigene Node-Typen (#126) gar nicht möglich. Die Projektdatei zu holen ist dabei nicht dasselbe wie das Projekt zu laden: weggeworfen wird erst beim Deserialisieren.
Zu bauen
Neues, deklariertes Feld in der Projektdatei, z. B. plugins: string[] (Dateinamen), Default [], im Schema von deserializeProject. Fehlt es, ist es leer — kein formatVersion-Bump nötig.
data/plugins/plugins.json, PluginStore::setActive und die Route PUT ?r=plugins/{file} entfallen, ebenso der 403 für inaktive Module.
Der Plugins-Tab aus #114 bleibt. Sein Schalter schreibt jetzt ins Projekt.
Ein Projektwechsel schaltet um: Plugins, die im neuen Projekt aus sind, werden abgeschaltet (deactivate, Scope aufräumen wie in #114), die anderen angeschaltet.
api.project.assets() und api.project.on('change') müssen aushalten, dass es während activate noch kein Projekt gibt: leere Liste, der erste change kommt, wenn das Projekt steht.
tools/lib/plugins.mjs: Build und seed-e2e schalten das Demo-Plugin nicht mehr in plugins.json ein, sondern in den Seed-Projekten.
Tests: Unit-Tests für Reihenfolge und Projektwechsel, e2e-Modul 38 nachziehen.
Doku: Wiki Plugins.md, API.md (Route weg), Architecture.md, README/Check-Summe.
Eine Folge, bewusst in Kauf genommen
Bisher wurde ein ausgeschaltetes Plugin nie ausgeliefert (403, #114). Mit der Aktivierung im Projekt kann der Server das nicht mehr wissen: jede Plugin-Datei ist abrufbar, ausgeführt wird sie nur, wenn das offene Projekt sie nennt. Solange es keine Benutzerverwaltung gibt, ist das ohne Belang (Entscheidung in #117). Für #115 und #116 ist es dort zu berücksichtigen.
Offen (mit Vorschlag)
Ist der Schalter im Plugins-Tab ein Undo-Schritt? Vorschlag: ja, er ändert das Projekt.
Welche Plugins sind in einem neu angelegten Projekt an? Vorschlag: keine. Die Seed-Projekte bekommen das Demo-Plugin.
Folge von #117. Dort entschieden: Plugins werden **vor** dem Projekt geladen, und ob ein Plugin läuft, entscheidet das **Projekt**. Der serverweite Schalter aus #114 wird damit ersetzt.
## Warum beides ein Issue ist
Beides greift in denselben Startweg (`src/editor/App.ts:44`: erst `editor.boot()`, dann `startPlugins`). Welche Plugins an sind, steht künftig in der Projektdatei — die muss also gelesen sein, bevor ein Plugin geladen wird. Getrennt gebaut würde der Startweg zweimal umgebaut.
## Der Startweg, wie er werden soll
1. Plugin-Liste holen (`GET ?r=plugins`) und die Projektdatei als **rohes JSON** holen.
2. Aus dem rohen JSON die aktiven Plugins lesen (neues Feld, siehe unten).
3. Diese Plugins laden und `activate` rufen.
4. **Erst danach** `deserializeProject` und den Editor bauen.
Schritt 4 nach 3 ist der Punkt: `normalizeGraph` (`src/core/store/serialize.ts:337`) und die Typprüfung der Assets (`serialize.ts:85`) werfen beim Deserialisieren weg, was sie nicht kennen. Was ein Plugin anmeldet, muss vorher dastehen — sonst sind eigene Node-Typen (#126) gar nicht möglich. Die Projektdatei zu *holen* ist dabei nicht dasselbe wie das Projekt zu *laden*: weggeworfen wird erst beim Deserialisieren.
## Zu bauen
- Neues, deklariertes Feld in der Projektdatei, z. B. `plugins: string[]` (Dateinamen), Default `[]`, im Schema von `deserializeProject`. Fehlt es, ist es leer — kein `formatVersion`-Bump nötig.
- `data/plugins/plugins.json`, `PluginStore::setActive` und die Route `PUT ?r=plugins/{file}` entfallen, ebenso der 403 für inaktive Module.
- Der **Plugins-Tab aus #114 bleibt**. Sein Schalter schreibt jetzt ins Projekt.
- Ein Projektwechsel schaltet um: Plugins, die im neuen Projekt aus sind, werden abgeschaltet (`deactivate`, Scope aufräumen wie in #114), die anderen angeschaltet.
- `api.project.assets()` und `api.project.on('change')` müssen aushalten, dass es während `activate` noch kein Projekt gibt: leere Liste, der erste `change` kommt, wenn das Projekt steht.
- `tools/lib/plugins.mjs`: Build und `seed-e2e` schalten das Demo-Plugin nicht mehr in `plugins.json` ein, sondern in den Seed-Projekten.
- Tests: Unit-Tests für Reihenfolge und Projektwechsel, e2e-Modul 38 nachziehen.
- Doku: Wiki `Plugins.md`, `API.md` (Route weg), `Architecture.md`, README/Check-Summe.
## Eine Folge, bewusst in Kauf genommen
Bisher wurde ein ausgeschaltetes Plugin **nie ausgeliefert** (403, #114). Mit der Aktivierung im Projekt kann der Server das nicht mehr wissen: jede Plugin-Datei ist abrufbar, ausgeführt wird sie nur, wenn das offene Projekt sie nennt. Solange es keine Benutzerverwaltung gibt, ist das ohne Belang (Entscheidung in #117). Für #115 und #116 ist es dort zu berücksichtigen.
## Offen (mit Vorschlag)
- Ist der Schalter im Plugins-Tab ein Undo-Schritt? Vorschlag: ja, er ändert das Projekt.
- Welche Plugins sind in einem **neu angelegten** Projekt an? Vorschlag: keine. Die Seed-Projekte bekommen das Demo-Plugin.
Die Projektdatei hat das Feld plugins: ["demo-plugin.js"] (Default leer, kein formatVersion-Bump). Der Schalter im Plugins-Tab schreibt dorthin, als gewöhnlicher Undo-Schritt (SetPluginActiveCommand) — so wie in den Rückfragen besprochen. Ein neues Projekt schaltet nichts ein.
Die Ladereihenfolge ist umgedreht: startPlugins holt nur die Liste der Dateien, danach editor.boot(), und ProjectStore.load(id, prepare) reicht das rohe JSON durch, bevor deserialisiert wird. Dort liest pluginsOf(raw) die Liste und PluginManager.syncTo() aktiviert genau diese Plugins. Erst danach läuft deserializeProject — genau der Punkt des Issues, sonst würde normalizeGraph die vom Plugin angemeldeten Knoten als unbekannt verwerfen (#126, #127).
Projektwechsel: erst stoppen, was das neue Projekt nicht nennt, dann starten, was es nennt. Eine Datei, die der Server nicht hat, wird stillschweigend übergangen. Nach dem Abmelden ist der Manager tot.
Server entkernt: plugins.json, PluginStore::setActive, isActive, die Route PUT ?r=plugins/{file} und der 403 für inaktive Module sind weg, GET ?r=plugins liefert nur noch {file,bytes,updatedAt}. Damit ist jede Plugin-Datei abrufbar — die in #117 bewusst in Kauf genommene Folge; sie steht als Absatz im Wiki, damit #115 und #116 sie berücksichtigen.
Nachgereicht auf Zuruf: der Plugins-Tab hat jetzt ein eigenes Icon (Puzzleteil). Ohne Icon bleibt nur der Tab eines Plugins selbst.
Zu wissen: Vorher war das Demo-Plugin serverweit eingeschaltet, also auch im Playground und in den Demo-Projekten. Diese Projektdateien haben kein plugins-Feld, und das heißt „keines" — im Plugins-Tab pro Projekt wieder einschaltbar.
3 neue e2e-Checks in Modul 38: das Projekt schaltet ein, nicht der Server; nach dem Abschalten ist project.plugins leer, während der Server die Datei weiter listet; Undo nimmt den Schalter zurück.
5 neue Unit-Tests: Reihenfolge (die Mutation im rohen JSON steht im fertigen Projekt), pluginsOf gegen krude Eingaben, Undo/Redo, Projektwechsel, unbekannter Dateiname.
Gegenprobe: startPlugins wieder nach editor.boot() gerufen → Modul 38 fällt mit 5 FAILs. Original zurückgeprüft und neu gebaut.
Nach dem Icon-Nachtrag noch einmal Gate (1718 Tests) und die Module 1–14 (428 Checks) — die Icon-Checks liegen in 14. Die Check-Summe 1129 ändert sich dadurch nicht.
Die Summe 1129 ist lokal gemessen, CI läuft noch (Tasks 174/175).
Falle fürs Protokoll: Pixis SVG-Parser zeichnet ein A nach einem L als Zacken statt als Bogen. Knopf und Aussparung des Puzzleteils sind deshalb kubische Kurven.
Gebaut und gepusht als `5ba796a`, Wiki `b6614c5`.
**Was jetzt gilt**
- Die Projektdatei hat das Feld `plugins: ["demo-plugin.js"]` (Default leer, kein `formatVersion`-Bump). Der Schalter im Plugins-Tab schreibt dorthin, als gewöhnlicher Undo-Schritt (`SetPluginActiveCommand`) — so wie in den Rückfragen besprochen. Ein neues Projekt schaltet nichts ein.
- Die Ladereihenfolge ist umgedreht: `startPlugins` holt nur die Liste der Dateien, danach `editor.boot()`, und `ProjectStore.load(id, prepare)` reicht das **rohe** JSON durch, bevor deserialisiert wird. Dort liest `pluginsOf(raw)` die Liste und `PluginManager.syncTo()` aktiviert genau diese Plugins. Erst danach läuft `deserializeProject` — genau der Punkt des Issues, sonst würde `normalizeGraph` die vom Plugin angemeldeten Knoten als unbekannt verwerfen (#126, #127).
- Projektwechsel: erst stoppen, was das neue Projekt nicht nennt, dann starten, was es nennt. Eine Datei, die der Server nicht hat, wird stillschweigend übergangen. Nach dem Abmelden ist der Manager tot.
- Server entkernt: `plugins.json`, `PluginStore::setActive`, `isActive`, die Route `PUT ?r=plugins/{file}` und der 403 für inaktive Module sind weg, `GET ?r=plugins` liefert nur noch `{file,bytes,updatedAt}`. Damit ist **jede Plugin-Datei abrufbar** — die in #117 bewusst in Kauf genommene Folge; sie steht als Absatz im Wiki, damit #115 und #116 sie berücksichtigen.
- Nachgereicht auf Zuruf: der Plugins-Tab hat jetzt ein eigenes Icon (Puzzleteil). Ohne Icon bleibt nur der Tab eines Plugins selbst.
**Zu wissen:** Vorher war das Demo-Plugin serverweit eingeschaltet, also auch im Playground und in den Demo-Projekten. Diese Projektdateien haben kein `plugins`-Feld, und das heißt „keines" — im Plugins-Tab pro Projekt wieder einschaltbar.
**Tests**
```
./check.sh → 118 Files, 1718 Tests, CHECK EXIT 0
npm run build → BUILD EXIT 0
node tools/e2e.mjs (alle Module) → ALL PASSED – 1129 checks
```
- 3 neue e2e-Checks in Modul 38: das Projekt schaltet ein, nicht der Server; nach dem Abschalten ist `project.plugins` leer, während der Server die Datei weiter listet; Undo nimmt den Schalter zurück.
- 5 neue Unit-Tests: Reihenfolge (die Mutation im rohen JSON steht im fertigen Projekt), `pluginsOf` gegen krude Eingaben, Undo/Redo, Projektwechsel, unbekannter Dateiname.
- Gegenprobe: `startPlugins` wieder nach `editor.boot()` gerufen → Modul 38 fällt mit 5 FAILs. Original zurückgeprüft und neu gebaut.
- Nach dem Icon-Nachtrag noch einmal Gate (1718 Tests) und die Module 1–14 (428 Checks) — die Icon-Checks liegen in 14. Die Check-Summe 1129 ändert sich dadurch nicht.
- Die Summe 1129 ist lokal gemessen, CI läuft noch (Tasks 174/175).
**Falle fürs Protokoll:** Pixis SVG-Parser zeichnet ein `A` nach einem `L` als Zacken statt als Bogen. Knopf und Aussparung des Puzzleteils sind deshalb kubische Kurven.
Nachtrag: Der CI-Lauf zu 5ba796a war rot — Gate (Task 174) grün, Suite (Task 175) mit genau einem FAIL in Modul 18:
FAIL while playing, each output shows the value it last carried in front of its name –
{"update":["","0.05 dt"], …}
Ursache, kein echter Fehler am Stand: Der Check verlangte das Format /^0\.\d{3} dt$/, also exakt drei Nachkommastellen. formatNumber schneidet aber nachlaufende Nullen weg, und ein Frame von genau 50 ms liest sich dann als 0.05 statt 0.050. Auf dieser Maschine trifft die Frame-Zeit nie eine runde Zahl, deshalb war es lokal immer grün. Der Wert selbst war richtig, nur die Formatprüfung zu eng.
Behoben in defd4a8: Der Check fragt jetzt nach beliebig vielen Nachkommastellen — worum es ihm immer ging —, die Begründung steht als Kommentar daneben. Lokal nachgeprüft mit ./check.sh (CHECK EXIT 0) und den Modulen 1–18 (ALL PASSED – 522 checks); die Check-Summe ändert sich nicht.
Ein weiterer Lauf davor war einmalig an einem anderen Check desselben Blocks rot (Bead-Abstände, [0.611, 0]); bei Wiederholung grün. Das war Last auf der Maschine — der CI-Lauf lief parallel —, in CI ist dieser Check grün geblieben.
Nachtrag: **Der CI-Lauf zu `5ba796a` war rot** — Gate (Task 174) grün, Suite (Task 175) mit genau **einem** FAIL in Modul 18:
```
FAIL while playing, each output shows the value it last carried in front of its name –
{"update":["","0.05 dt"], …}
```
**Ursache, kein echter Fehler am Stand:** Der Check verlangte das Format `/^0\.\d{3} dt$/`, also exakt drei Nachkommastellen. `formatNumber` schneidet aber nachlaufende Nullen weg, und ein Frame von genau 50 ms liest sich dann als `0.05` statt `0.050`. Auf dieser Maschine trifft die Frame-Zeit nie eine runde Zahl, deshalb war es lokal immer grün. Der Wert selbst war richtig, nur die Formatprüfung zu eng.
**Behoben in `defd4a8`:** Der Check fragt jetzt nach beliebig vielen Nachkommastellen — worum es ihm immer ging —, die Begründung steht als Kommentar daneben. Lokal nachgeprüft mit `./check.sh` (`CHECK EXIT 0`) und den Modulen 1–18 (`ALL PASSED – 522 checks`); die Check-Summe ändert sich nicht.
Ein weiterer Lauf davor war einmalig an einem anderen Check desselben Blocks rot (Bead-Abstände, `[0.611, 0]`); bei Wiederholung grün. Das war Last auf der Maschine — der CI-Lauf lief parallel —, in CI ist dieser Check grün geblieben.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Folge von #117. Dort entschieden: Plugins werden vor dem Projekt geladen, und ob ein Plugin läuft, entscheidet das Projekt. Der serverweite Schalter aus #114 wird damit ersetzt.
Warum beides ein Issue ist
Beides greift in denselben Startweg (
src/editor/App.ts:44: ersteditor.boot(), dannstartPlugins). Welche Plugins an sind, steht künftig in der Projektdatei — die muss also gelesen sein, bevor ein Plugin geladen wird. Getrennt gebaut würde der Startweg zweimal umgebaut.Der Startweg, wie er werden soll
GET ?r=plugins) und die Projektdatei als rohes JSON holen.activaterufen.deserializeProjectund den Editor bauen.Schritt 4 nach 3 ist der Punkt:
normalizeGraph(src/core/store/serialize.ts:337) und die Typprüfung der Assets (serialize.ts:85) werfen beim Deserialisieren weg, was sie nicht kennen. Was ein Plugin anmeldet, muss vorher dastehen — sonst sind eigene Node-Typen (#126) gar nicht möglich. Die Projektdatei zu holen ist dabei nicht dasselbe wie das Projekt zu laden: weggeworfen wird erst beim Deserialisieren.Zu bauen
plugins: string[](Dateinamen), Default[], im Schema vondeserializeProject. Fehlt es, ist es leer — keinformatVersion-Bump nötig.data/plugins/plugins.json,PluginStore::setActiveund die RoutePUT ?r=plugins/{file}entfallen, ebenso der 403 für inaktive Module.deactivate, Scope aufräumen wie in #114), die anderen angeschaltet.api.project.assets()undapi.project.on('change')müssen aushalten, dass es währendactivatenoch kein Projekt gibt: leere Liste, der erstechangekommt, wenn das Projekt steht.tools/lib/plugins.mjs: Build undseed-e2eschalten das Demo-Plugin nicht mehr inplugins.jsonein, sondern in den Seed-Projekten.Plugins.md,API.md(Route weg),Architecture.md, README/Check-Summe.Eine Folge, bewusst in Kauf genommen
Bisher wurde ein ausgeschaltetes Plugin nie ausgeliefert (403, #114). Mit der Aktivierung im Projekt kann der Server das nicht mehr wissen: jede Plugin-Datei ist abrufbar, ausgeführt wird sie nur, wenn das offene Projekt sie nennt. Solange es keine Benutzerverwaltung gibt, ist das ohne Belang (Entscheidung in #117). Für #115 und #116 ist es dort zu berücksichtigen.
Offen (mit Vorschlag)
vorschläge passen
Gebaut und gepusht als
5ba796a, Wikib6614c5.Was jetzt gilt
plugins: ["demo-plugin.js"](Default leer, keinformatVersion-Bump). Der Schalter im Plugins-Tab schreibt dorthin, als gewöhnlicher Undo-Schritt (SetPluginActiveCommand) — so wie in den Rückfragen besprochen. Ein neues Projekt schaltet nichts ein.startPluginsholt nur die Liste der Dateien, danacheditor.boot(), undProjectStore.load(id, prepare)reicht das rohe JSON durch, bevor deserialisiert wird. Dort liestpluginsOf(raw)die Liste undPluginManager.syncTo()aktiviert genau diese Plugins. Erst danach läuftdeserializeProject— genau der Punkt des Issues, sonst würdenormalizeGraphdie vom Plugin angemeldeten Knoten als unbekannt verwerfen (#126, #127).plugins.json,PluginStore::setActive,isActive, die RoutePUT ?r=plugins/{file}und der 403 für inaktive Module sind weg,GET ?r=pluginsliefert nur noch{file,bytes,updatedAt}. Damit ist jede Plugin-Datei abrufbar — die in #117 bewusst in Kauf genommene Folge; sie steht als Absatz im Wiki, damit #115 und #116 sie berücksichtigen.Zu wissen: Vorher war das Demo-Plugin serverweit eingeschaltet, also auch im Playground und in den Demo-Projekten. Diese Projektdateien haben kein
plugins-Feld, und das heißt „keines" — im Plugins-Tab pro Projekt wieder einschaltbar.Tests
project.pluginsleer, während der Server die Datei weiter listet; Undo nimmt den Schalter zurück.pluginsOfgegen krude Eingaben, Undo/Redo, Projektwechsel, unbekannter Dateiname.startPluginswieder nacheditor.boot()gerufen → Modul 38 fällt mit 5 FAILs. Original zurückgeprüft und neu gebaut.Falle fürs Protokoll: Pixis SVG-Parser zeichnet ein
Anach einemLals Zacken statt als Bogen. Knopf und Aussparung des Puzzleteils sind deshalb kubische Kurven.Nachtrag: Der CI-Lauf zu
5ba796awar rot — Gate (Task 174) grün, Suite (Task 175) mit genau einem FAIL in Modul 18:Ursache, kein echter Fehler am Stand: Der Check verlangte das Format
/^0\.\d{3} dt$/, also exakt drei Nachkommastellen.formatNumberschneidet aber nachlaufende Nullen weg, und ein Frame von genau 50 ms liest sich dann als0.05statt0.050. Auf dieser Maschine trifft die Frame-Zeit nie eine runde Zahl, deshalb war es lokal immer grün. Der Wert selbst war richtig, nur die Formatprüfung zu eng.Behoben in
defd4a8: Der Check fragt jetzt nach beliebig vielen Nachkommastellen — worum es ihm immer ging —, die Begründung steht als Kommentar daneben. Lokal nachgeprüft mit./check.sh(CHECK EXIT 0) und den Modulen 1–18 (ALL PASSED – 522 checks); die Check-Summe ändert sich nicht.Ein weiterer Lauf davor war einmalig an einem anderen Check desselben Blocks rot (Bead-Abstände,
[0.611, 0]); bei Wiederholung grün. Das war Last auf der Maschine — der CI-Lauf lief parallel —, in CI ist dieser Check grün geblieben.