diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 1eab128961b26274a16980a06e0958b0fbb70ac4..2d549ff8530cc217b74cf891886ff2552d117a38 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -119,3 +119,4 @@ deploy to dev.pasteur.cloud:
     - if: ($CI_COMMIT_BRANCH == "dev" &&
         $CI_PROJECT_ID == $GITLAB_PASTEUR_PROJECT_ID)      # gitlab.pasteur.fr only
       when: manual
+
diff --git a/README.md b/README.md
index 0b5cf10fc87d22e3e55571c56418cf4b6e52267c..bfddaa8a59120b2c8c1835ac81a578b8cd9506fc 100644
--- a/README.md
+++ b/README.md
@@ -7,3 +7,25 @@ Web interface meant to be served at [nyx.pasteur.cloud](https://nyx.pasteur.clou
 It features an app catalog, with currently a single available app:
 
 * an editor for muscular activity programs.
+
+
+## Local installation
+
+For local execution, the installation instructions are as follows:
+
+You will need [JuliaUp](https://github.com/JuliaLang/juliaup?tab=readme-ov-file#juliaup---julia-version-manager) and [git](https://git-scm.com/downloads).
+
+Once these tools are installed, in a terminal (on Windows, preferably PowerShell), type:
+```
+git clone --branch dev https://gitlab.pasteur.fr/nyx/NyxUI.jl NyxUI
+cd NyxUI
+cp front/Manifest.toml .
+juliaup add lts
+juliaup default lts
+julia --project=. -e 'using Pkg; Pkg.instantiate()'
+julia --project=. routes.jl
+```
+
+You may be asked whether to authorize ports 9284 and 9285; please give the app permission.
+
+From there, in a web browser, open http://localhost:9284/ to access the app.
diff --git a/src/MuscleActivities.jl b/src/MuscleActivities.jl
index 86b74a322943c47270c03d17afa896e522d4ce12..57911ca511edf4c89124e4a2d6fbc4b034c51358 100644
--- a/src/MuscleActivities.jl
+++ b/src/MuscleActivities.jl
@@ -3,6 +3,7 @@ module MuscleActivities
 using StructTypes
 using NyxWidgets.Muscles
 using Random
+using Dates
 using JSON3
 using StructTypes
 using OrderedCollections: OrderedDict
@@ -217,8 +218,18 @@ from_json_file(::Type{T}, filepath) where {T} = JSON3.read(read(filepath, String
 StructTypes.StructType(::Type{MuscleActivity}) = StructTypes.CustomStruct()
 
 function StructTypes.lower(seq::MuscleActivity)
+    version = string(pkgversion(@__MODULE__))
+    if endswith(version, ".0")
+        version = version[1:end-2]
+    end
+    datetime = Dates.format(Dates.now(), "yyyymmdd_HHMMSS")
     Dict = OrderedDict
     Dict("name" => seq.program_name,
+         "metadata" =>
+         Dict("software" =>
+              Dict("name" => "NyxUI",
+                   "version" => version),
+              "date_time" => datetime),
          "time" =>
          Dict("start" => seq.times[1],
               "step" => step(seq.times),
diff --git a/src/apps/muscles/Backbone.jl b/src/apps/muscles/Backbone.jl
index 21868e65dd545970b26baa197231faf9a1808589..eb4758a59f0e5864392c222e985a7d631015d9b1 100644
--- a/src/apps/muscles/Backbone.jl
+++ b/src/apps/muscles/Backbone.jl
@@ -9,7 +9,7 @@ include("MuscleWidgets.jl")
 using .MuscleWidgets
 
 export hasmodel, getmodel, setmodel, newsequence, getsequence, withsequences,
-       exportsequence, loadsequence
+       exportsequence, loadsequence, deletesequence
 
 # mutable struct Backbone{W}
 #     widget::Union{Nothing, W}
@@ -150,4 +150,20 @@ function loadsequence(session_id, filepath)
     return sequence
 end
 
+function deletesequence(session_id)
+    model = getmodel(session_id)
+    current = model.sequence[].program_name
+    sequences = __sequences__[session_id]
+    lock(sequences) do
+        for (ix, seq) in pairs(sequences)
+            if seq.program_name == current
+                current = ix
+                break
+            end
+        end
+        @assert current isa Int
+        pop!(sequences.cache, current)
+    end
+end
+
 end
diff --git a/src/apps/muscles/MuscleWidgets.jl b/src/apps/muscles/MuscleWidgets.jl
index 21a81d04e8e0d699a4f4ddb14dd5d40e5b10abcf..e5577ed86345d7c58f1d200b59fb2ccd05e3ea1c 100644
--- a/src/apps/muscles/MuscleWidgets.jl
+++ b/src/apps/muscles/MuscleWidgets.jl
@@ -1,6 +1,5 @@
 module MuscleWidgets
 
-using Base: has_nondefault_cmd_flags
 using NyxWidgets.Players, NyxWidgets.Muscles, NyxWidgets.AnimatedLayers
 import NyxWidgets.Base: lowerdom, dom_id, dom_id!, Div
 using NyxPlots
diff --git a/src/apps/muscles/app.jl b/src/apps/muscles/app.jl
index b24ccc970093d408c5bd7e24ecb17515264c34e1..082584919dcbe8e831b50d195d406f2e2ff249b1 100644
--- a/src/apps/muscles/app.jl
+++ b/src/apps/muscles/app.jl
@@ -34,9 +34,9 @@ const bonito_app = NamedApp(:inherit, Backbone.app)
 
     @out export_sequence_link = ""
     @in export_sequence_click = false
-    #@in delete_sequence_click = false
 
     @in colormap = default_colormap
+    @out first_time_in_editmode = true # and in the heatmap view
     @in editmode = false
     @out editmode_disable = true
     @in setvalue = 1.0
@@ -44,9 +44,14 @@ const bonito_app = NamedApp(:inherit, Backbone.app)
     @in time_interval = 0.05
     @in start_time = 0.0
 
+    @out heatmap_view = false
+
     @out area_selected = false
     @out new_clipboard_item = false
 
+    @in delete_sequence_click = false
+    @out no_sequences_yet = true
+
     @onchange isready begin
         _, url = init_model(__model__)
         run(__model__, """
@@ -71,6 +76,7 @@ const bonito_app = NamedApp(:inherit, Backbone.app)
             series_length = length(seq.times)
             time_interval = step(seq.times)
             start_time = first(seq.times)
+            no_sequences_yet = isempty(sequence_names)
         end
         on(MuscleWidgets.clipboard(model)) do _
             new_clipboard_item[!] = false
@@ -80,6 +86,12 @@ const bonito_app = NamedApp(:inherit, Backbone.app)
             area_selected[!] = false
             area_selected = true
         end
+        on(model.animation.switchlayer) do layers
+            if !isnothing(layers)
+                current = layers[1]
+                heatmap_view = 1 < current
+            end
+        end
     end
 
     @onchange start_time, time_interval, series_length begin
@@ -96,24 +108,29 @@ const bonito_app = NamedApp(:inherit, Backbone.app)
             end
             valid = false
         end
+        previous_length = length(seq.times)
+        new_length = round(Int, series_length)
         if series_length < 1
             @notify "Time series length must be strictly positive" :error
-            series_length = length(seq.times)
+            series_length = previous_length
             valid = false
-        elseif round(series_length) != series_length
+        elseif series_length != new_length
             @notify "Time series length must be an integer" :error
-            series_length = round(series_length)
+            series_length = new_length
             valid = false
-        elseif !isnothing(maxlength) && maxlength < series_length
+        elseif !isnothing(maxlength) && maxlength < new_length
             @notify "Time series is too long" :error
-            series_length = length(seq.times)
+            series_length = previous_length
+            valid = false
+        elseif new_length < previous_length &&
+            startswith(string(previous_length), string(new_length))
+            @debug "Ignoring new size at the moment"
             valid = false
         end
         #
         if valid && !(start_time == seq.times[1] && time_interval == step(seq.times) &&
-                      series_length == length(seq.times))
-            n = round(Int, series_length)
-            stop_time = start_time + (n-1) * time_interval
+                      new_length == previous_length)
+            stop_time = start_time + (new_length - 1) * time_interval
             ts = start_time:time_interval:stop_time
             @info "Resampling" start_time time_interval series_length
             MuscleActivities.resample!(seq, ts)
@@ -140,6 +157,8 @@ const bonito_app = NamedApp(:inherit, Backbone.app)
         notify(start_time)
         notify(time_interval)
         notify(series_length)
+        # enable the download and delete buttons
+        no_sequences_yet = false
         # toggle edit mode on
         editmode = true
     end
@@ -149,6 +168,7 @@ const bonito_app = NamedApp(:inherit, Backbone.app)
     end
 
     @onchange selected_sequence_name begin
+        isempty(selected_sequence_name) && return
         seq = getsequence(back_session_id, selected_sequence_name)
         if !isnothing(seq)
             ix = findfirst(==(selected_sequence_name), sequence_names)
@@ -204,6 +224,20 @@ const bonito_app = NamedApp(:inherit, Backbone.app)
         model = getmodel(back_session_id)
         model.editmode[] = editmode
         editmode_disable = !editmode
+        if editmode && first_time_in_editmode && heatmap_view
+            first_time_in_editmode = false
+        end
+    end
+
+    @onchange heatmap_view begin
+        if heatmap_view && editmode && first_time_in_editmode
+            first_time_in_editmode = false
+        end
+    end
+
+    @onchange first_time_in_editmode begin
+        @assert !first_time_in_editmode
+        @notify "Toggle the \"edit mode\" off to browse the sequence with the heatmap" :info
     end
 
     @onchange setvalue begin
@@ -247,7 +281,10 @@ const bonito_app = NamedApp(:inherit, Backbone.app)
                 if !(sequence.program_name in sequence_names)
                     push!(sequence_names, sequence.program_name)
                 end
+                notify(sequence_names)
                 selected_sequence_name = sequence.program_name
+                # enable the download and delete buttons
+                no_sequences_yet = false
             catch exception
                 @error  "Failed to load file" exception
                 @notify "Failed to load file" :error
@@ -263,6 +300,37 @@ const bonito_app = NamedApp(:inherit, Backbone.app)
     @onchange area_selected begin
         @notify "Click inside the selected area to assign the setvalue, or press Ctrl while clicking to copy the selection" :info
     end
+
+    @onbutton delete_sequence_click begin
+        isempty(selected_sequence_name) && return
+        # update the model
+        deletesequence(back_session_id)
+        popat!(sequence_names, selected_sequence_index)
+        if length(sequence_names) < selected_sequence_index
+            selected_sequence_index[!] -= 1
+        end
+        if selected_sequence_index == 0
+            n = round(Int, series_length)
+            stop_time = start_time + (n-1) * time_interval
+            time_support = start_time:time_interval:stop_time
+            model = getmodel(back_session_id)
+            muscles = model.overview
+            sequence = MuscleActivity("", time_support, muscles)
+            # update the view
+            selected_sequence_name = ""
+            # update the view
+            sequence_name = ""
+            # disable the download and delete buttons (view)
+            no_sequences_yet = true
+            # update the model
+            model.sequence[] = sequence
+        else
+            # update both the view and model
+            selected_sequence_name = sequence_names[selected_sequence_index]
+        end
+        # update the view
+        notify(sequence_names)
+    end
 end
 
 function dropdown(key, options, label; margin=true, class=nothing)
@@ -283,7 +351,7 @@ function muscle_view(bonito_url=""; channel=":channel")
                       label="Upload motor program(s)", autoupload=true, hideuploadbtn=true,
                       class="no-file-listing", id="uploader", url="/____/upload/$channel")),
         item(btn("Download motor program", @click(:export_sequence_click), color="primary",
-                 loading=:export_sequence_click,
+                 loading=:export_sequence_click, disable=:no_sequences_yet,
                  [tooltip("Download the current motor program as a JSON file")])),
         dropdown(:selected_sequence_name, :sequence_names, "Motor program",
                  class="sequence-list"),
@@ -307,7 +375,8 @@ function muscle_view(bonito_url=""; channel=":channel")
             textfield("Start time", :start_time, type="number", step="any",
                       disable=:editmode_disable),
         ]),
-        #item(btn("Delete motor program", @click(:delete_sequence_click), color="negative")),
+        item(btn("Delete motor program", @click(:delete_sequence_click), color="negative",
+                 disable=:no_sequences_yet)),
     ])
 
     Html.div(style=col, [
diff --git a/src/apps/muscles/muscleapp.css b/src/apps/muscles/muscleapp.css
index 436d5d89a569da0f7a841b4d2c84ecc3919ff238..5239c123f715bc275a08e199408dc65b5eedb250 100644
--- a/src/apps/muscles/muscleapp.css
+++ b/src/apps/muscles/muscleapp.css
@@ -12,3 +12,6 @@
   margin-left: 1rem;
 }
 
+.q-notification__message {
+  font-size: 120%;
+}