Plugin support: Plugins vor dem Projekt laden, Aktivierung pro Projekt #119

Closed
opened 2026-09-17 20:30:08 +02:00 by meleki · 3 comments
Owner

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.
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.
admin added this to the Development project 2026-09-17 20:32:57 +02:00
admin moved this to Next in Development on 2026-09-20 11:00:15 +02:00

vorschläge passen

vorschläge passen
admin added the question label 2026-09-20 13:39:10 +02:00
admin removed the question label 2026-09-20 13:40:29 +02:00
meleki referenced this issue from a commit 2026-09-20 14:24:29 +02:00
Plugins belong to the project, and they load before it: the project file gained the field plugins: ["demo-plugin.js"] (default empty, no formatVersion bump), the Plugins tab's switch writes into it as an ordinary undo step (SetPluginActiveCommand) instead of into a server-wide plugins.json, and the start order is turned around - startPlugins only fetches the list of files, then editor.boot() runs and ProjectStore.load(id, prepare) hands the raw JSON through before it is deserialized, where pluginsOf(raw) reads the list and PluginManager.syncTo() activates exactly those; that is the point of the issue, because what a plugin registers has to stand there before normalizeGraph would throw it away as unknown (#126, #127), a project switch first stops what the new project does not name and then starts what it does, a file the server does not have is passed over silently, and after signing out the manager is dead so a late event cannot start anything in a torn-down session; on the server plugins.json, PluginStore::setActive, isActive, the route PUT ?r=plugins/{file} and the 403 for inactive modules are gone and GET ?r=plugins only answers {file,bytes,updatedAt}, which makes every plugin file fetchable - the consequence accepted in #117, written down in the wiki for #115 and #116; publishPlugins(root) only copies files now, the seeder switches the demo plugin on in the fixture project, and the Plugins tab carries a puzzle piece as its icon while only a plugin's own tab still goes without one (the knob and the socket are cubic curves, not arcs, which Pixi's parser draws as a spike after an L); 3 new e2e checks in module 38 and 5 new unit tests cover the project as the owner of the switch, its undo, the order, pluginsOf against crude input and an unknown file name (#119)
Author
Owner

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.

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.
admin moved this to Done in Development on 2026-09-20 14:26:11 +02:00
Author
Owner

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.
Sign in to join this conversation.
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: meleki/feuerwerk#119