How to evaluate page header at the start of the page?

I’ve been trying to create a template for homework (math problems in particular, but could easily be expanded) which has an “exercise” function, and which shows the current (last as of the current page) exercise in the page’s header. The way I chose for doing this is to create an “exercises” state (which I’m not a fan of, as it exists outside of the main template function), update it with every call of the “exercise” function, and display the “last” one in the header.

The problem is, I’ve found out the header’s content seems to “evaluate” before any of the page itself does. In contrast, the footer seems to update after. So the template works as intended - always one page behind.

Consider the following example:

#let arr = state("arr", ())

#set page(
  header: [
    arr:
    #context arr.get()
    #line(length: 100%)
  ],
  footer: [
    arr:
    #context arr.get()
    #line(length: 100%)
  ]
)

#arr.update("Hey!")

Which renders as

while I want the header to look like the footer.

Anyone has any idea on how to solve this? Or alternatively, a better way to do what I want? Initially I tried using labels, but working with content is a nightmare. I do not know how the rendering in Typst actually works, so this problem may be deeper than it seems.

I would have a look at how hydra – Typst Universe is dealing with this (with anchors I believe). It may even be working out of the box for this purpose.

Edit: This is a start, adapted from How do I count elements on a page so the result isn't always zero in the header? - #7 by janekfleper


#let exercise(body, label: none) = [
  #show figure: set align(left)
  #set figure(numbering: "1.")
  #show figure.caption: it => context { 
    strong(it.counter.display()) + [ ]
    it.supplement + [: ]
    linebreak()
    it.body
  }
  #figure(kind: "exercise", supplement: "Ex", [], caption: body)
  #label
]

#let disp-exercises() = {
  let locations = query(figure.where(kind: "exercise"))
  locations.filter(loc => loc.location().page() == here().page()).first()
}

#set page(
  header: context disp-exercises(),
  footer: context disp-exercises(),
)

#exercise(lorem(2))
#exercise(lorem(2))
#exercise(lorem(2))
#exercise(lorem(2))

#pagebreak()
#exercise(lorem(2))
#exercise(lorem(2))
#exercise(lorem(2))