How to keep exercise and solution together in source, but render them separately?

You are using all-problems.get() and all-problems.update(current-problems) in the same context, which always(?) leads to the converge issue, provided this happens 5 times. And it does here, if there are 5+ problems:

#let show-solutions = true
#let all-problems = state("all-problems", ())

#let problem(title: "Example title", statement: [], solution: []) = context {
  let current-problems = all-problems.get()
  current-problems.push((title: title, solution: solution, location: here()))
  all-problems.update(current-problems)
  [
    == #title #label(repr(title))
    #statement
  ]
}

= Problem list

#for n in range(1, 6) {
  problem(title: [Problem #n])
}

#if show-solutions [
  #pagebreak()
  = Solutions
  #context {
    for p in all-problems.get() [
      == #p.title #link(p.location, text(blue)[(go to problem)])
      #p.solution
    ]
  }
]

To fix this, you should pass closure to the update function instead of overriding the value. Also the title can’t be content, because of the label, which is “fixed” with repr() content wrapper.

#let show-solutions = true
#let all-problems = state("all-problems", ())

#let problem(title: "Example title", statement: [], solution: []) = context {
  let problem = (title: title, solution: solution, location: here())
  all-problems.update(problems => problems + (problem,))
  [
    == #title #label(repr(title))
    #statement
  ]
}

= Problem list

#for n in range(1, 6) {
  problem(title: [Problem #n])
}

#if show-solutions [
  #pagebreak()
  = Solutions
  #context {
    for p in all-problems.get() [
      == #p.title #link(p.location, text(blue)[(go to problem)])
      #p.solution
    ]
  }
]
4 Likes