How to customize page header with current or previous level 1 headline displayed in corner?

Hi, I’m fairly new to Typst and I am currently porting my LaTeX doc over to Typst. The styling of that document has a dynamic headline of the current headline the text belongs to, or the first headline on that page. While trying to achieve just that I have hit a few road blocks which I managed to clear, but this one has some weird behavior with the state-Type I have not been able to figure out.

The code as it is now:

#let ht-first = state("page-first-section", [])
#ht-first.
#set page(header: {
  let first-heading = context query(
    heading.where(level: 1)).find(h => h.location().page() == here().page())
// find last heading of level 1 on current page

  context ht-first.get()

  // Title top left
  set text(8pt)
  place(top + left,smallcaps[#{
    context if query(
    heading.where(level: 1)).find(h => h.location().page() == here().page()) == none {

    context ht-first.at(here())
    
    }
    else {
      ht-first.update([#heading(first-heading).body])
      ht-first.at(here())
      
    }
  }], dy: 1.2cm)
  // Numbering top right
  context place(top + right, text(10pt, numbering(here().page-numbering(), counter(page).at(here()).first())), dy: 1.25cm)
  // horizontal line underneath
  line(length: 100%)
  }
)

The goal here is to dynamically display the first level 1headline of the page, or the last level 1 headline of the previous page-/s. The current headline (as in, defined on that page) does work and will display correctly. I want to store that headline in this state:

#let ht-first = state("page-first-section", [])

For some reason however, the state will be empty by the time the next page without headline comes along. Manually overriding the state on the page using #ht-first.update("Some arbitrary text") does work for that single page, before going back to the non working state.
Ideally this would also carry over the latest level 1 headline of the previous page, if there is no headline on the current page.

I also found this StackOverflow post that has done this exact thing in the past, however, this does not seem to work anymore.

Any ideas and hints would be greatly appreciated!

Update:
After posting this I, as is usual, thought of the solution, so here is a cleaned up working example of my code:

#set page(header: context
{
  // Title top left
  set text(10pt)
  place(top + left, {
    if query(heading.where(level: 1))
    .find(h => h.location().page() == here().page()) == none {
      // Filter headers that come after the current page
      let smh = query(heading.where(level: 1)).filter(h => h.location().page() <= here().page())
      smh.last().body // last element in array is newest level 1 headline
    } else {
      let onPageHeading = query(heading.where(level: 1)).filter(h => h.location().page() == here().page())
      onPageHeading.first().body
    }
  }, dy: 1.25cm)

  // Numbering top right
  place(top + right, numbering(here().page-numbering(), counter(page).at(here()).first()), dy: 1.25cm)
  // horizontal line underneath
  line(length: 100%)
})

One question remains, however: Is there a simpler/faster/better solution?

Hey @MrConfuse, welcome to the forum! I’ve changed your question post’s title to better fit our guidelines: How to post in the Questions category

For future posts, make sure your title is a question you’d ask to a friend about Typst. :wink:

In addition, instead of editing your post and prefixing your title with “[Solved]”, please either post your solution as a reply to your post and mark it as a Solution, or wait for someone to answer and do so for their answer if it solves your problem. This allows for easy filtering of solved posts in the forum, and also displays the solution in a dedicated box under your original post. Thank you!

Someone already did this for you! Check out the hydra package:

Regarding placing the heading on the left and the page number on the right, instead of placing left and right at the same time, I recommend placing both at the same line with a filling space between them:

// Displays "Left" to the left and "Right" to the right
Left #h(1fr) Right

For more complex setups (with, say, three sections in the header instead of just two), use a multi-column grid (Grid Function – Typst Documentation).