Hilfe bei MessageBus Echtzeit-Updates im Lotterie-Plugin (entwickelt mit KI-Unterstützung)

Hallo zusammen,

ich entwickle ein Lotterie-Plugin für Discourse, bei dem Benutzer teilnehmen können, indem sie auf ein Lotterie-Thema antworten. Offenlegung: Ich bin kein Programmierer – ich baue das komplett mit KI-Unterstützung (Claude Code), daher fehlen mir möglicherweise grundlegende Konzepte. Die grundlegende Funktionalität funktioniert, aber ich habe Probleme mit Echtzeit-Updates über MessageBus. Ich würde mich über jeden Rat zum richtigen Vorgehen freuen.

Was ich erreichen möchte

  • Einen Lotterie-Beitrag mit BBCode [lottery]...[/lottery] erstellen
  • Eine Lotterie-Karte im Beitrag anzeigen, die die Teilnehmerzahl zeigt
  • Wenn Benutzer zur Teilnahme antworten, sollen alle Zuschauer die Teilnehmerzahl in Echtzeit aktualisiert sehen, ohne die Seite neu laden zu müssen
  • Ähnlich wie das discourse-calendar-Plugin Ereignisinformationen aktualisiert

Aktuelle Implementierung

Backend (Ruby)

Lottery-Modell (app/models/lottery.rb):

def publish_update!
  channel = "/lottery/#{self.post.topic_id}"
  message = { id: self.id }

  MessageBus.publish(channel, message)
end

LotteryParticipant-Modell (app/models/lottery_participant.rb):

after_commit :publish_lottery_update, on: [:create, :destroy]

private

def publish_lottery_update
  self.lottery.publish_update!
end

Backend-Protokolle bestätigen, dass MessageBus.publish erfolgreich aufgerufen wird:

[Lottery] 📡 Publishing MessageBus update:
  Channel: /lottery/27
  Message: {:id=>52}
[Lottery] ✅ MessageBus.publish completed

Frontend (JavaScript)

Decorator (discourse-lottery-decorator.gjs):

api.decorateCookedElement((cooked, helper) => {
  const post = helper.getModel();

  if (!post?.lottery_data) return;

  // Prüfen, ob bereits dekoriert (KI schlug die Verwendung eines data-Attributs vor)
  if (cooked.dataset.lotteryDecorated === "true") return;

  const lotteryNode = cooked.querySelector(".discourse-lottery");
  if (!lotteryNode) return;

  const wrapper = document.createElement("div");
  lotteryNode.before(wrapper);

  const lottery = Lottery.create(post.lottery_data);

  helper.renderGlimmer(
    wrapper,
    `<template><DiscourseLottery @lotteryData={{lottery}} /></template>`
  );

  lotteryNode.remove();
  cooked.dataset.lotteryDecorated = "true";
}, { id: "discourse-lottery" });

Komponente (discourse-lottery/index.gjs):

export default class DiscourseLottery extends Component {
  @service messageBus;
  @service lotteryApi;

  constructor() {
    super(...arguments);

    const { lotteryData } = this.args;
    if (lotteryData?.topicId) {
      this.lotteryPath = `/lottery/${lotteryData.topicId}`;
      this.lotteryData = lotteryData;

      // KI schlug die manuelle Bindung der Callback-Funktion vor
      this._boundOnLotteryUpdate = this._onLotteryUpdate.bind(this);
      this.messageBus.subscribe(this.lotteryPath, this._boundOnLotteryUpdate);

      registerDestructor(this, () => {
        this.messageBus.unsubscribe(this.lotteryPath, this._boundOnLotteryUpdate);
      });
    }
  }

  async _onLotteryUpdate(msg) {
    console.log('[Lottery Component] 🔔 MessageBus message received!');

    const updatedData = await this.lotteryApi.lottery(this.lotteryData.topicId);
    this.lotteryData.updateFromLottery(updatedData);
  }
}

Modell (models/lottery.js):

export default class Lottery {
  @tracked _stats;

  set stats(stats) {
    this._stats = LotteryStats.create(stats || {});
  }

  updateFromLottery(lottery) {
    // Alle Eigenschaften aktualisieren, einschließlich Stats
    this.stats = lottery.stats || {};
    // ... andere Eigenschaften
  }
}

Das Problem

Nur die Seite des Teilnehmers aktualisiert sich in Echtzeit, aber die Seiten anderer Zuschauer werden ohne Aktualisierung nicht aktualisiert.

Was funktioniert:

  • :white_check_mark: Wenn ein Benutzer zur Teilnahme antwortet, zeigt seine eigene Seite die Echtzeit-Aktualisierung der Teilnehmerzahl an
  • :white_check_mark: Das Backend veröffentlicht MessageBus-Nachrichten erfolgreich
  • :white_check_mark: Die MessageBus-Nachricht wird auf der Seite des Teilnehmers empfangen (Protokolle zeigen “:bell: MessageBus message received!”)
  • :white_check_mark: Das Modell wird mit der neuen Teilnehmerzahl aktualisiert (Protokolle zeigen, wie die Zahl von 0 auf 1 steigt)

Was nicht funktioniert:

  • :cross_mark: Andere Zuschauer, die das Lotterie-Thema verfolgen, sehen die Zählung nicht in Echtzeit aktualisiert
  • :cross_mark: Die Seite des Lotterie-Erstellers wird nicht aktualisiert
  • :cross_mark: Jede andere Benutzerseite, auf der das Lotterie-Thema geöffnet ist, wird nicht aktualisiert
  • Sie müssen alle die Seite manuell aktualisieren, um die aktualisierte Teilnehmerzahl zu sehen

Zusätzlich sehe ich, dass die Komponente mehrmals in der Konsole zerstört und neu erstellt wird:

[Lottery Component] ✅ MessageBus subscription established
[Lottery Component] 🧹 Cleaning up MessageBus for topic: 27
[Lottery Component] ✅ MessageBus subscription established
[Lottery Component] 🧹 Cleaning up MessageBus for topic: 27

Dies deutet darauf hin, dass decorateCookedElement mehrmals ausgelöst wird, was dazu führt, dass Komponenten zerstört und neu erstellt werden, was möglicherweise das MessageBus-Abonnement für andere Zuschauer unterbricht.

Fragen

  1. Ist die Verwendung des data-decorated-Attributs auf dem dekorierten Element die richtige Methode, um doppelte Dekorationen zu verhindern? Die KI hat diesen Ansatz vorgeschlagen, aber ich konnte keine offizielle Dokumentation finden, die dies als empfohlene Methode bestätigt.

  2. Warum aktualisiert sich die Benutzeroberfläche nicht, obwohl sich die verfolgte Modelleigenschaft ändert? Die @tracked _stats sollten ein erneutes Rendern auslösen, wenn sie über this.stats = lottery.stats aktualisiert werden, aber die angezeigte Teilnehmerzahl bleibt gleich. Gibt es ein Problem damit, wie ich @tracked verwende?

  3. Sollte ich einen völlig anderen Ansatz verwenden? Ich habe die KI gebeten, dem Muster von discourse-calendar zu folgen, aber vielleicht übersehe ich etwas Grundlegendes daran, wie Discourse Echtzeit-Updates handhabt.

  4. Verursacht das Entfernen des ursprünglichen lotteryNode das Problem? Die KI hat zunächst versucht, es nicht zu entfernen, aber das führte dazu, dass die Lotterie-Karte durch CSS (.cooked > .discourse-lottery { display: none; }) ausgeblendet wurde. Gibt es ein besseres Muster?

Da ich kein Programmierer bin, stelle ich möglicherweise grundlegende Fragen – jeder Hinweis auf Dokumentation oder Beispiele wäre äußerst hilfreich! Danke!

2 „Gefällt mir“