FlowFuse / FlowFuse/node-red-dashboard
ui-template: replayed messages cannot be distinguished from live messages
- Dominant language
- HTML
- Stars
- 355
- Forks
- 82
- Avg merge
- 4d 23h
- Merged PRs (30d)
- 24
Description
### Current Behavior
When a Dashboard `ui-template` is loaded/reloaded, Dashboard replays the last message stored for that widget. The replayed message appears identical to the original live message: it retains the original `_msgid`, payload and custom properties, with no metadata indicating that it came from the Dashboard datastore.
This is problematic when the authoritative state is maintained elsewhere in Node-RED. In my case, a mailbox widget receives `count: 1` when mail arrives, while the authoritative current state is stored in global context. After the mailbox is cleared, the authoritative state can be `count: 0`, but a newly loaded Dashboard can initially replay an older `count: 1` message.
I use `ui-event` / `$pageview` to fetch the authoritative state and send it to the `ui-template`:
```text
ui-event ($pageview)
-> Fetch global state
-> ui-template
```
This means two independent messages can reach the widget during page loading: the Dashboard datastore replay and the current state fetched in response to `$pageview`.
I have observed the replay followed by the current state, but I cannot find a documented ordering guarantee. If the two mechanisms race, simply overwriting the replay with the `$pageview` result is not sufficient.
The workaround I now use is to ignore all messages until an explicit `fetchGlobal` message initializes the widget, and to attach a state timestamp/version to backend state so that an older replay arriving after initialization can also be rejected. This works, but requires application-specific synchronization logic solely because the template cannot identify a replayed message.
Checking `_msgid` alone does not help: normal live Node-RED messages also have `_msgid`, and the replay preserves the original value.
Exported `ui-template` nodes also contain properties such as:
```json
"storeOutMessages": true,
"passthru": false,
"resendOnRefresh": true
```
`resendOnRefresh` sounds related, but I cannot find a corresponding option in the current `ui-template` editor, and it is unclear whether this property is currently implemented/used by Dashboard 2.
### Expected Behavior
For applications where Node-RED owns the authoritative state, there should ideally be a supported way to distinguish datastore replay from a new live message.
For example, replayed messages could carry metadata such as:
```javascript
msg._dashboard = {
replay: true
};
```
Alternatively, it would be useful to allow datastore replay to be disabled per widget, e.g. an option such as `Restore last message on client load`.
A documented guarantee that datastore replay always occurs before `$pageview` would also make explicit synchronization possible, although marking replayed messages or allowing replay to be disabled would seem more robust.
### Steps To Reproduce
1. Create a `ui-template` that displays a value from `msg.payload.count`.
2. Send a live message to it, for example:
```javascript
msg.payload = {
count: 1,
date: Date.now()
};
msg.mailSource = "increase";
return msg;
```
3. Observe the message in the template. For example:
```javascript
{
payload: {
count: 1,
date: 1786717839872
},
topic: "",
_msgid: "4c284f268c64dd58",
mailSource: "increase"
}
```
4. Maintain/change the authoritative application state separately (for example, set the corresponding global state to `count: 0`).
5. Add a `ui-event` `$pageview` path that reads that authoritative state and sends `count: 0` to the `ui-template` when the page loads.
6. Reload/open the Dashboard page.
7. Observe that the `ui-template` also receives the previously stored `count: 1` message. From inside the template it is indistinguishable from the original live message; the original `_msgid` and custom properties are preserved.
### Environment
- Dashboard version: 1.30.2
- Node-RED version: 5
- Node.js version:
- npm version:
- Platform/OS:
- Browser:
### Have you provided an initial effort estimate for this issue?
I am not a FlowFuse team member
Contributor guide
No contributing guide indexed for this repository
Research direction
Start at the ui-template datastore replay path and the ui-event/$pageview flow described in the reproduction; inspect how storeOutMessages, passthru, and resendOnRefresh are handled. Done means the Dashboard provides a defined way to identify or suppress replayed messages, or documents a reliable ordering guarantee, with behavior covered by the relevant tests.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- html, javascript
- Domain
- frontend
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 38/100