diff --git a/Manifest.toml b/Manifest.toml
index dd3ed0969c0d4f24924c10e1f5bb57fdf3a4be40..81d6ac55064841a32c21e2c009a9a0639654af3f 100644
--- a/Manifest.toml
+++ b/Manifest.toml
@@ -1,6 +1,6 @@
 # This file is machine-generated - editing it directly is not advised
 
-julia_version = "1.7.1"
+julia_version = "1.8.3"
 manifest_format = "2.0"
 project_hash = "2a3d26785ece5a51ad6d73d93147b49dbedd4551"
 
@@ -29,6 +29,7 @@ version = "0.4.1"
 
 [[deps.ArgTools]]
 uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"
+version = "1.1.1"
 
 [[deps.Artifacts]]
 uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"
@@ -144,6 +145,7 @@ version = "4.3.0"
 [[deps.CompilerSupportLibraries_jll]]
 deps = ["Artifacts", "Libdl"]
 uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"
+version = "0.5.2+0"
 
 [[deps.Contour]]
 git-tree-sha1 = "d05d9e7b7aedff4e5b51a029dced05cfb6125781"
@@ -214,8 +216,9 @@ uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
 version = "0.9.2"
 
 [[deps.Downloads]]
-deps = ["ArgTools", "LibCURL", "NetworkOptions"]
+deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"]
 uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
+version = "1.6.0"
 
 [[deps.DualNumbers]]
 deps = ["Calculus", "NaNMath", "SpecialFunctions"]
@@ -270,6 +273,9 @@ git-tree-sha1 = "7be5f99f7d15578798f338f5433b6c432ea8037b"
 uuid = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
 version = "1.16.0"
 
+[[deps.FileWatching]]
+uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee"
+
 [[deps.FillArrays]]
 deps = ["LinearAlgebra", "Random", "SparseArrays", "Statistics"]
 git-tree-sha1 = "802bfc139833d2ba893dd9e62ba1767c88d708ae"
@@ -578,10 +584,12 @@ version = "0.3.1"
 [[deps.LibCURL]]
 deps = ["LibCURL_jll", "MozillaCACerts_jll"]
 uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21"
+version = "0.6.3"
 
 [[deps.LibCURL_jll]]
 deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"]
 uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0"
+version = "7.84.0+0"
 
 [[deps.LibGit2]]
 deps = ["Base64", "NetworkOptions", "Printf", "SHA"]
@@ -590,6 +598,7 @@ uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"
 [[deps.LibSSH2_jll]]
 deps = ["Artifacts", "Libdl", "MbedTLS_jll"]
 uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8"
+version = "1.10.2+0"
 
 [[deps.Libdl]]
 uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
@@ -703,6 +712,7 @@ version = "1.1.7"
 [[deps.MbedTLS_jll]]
 deps = ["Artifacts", "Libdl"]
 uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1"
+version = "2.28.0+0"
 
 [[deps.Meshes]]
 deps = ["Bessels", "CircularArrays", "Distances", "IterTools", "IteratorInterfaceExtensions", "LinearAlgebra", "NearestNeighbors", "Random", "ReferenceFrameRotations", "SparseArrays", "StaticArrays", "StatsBase", "TableTraits", "Tables", "TransformsBase"]
@@ -727,6 +737,7 @@ version = "0.3.3"
 
 [[deps.MozillaCACerts_jll]]
 uuid = "14a3606d-f60d-562e-9121-12d972cd8159"
+version = "2022.2.1"
 
 [[deps.MsgPack]]
 deps = ["Serialization"]
@@ -754,6 +765,7 @@ version = "1.0.2"
 
 [[deps.NetworkOptions]]
 uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"
+version = "1.2.0"
 
 [[deps.Observables]]
 git-tree-sha1 = "5a9ea4b9430d511980c01e9f7173739595bbd335"
@@ -762,11 +774,11 @@ version = "0.5.2"
 
 [[deps.ObservationPolicies]]
 deps = ["Observables"]
-git-tree-sha1 = "d5dfd602da5f4e074e19506e794d3973e3ce3d7c"
+git-tree-sha1 = "5bc07fca67b56bc6031965204753cab578e5ff23"
 repo-rev = "main"
 repo-url = "https://gitlab.com/dbc-nyx/ObservationPolicies.jl"
 uuid = "6317928a-6b1a-42e8-b853-b8e2fc3e9ca3"
-version = "0.2.2"
+version = "0.2.3"
 
 [[deps.OffsetArrays]]
 deps = ["Adapt"]
@@ -783,6 +795,7 @@ version = "1.3.5+1"
 [[deps.OpenBLAS_jll]]
 deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"]
 uuid = "4536629a-c528-5b80-bd46-f80d51c5b363"
+version = "0.3.20+0"
 
 [[deps.OpenEXR]]
 deps = ["Colors", "FileIO", "OpenEXR_jll"]
@@ -799,6 +812,7 @@ version = "3.1.1+0"
 [[deps.OpenLibm_jll]]
 deps = ["Artifacts", "Libdl"]
 uuid = "05823500-19ac-5b8b-9628-191a04bc5112"
+version = "0.8.1+0"
 
 [[deps.OpenSSL_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
@@ -826,6 +840,7 @@ version = "1.4.1"
 [[deps.PCRE2_jll]]
 deps = ["Artifacts", "Libdl"]
 uuid = "efcefdf7-47ab-520b-bdef-62a2eaa19f15"
+version = "10.40.0+0"
 
 [[deps.PDMats]]
 deps = ["LinearAlgebra", "SparseArrays", "SuiteSparse"]
@@ -866,6 +881,7 @@ version = "0.40.1+0"
 [[deps.Pkg]]
 deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"]
 uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
+version = "1.8.0"
 
 [[deps.PkgVersion]]
 deps = ["Pkg"]
@@ -971,6 +987,7 @@ version = "0.3.0+0"
 
 [[deps.SHA]]
 uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"
+version = "0.7.0"
 
 [[deps.SIMD]]
 git-tree-sha1 = "bc12e315740f3a36a6db85fa2c0212a848bd239e"
@@ -1102,6 +1119,7 @@ uuid = "4607b0f0-06f3-5cda-b6b1-a6196a1729e9"
 [[deps.TOML]]
 deps = ["Dates"]
 uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
+version = "1.0.0"
 
 [[deps.TableTraits]]
 deps = ["IteratorInterfaceExtensions"]
@@ -1118,6 +1136,7 @@ version = "1.10.0"
 [[deps.Tar]]
 deps = ["ArgTools", "SHA"]
 uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"
+version = "1.10.1"
 
 [[deps.TensorCore]]
 deps = ["LinearAlgebra"]
@@ -1260,6 +1279,7 @@ version = "1.4.0+3"
 [[deps.Zlib_jll]]
 deps = ["Libdl"]
 uuid = "83775a58-1f1d-513f-b197-d71354ab007a"
+version = "1.2.12+3"
 
 [[deps.Zstd_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
@@ -1288,6 +1308,7 @@ version = "0.15.1+0"
 [[deps.libblastrampoline_jll]]
 deps = ["Artifacts", "Libdl", "OpenBLAS_jll"]
 uuid = "8e850b90-86db-534c-a0d3-1478176c7d93"
+version = "5.1.1+0"
 
 [[deps.libfdk_aac_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
@@ -1316,10 +1337,12 @@ version = "1.3.7+1"
 [[deps.nghttp2_jll]]
 deps = ["Artifacts", "Libdl"]
 uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"
+version = "1.48.0+0"
 
 [[deps.p7zip_jll]]
 deps = ["Artifacts", "Libdl"]
 uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"
+version = "17.4.0+0"
 
 [[deps.x264_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
diff --git a/README.md b/README.md
index 44ba00e72ac3ebc568a4e8a8917fff23611a37ef..6ddae48b74b842ad5069645af6c36cb6aa51228b 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@ If you have Docker or Singularity/Apptainer installed, take a look at the [dedic
 
 ## Installing
 
-To natively run `LarvaTagger.jl`, you will need [`julia>=1.6`](https://julialang.org/downloads/).
+To natively run `LarvaTagger.jl`, you will need [`julia>=1.7`](https://julialang.org/downloads/).
 
 If you are not familiar with installing Julia, you may appreciate installation helpers such as [Juliaup](https://github.com/JuliaLang/juliaup).
 
@@ -31,6 +31,15 @@ julia --project=. -e 'using Pkg; Pkg.instantiate()'
 Calling `Pkg.instantiate` in a copy of the project is preferred over using `Pkg.add`,
 because `LarvaTagger.jl` depends on several unregistered packages.
 
+### With Julia 1.6
+
+`LarvaTagger.jl` is known to work also with `julia==1.6`.
+However, with this version, `Pkg.instantiate` does not take into account version constraints.
+In particular, `Makie` must be manually downgraded to version `0.17.13`:
+```
+julia --project=. -e 'using Pkg; Pkg.add(name="Makie", version="0.17.13")'
+```
+
 ### Alternative procedure
 
 Users who would prefer not to clone the repository or implicitly use the shipped `Manifest.toml` file
@@ -138,4 +147,4 @@ See the following [comment](https://gitlab.pasteur.fr/nyx/larvatagger.jl/-/issue
 
 ## Developer documentation
 
-See the [release announcements](https://gitlab.pasteur.fr/nyx/larvatagger.jl/-/issues/50)  and the [project structure](https://gitlab.pasteur.fr/nyx/larvatagger.jl/-/blob/dev-doc/doc/develop.md).
+See the [release announcements](https://gitlab.pasteur.fr/nyx/larvatagger.jl/-/issues/50), [change logs](https://gitlab.pasteur.fr/nyx/larvatagger.jl/-/releases) and [project structure](https://gitlab.pasteur.fr/nyx/larvatagger.jl/-/blob/dev-doc/doc/develop.md).
diff --git a/scripts/larvatagger.jl b/scripts/larvatagger.jl
index ab1dc5f15e1eac39cc913f4df43dd2c4400a8635..73252ed77256088decf08a721aedb87df72d56d5 100755
--- a/scripts/larvatagger.jl
+++ b/scripts/larvatagger.jl
@@ -5,7 +5,8 @@ FLAGS=
 if [ "$1" = "--sysimage" -o "$1" = "-J" ]; then FLAGS="--sysimage $2 "; shift 2; fi
 if [ "${1:0:2}" = "-J" ]; then FLAGS="$1"; shift; fi
 if [ "$1" = "open" ]; then FLAGS="$FLAGS -iq "; fi
-exec julia --project="$PROJECT_DIR" --color=yes --startup-file=no $FLAGS\
+if [ -z "$JULIA" ]; then JULIA=julia; fi
+exec $JULIA --project="$PROJECT_DIR" --color=yes --startup-file=no $FLAGS\
     "${BASH_SOURCE[0]}" "$@"
 =#
 
diff --git a/src/controllers.jl b/src/controllers.jl
index e309044e1bdd9b3d4ebc6ba745944dcebb60cbfa..aec922a2c43739ae752095f00430ffb1aad8fdb7 100644
--- a/src/controllers.jl
+++ b/src/controllers.jl
@@ -127,7 +127,7 @@ function renamefailed!(controller,
     tagname = current.name[]
     @info "Renaming tag \"$(tagname)\" failed"
     tagevents(controller).renamefailed[] = tagname => failure
-    Observables.notify(gettags(controller))
+    notify(gettags(controller))
 end
 
 function activatetag!(controller, tag::ObservableTag)
@@ -385,7 +385,7 @@ function setevents!(controller, tagvalidator::ValidatorBundle{ObservableTag})
        return Validate
     end
     onany(tag.active, tag.name, tag.color) do _, _, _
-        Observables.notify(gettags(controller))
+        notify(gettags(controller))
     end
 end
 
diff --git a/src/players.jl b/src/players.jl
index 497483db11649ee97050e13b3bf3ae9f92e766e1..b6c33556bd9208218c06cb3f7863eb0eea779114 100644
--- a/src/players.jl
+++ b/src/players.jl
@@ -208,7 +208,7 @@ function timecontroller(times::Vector{Float64}; speed=1.0)
 end
 
 function slave(master::TimeController, policy::ObservationPolicy=IndependentObservables)
-    slave_timestep = newobservable(policy, ObservationPolicies.output(master.timestep))
+    slave_timestep = newobservable(policy, master.timestep)
     slave_time = map(slave_timestep) do step
         master.times[step]
     end
diff --git a/src/plots.jl b/src/plots.jl
index 89fb86297301ca04d3f2c3cb52e779beb360d85e..d4073b660cd74e9b53858010dda4f501c216a5f8 100644
--- a/src/plots.jl
+++ b/src/plots.jl
@@ -86,7 +86,7 @@ function StatefulLarva(larva::LarvaModel,
     shape_color = Observable(html_color(color))
     visibility = Observable(false)
 
-    onoutput(timestep, tag_lut, larva.usertags) do timestep, tag_lut, _
+    onany(timestep, tag_lut, larva.usertags) do timestep, tag_lut, _
         parent_visible[] || return
         if first_timestep<=timestep && timestep<=last_timestep
             step = timestep - first_timestep + 1
@@ -182,11 +182,11 @@ function SingleLarvaView(larvae::Vector{LarvaModel}, controller; editabletags::B
         end
     end
 
-    onoutput(model,
-             timestep,
-             tags,
-             usertags,
-            ) do larva, timestep, tag_lut, usertags
+    onany(model,
+          timestep,
+          tags,
+          usertags,
+         ) do larva, timestep, tag_lut, _
         first_timestep = larva.alignedsteps[1]
         last_timestep = larva.alignedsteps[end]
         if (first_timestep <= timestep <= last_timestep &&
@@ -297,9 +297,9 @@ function assign_tag_to_segment!(larvaview, firststep)
         end
     end
     push!(transactions(controller), transaction)
-    Observables.notify(larva.usertags)
-    Observables.notify(larvaview.usertags)
-    Observables.notify(gettimestep(controller)) # full refresh
+    notify(larva.usertags)
+    notify(larvaview.usertags)
+    notify(gettimestep(controller)) # full refresh
     flag_active_larva_as_edited(controller)
 end
 
diff --git a/src/viewer.jl b/src/viewer.jl
index ca302862b354447ddad0cc9ee8510d7c8c164c46..6ea1a4c93e0779f2d77fc86d298de3e60cec76c2 100644
--- a/src/viewer.jl
+++ b/src/viewer.jl
@@ -71,9 +71,12 @@ function larvaviewer(controller;
     controller[:larva] = larva = LarvaController(controller, model, tag_lut, player)
 
     n_simultaneous_larvae = simultaneouslarvae(model)
-    if 40 < n_simultaneous_larvae
+    if 100 < n_simultaneous_larvae
         @info "Refresh rate throttled to 1Hz" n_simultaneous_larvae
-        delayed_controller = slave(larva, PeriodicObservation(1.0))
+        delayed_controller = slave(larva, Cooldown(1.0))
+    elseif 50 < n_simultaneous_larvae
+        @info "Refresh rate throttled to 2Hz" n_simultaneous_larvae
+        delayed_controller = slave(larva, Cooldown(0.5))
     else
         delayed_controller = larva
     end
diff --git a/src/wgl.jl b/src/wgl.jl
index 06569658e50b3df634cf76381a05052eb6715a5b..c842bc317bec749db040ecabaab75f79c99180af 100644
--- a/src/wgl.jl
+++ b/src/wgl.jl
@@ -607,7 +607,7 @@ function JSServe.jsrender(session::Session, ti::IndividualTagView)
                          """,
                          class="tag-active")
     setcallbacks!(ti.controller, tag, checkbox, label, colorpicker)
-    Observables.notify(active)
+    notify(active)
     r(session,
       DOM.tr(DOM.td(checkbox),
              DOM.td(label),
@@ -691,7 +691,7 @@ function addtag!(parent::TagController)
     childview = tagview(parent,
                         newview!(tag))
     addtag!(view, childview)
-    Observables.notify(taglut)
+    notify(taglut)
 end
 
 function addtag!(view::TagFilter, newtag::IndividualTagView)
@@ -742,14 +742,14 @@ function TagSelector(controller::TagController,
             push!(registered_observables, newobs)
         end
         selectedtags[] = collect(zip(names, registered_observables[1:length(names)]))
-        Observables.notify(refresh_view)
+        notify(refresh_view)
     end
     activelarva = getactivelarva(controller)
-    onoutput(gettimestep(controller),
-             refresh_view,
-             tag_lut,
-             multitag,
-            ) do timestep, _, _, multitag
+    onany(gettimestep(controller),
+          refresh_view,
+          tag_lut,
+          multitag,
+         ) do timestep, _, _, multitag
         id = activelarva[]
         isnothing(id) && return
         if isempty(selectedtags[]) && any(tag -> tag.active[], tag_lut[])
@@ -759,7 +759,7 @@ function TagSelector(controller::TagController,
             # Notifying `tag_lut` actually triggers a bug if
             # empty, e.g. on loading Chore files.
             # This also triggers another bug if all tags are unchecked.
-            Observables.notify(tag_lut)
+            notify(tag_lut)
             return
         end
         if multitag
@@ -773,8 +773,8 @@ function TagSelector(controller::TagController,
     end
     on(filterevents(controller).discard_larva_edits) do id
         activelarva[] == id || return
-        #Observables.notify(refresh_view) # applies only to the tag selector
-        Observables.notify(gettimestep(controller)) # full refresh
+        #notify(refresh_view) # applies only to the tag selector
+        notify(gettimestep(controller)) # full refresh
     end
     return ctrl
 end
@@ -860,7 +860,7 @@ function toggletag!(controller, tagname, selected, selectedtags, selectable)
         deletetag!(tags, tag)
     end
     push!(transactions(controller), transaction)
-    Observables.notify(larva.usertags)
+    notify(larva.usertags)
 end
 
 function to_dom(selectedtags, selectable)
@@ -1035,7 +1035,7 @@ function JSServe.jsrender(session::Session, fm::FileMenu)
                   select;
                   id="filemenu-panel",
                   class="flex flex-col panel")
-    Observables.notify(wdcontent) # populate the select element
+    notify(wdcontent) # populate the select element
     r(session, dom)
 end