Notify not working when triggered by a button

My code for updating a heatmap is not working when triggered by a button.

I have tried to boil things down to a minimum working example. The expected behaviour is that at each evaluation of the cost function, one pixel of the heatmap is updated. In the example this works when run from code, but not when the update is induced by pressing a button.

Any ideas on what might be going wrong?

module ObservableHeatmaps

export ObservableHeatmap, update_observable_heatmap!

using Base.Iterators
using Makie


struct ObservableHeatmap{H}
    xs::Observable{Vector{Float64}}
    ys::Observable{Vector{Float64}}
    zs::Observable{Matrix{Float64}}
    ax::Axis
    hm::H
    function ObservableHeatmap(ax)
        xs, ys, zs = Observable(zeros(1)), Observable(zeros(1)),  Observable(zeros(1, 1))
        hm = heatmap!(ax, xs, ys, zs)    
        new{typeof(hm)}(xs, ys, zs, ax, hm)
    end
end

function scheduled_n_tasks(n_schedule_max, tasks, ondone)
    unscheduled_task_idxs = collect(eachindex(tasks))
    scheduled_task_idxs = empty(unscheduled_task_idxs)
    while length(unscheduled_task_idxs) + length(scheduled_task_idxs) > 0
        while length(scheduled_task_idxs) < n_schedule_max && length(unscheduled_task_idxs) > 0
            i = popfirst!(unscheduled_task_idxs)
            task = tasks[i]
            schedule(task)
            # Threads.@spawn :default () -> task.code()
            push!(scheduled_task_idxs, i)
        end
        scheduled_task_idxs = filter(scheduled_task_idxs) do i
            task = tasks[i]
            # If task has just finished
            isdone = istaskdone(task)
            if isdone
                ondone(fetch(task)...)
            end
            ~isdone
        end
        yield()
    end
end

function update_observable_heatmap!(
        hm::ObservableHeatmap,
        cost_func::Function,
        new_xs::AbstractVector{Float64},
        new_ys::AbstractVector{Float64})
    (;xs, ys, zs, ax) = hm
    
    # Reset heatmap to zero
    xs[], ys[], zs[] = new_xs, new_ys, zeros(length(new_xs), length(new_ys))
    notify.( (xs, ys, zs) )
    notify(ax.limits)
    sleep(0.1)

    # Create task for each pixel
    xy_iterator = product(enumerate(xs[]), enumerate(ys[]))
    tasks = Vector{Task}()
    for ((i, x), (j, y)) in xy_iterator
        task = Task() do
            C::Float64 = cost_func(x, y)
            i, j, C
        end
        push!(tasks, task)
    end

    # Callback to update plot (in main thread) when done
    function ondone(i, j, C)
        zs[][i, j] = C
        notify(zs)
        yield()
    end
    scheduled_n_tasks(Threads.nthreads()-1, tasks, ondone)
    nothing
end

end

using GLMakie
using .ObservableHeatmaps

fig = Figure()
display(fig)
ax = Axis(fig[1,1])
hmplot = ObservableHeatmap(ax)
# cb = Colorbar(fig[1, 2], hmplot.hm)

function expensive_cost_function(x, y)
    val = sin(x) + cos(y)
    for i = 1:1000
        val += val/i
    end
    return val
end

xs, ys = LinRange(0., 10., 50), LinRange(0., 10., 50)
update_observable_heatmap!(hmplot, expensive_cost_function, xs, ys)

button = Button(fig[2, 1], label="Recompute", tellwidth=false)
on(button.clicks) do n
    println("Recomputing...")
    update_observable_heatmap!(hmplot, expensive_cost_function, xs, ys)
    println("Done.")
end