Hi everyone,
I’m developing a lottery plugin for Discourse where users can participate by replying to a lottery topic. Full disclosure: I’m not a programmer - I’m building this entirely with AI assistance (Claude Code), so I may be missing fundamental concepts. The basic functionality works, but I’m having issues with real-time updates using MessageBus. I’d appreciate any guidance on the correct approach.
What I’m trying to achieve
- Create a lottery post using BBCode
[lottery]...[/lottery]
- Display a lottery card in the post showing participant count
- When users reply to participate, all viewers should see the participant count update in real-time without page refresh
- Similar to how the discourse-calendar plugin updates event information
Current implementation
Backend (Ruby)
Lottery model (app/models/lottery.rb
):
def publish_update!
channel = "/lottery/#{self.post.topic_id}"
message = { id: self.id }
MessageBus.publish(channel, message)
end
LotteryParticipant model (app/models/lottery_participant.rb
):
after_commit :publish_lottery_update, on: [:create, :destroy]
private
def publish_lottery_update
self.lottery.publish_update!
end
Backend logs confirm MessageBus.publish is called successfully:
[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
}
}
The problem
Only the participant’s page updates in real-time, but other viewers’ pages don’t update without refreshing.
What works:
When a user replies to participate, their own page shows the participant count update in real-time
Backend publishes MessageBus messages successfully
MessageBus message is received on the participant’s page (logs show “
MessageBus message received!”)
Model updates with new participant count (logs show count increases from 0 to 1)
What doesn’t work:
Other viewers watching the lottery topic don’t see the count update in real-time
The lottery creator’s page doesn’t update
Any other user’s page that has the lottery topic open doesn’t update
- They all need to manually refresh the page to see the updated participant count
Additionally, I see the component being destroyed and recreated multiple times in the 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
This suggests decorateCookedElement
is being triggered multiple times, causing components to be destroyed and recreated, which might be breaking the MessageBus subscription for other viewers.
Questions
-
Is using
data-decorated
attribute on the cooked element the correct way to prevent duplicate decoration? The AI suggested this approach, but I couldn’t find official documentation confirming it’s the recommended pattern. -
Why is the UI not updating even though the tracked model property changes? The
@tracked _stats
should trigger re-render when updated viathis.stats = lottery.stats
, but the displayed participant count stays the same. Is there something wrong with how I’m using@tracked
? -
Should I be using a different approach entirely? I asked the AI to follow the discourse-calendar pattern, but maybe I’m missing something fundamental about how Discourse handles real-time updates.
-
Is removing the original
lotteryNode
causing the issue? The AI initially tried not removing it, but that caused the lottery card to be hidden by CSS (.cooked > .discourse-lottery { display: none; }
). Is there a better pattern?
Since I’m not a programmer, I may be asking basic questions - any pointers to documentation or examples would be incredibly helpful! Thank you!