Keep updating ui-template

I have a process that takes some time to complete and I wanted to add a Vue Timeline to show which steps are being executed. So the idea was that in the process (long chain of various nodes), at certain steps I branch out to the below ui-template node. So the same message is sent to this UI template, with different content, some properies are getting added as more steps are executed in the process.

The ui-template looks like this:

<template>
    <v-timeline side="end">
        <v-timeline-item :dot-color="voice_color" size="small">
            <div class="d-flex">
                <strong class="me-4">{{voice_amount}}</strong>
                <div>
                    <strong>Audio recording</strong>
                    <div class="text-body-small">
                        {{voice_text}}
                    </div>
                </div>
            </div>
        </v-timeline-item>

        <v-timeline-item :dot-color="whisper_color" size="small">
            <div class="d-flex">
                <strong class="me-4">{{whisper_amount}}</strong>
                <div>
                    <strong>Transcribe Audio</strong>
                    <div class="text-body-small">
                        {{whisper_text}}
                    </div>
                </div>
            </div>
        </v-timeline-item>

        <v-timeline-item :dot-color="llm_color" size="small">
            <div class="d-flex">
                <strong class="me-4">{{llm_amount}}</strong>
                <div>
                    <strong>Recognize Intent</strong>
                    <div class="text-body-small">
                        {{llm_text}}
                    </div>
                </div>
            </div>
        </v-timeline-item>

    </v-timeline>
</template>

<script>
    export default {
        data() {
            // define variables available component-wide
            // (in <template> and component functions)
            return {
                voice_color: 'grey',
                voice_text: 'No Audio',
                voice_amount: '',
                whisper_color: 'grey',
                whisper_text: 'Not completed yet',
                whisper_amount: '',
                llm_color: 'grey',
                llm_text: 'Not triggered yet',
                llm_amount: '',
            }
        },
        watch: {
            // watch for any changes of "count"

            msg: function(){
                    this.resetDefaults();

                    if (this.msg.rec_start) {
                        this.voice_color = 'red';
                        this.voice_amount = '';
                        this.voice_text = 'Recording...';
                    } 
                    if (this.msg.fname !== undefined) {
                        this.voice_color = 'green';
                        this.voice_amount = 'Saved';
                        this.voice_text = this.msg.fname;
                        this.whisper_color = 'yellow';
                    } 
                    if (this.msg.transcribedtext !== undefined) {
                        this.whisper_color = 'green';
                        this.whisper_amount = this.msg.whisperseconds + "ms";
                        this.whisper_text = this.msg.transcribedtext;
                        this.llm_color = 'yellow';
                    } 
                    if (this.msg.llmoutputstep) {
                        if (!this.msg.payload.done) { 
                            this.llm_color = 'red';
                            this.llm_amount = this.msg.llmseconds + "ms";
                            this.llm_text = "Error";
                        } else {
                            this.llm_color = 'green';
                            this.llm_amount = this.msg.llmseconds + "ms";
                            this.llm_text = this.msg.payload.message.content;
                        }
                    }
                
            }
        },
        computed: {

        },
        methods: {
            // expose a method to our <template> and Vue Application
            resetDefaults() {
                this.voice_color = 'grey';
                this.voice_text = 'No Audio';
                this.voice_amount = '';
                this.whisper_color = 'grey';
                this.whisper_text = 'Not completed yet';
                this.whisper_amount = '';
                this.llm_color = 'grey';
                this.llm_text = 'Not triggered yet';
                this.llm_amount = '';
            }
        },
        mounted() {
            // code here when the component is first loaded
        },
        unmounted() {
            // code here when the component is removed from the Dashboard
            // i.e. when the user navigates away from the page
        }
    }
</script>

It sort of works and sort of not:

  • when I restart the process, I would expect step 2 and 3 to be grey because the default values, but they seem to show the values from the previous message. Although the new message clearly does not have the properties in the if statements.
  • if I send an empty payload, it does not reset the UI to the grey states that I expect.

I have not done much Node-Red work in the past few months, but I am sure I have not forgotten that much, and the basic if logic is correct. If certain attributes do not exists the UI should fall back to the default color/text.

I asked Gemini about this, he said that insteal of this.msg.... I should change the watch to a handler: function(val) { and deep: true, but that seem to make no difference at all.

I am looking at this for a few hours now and just can't figure out what I have done wrong.

P.S. ignore some of the comments on the code, it is from an earlier template node.