Aiuto con aggiornamenti MessageBus in tempo reale per il plugin della lotteria (sviluppato con assistenza AI)

Ciao a tutti,

Sto sviluppando un plugin per lotterie per Discourse in cui gli utenti possono partecipare rispondendo a un argomento della lotteria. In piena trasparenza: non sono un programmatore, sto costruendo questo interamente con l’assistenza dell’IA (Claude Code), quindi potrei trascurare concetti fondamentali. La funzionalità di base funziona, ma ho problemi con gli aggiornamenti in tempo reale utilizzando MessageBus. Apprezzerei qualsiasi guida sull’approccio corretto.

Cosa sto cercando di ottenere

  • Creare un post della lotteria utilizzando BBCode [lottery]...[/lottery]
  • Visualizzare una scheda della lotteria nel post che mostra il conteggio dei partecipanti
  • Quando gli utenti rispondono per partecipare, tutti gli spettatori dovrebbero vedere il conteggio dei partecipanti aggiornarsi in tempo reale senza aggiornamento della pagina
  • Simile a come il plugin discourse-calendar aggiorna le informazioni sugli eventi

Implementazione attuale

Backend (Ruby)

Modello Lottery (app/models/lottery.rb):

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

  MessageBus.publish(channel, message)
end

Modello LotteryParticipant (app/models/lottery_participant.rb):

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

private

def publish_lottery_update
  self.lottery.publish_update!
end

I log del backend confermano che MessageBus.publish viene chiamato con successo:

[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;

  // Check if already decorated (AI suggested using data attribute)
  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" });

Component (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;

      // AI suggested manually binding the callback
      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);
  }
}

Model (models/lottery.js):

export default class Lottery {
  @tracked _stats;

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

  updateFromLottery(lottery) {
    // Update all properties including stats
    this.stats = lottery.stats || {};
    // ... other properties
  }
}

Il problema

Solo la pagina del partecipante si aggiorna in tempo reale, ma le pagine degli altri spettatori non si aggiornano senza refresh.

Cosa funziona:

  • :white_check_mark: Quando un utente risponde per partecipare, la sua pagina mostra l’aggiornamento in tempo reale del conteggio dei partecipanti
  • :white_check_mark: Il backend pubblica messaggi MessageBus con successo
  • :white_check_mark: Il messaggio MessageBus viene ricevuto sulla pagina del partecipante (i log mostrano “:bell: MessageBus message received!”)
  • :white_check_mark: Il modello viene aggiornato con il nuovo conteggio dei partecipanti (i log mostrano il conteggio aumentare da 0 a 1)

Cosa non funziona:

  • :cross_mark: Gli altri spettatori che guardano l’argomento della lotteria non vedono il conteggio aggiornarsi in tempo reale
  • :cross_mark: La pagina del creatore della lotteria non si aggiorna
  • :cross_mark: La pagina di qualsiasi altro utente che ha aperto l’argomento della lotteria non si aggiorna
  • Tutti devono aggiornare manualmente la pagina per vedere il conteggio aggiornato dei partecipanti

Inoltre, vedo il componente che viene distrutto e ricreato più volte nella console:

[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

Ciò suggerisce che decorateCookedElement viene attivato più volte, causando la distruzione e la ricreazione dei componenti, il che potrebbe interrompere la sottoscrizione MessageBus per gli altri spettatori.

Domande

  1. L’utilizzo dell’attributo data-decorated sull’elemento cotto è il modo corretto per prevenire decorazioni duplicate? L’IA ha suggerito questo approccio, ma non ho trovato documentazione ufficiale che lo confermi come pattern consigliato.

  2. Perché l’interfaccia utente non si aggiorna anche se la proprietà del modello tracciata cambia? La @tracked _stats dovrebbe attivare il re-render quando aggiornata tramite this.stats = lottery.stats, ma il conteggio dei partecipanti visualizzato rimane lo stesso. C’è qualcosa che non va nel modo in cui utilizzo @tracked?

  3. Dovrei utilizzare un approccio completamente diverso? Ho chiesto all’IA di seguire il modello discourse-calendar, ma forse mi sfugge qualcosa di fondamentale su come Discourse gestisce gli aggiornamenti in tempo reale.

  4. La rimozione di lotteryNode originale causa il problema? L’IA inizialmente ha provato a non rimuoverlo, ma ciò ha causato la scomparsa della scheda della lotteria a causa del CSS (.cooked > .discourse-lottery { display: none; }). Esiste un pattern migliore?

Dato che non sono un programmatore, potrei fare domande basilari - qualsiasi indicazione su documentazione o esempi sarebbe incredibilmente utile! Grazie!

2 Mi Piace