FlowFuse / FlowFuse/node-red-dashboard

ui-template: replayed messages cannot be distinguished from live messages

Open
#2,192 2 comments 0 reactions 0 assignees View on GitHub
needs-triage
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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.