Ah that’s because of the dedup
call which only checks the id. I’ve refactored the code a bit to avoid that, also using two metadata values per paragraph to simplify the logic:
#let get-chap-short(x) = {
let shorts = query(selector(<h1-short>).before(x.location()))
return shorts.at(-1, default: none)
}
#let short-and-id(x) = {
let short = get-chap-short(x)
return (short: short.value, id: x.value)
}
#let format-header(a, b) = {
if b == none or b == a {
return [#a.short: #a.id]
}
if a.short == b.short {
return [#a.short: #a.id -- #b.id]
}
return [#a.short: #a.id -- #b.short: #b.id]
}
#set page(
width: 100mm,
height: 100mm,
header: context {
let pars = query(selector.or(<h4-start>, <h4-end>))
.filter(x => x.location().page() == here().page())
.map(short-and-id)
.dedup()
if pars.len() == 0 {
// No paragraph starting or ending on this page
// -> check if a paragraph starts before and ends after this page
let prevs = query(selector(<h4-start>).before(here()))
let nexts = query(selector(<h4-end>).after(here()))
if prevs.len() == 0 or nexts.len() == 0 {
return none
}
let prev = short-and-id(prevs.last())
let next = short-and-id(nexts.first())
if prev != next { // should not happen
return none
}
return format-header(prev, next)
}
return format-header(pars.first(), pars.at(-1, default: none))
},
)
#show heading.where(level: 4): it => it.body + h(1em)
#let my-par(id: none, body) = par({
[#metadata([#id])<h4-start>]
if id != none {
heading(level: 4, id)
}
body
[#metadata([#id])<h4-end>]
})
#let my-h1(short: none, ..args) = {
heading(level: 1, ..args)
[#metadata(short)<h1-short>]
}
#my-h1(short: "A-Chapter")[This is Chapter A]
#my-par(id: "1", lorem(20))
#my-par(id: "2", lorem(20))
#my-h1(short: "B-Chapter")[This is Chapter B]
#my-par(id: "1", lorem(50))
#my-par(id: "2", lorem(20))
#pagebreak()
#my-h1(short: "C-Chapter")[This is Chapter C]
#my-par(id: "1", lorem(20))
#my-par(id: "2", lorem(20))
#my-par(id: "3", lorem(150))
#my-h1(short: "D-Chapter")[This is Chapter D]
#my-par(id: "11", lorem(50))
#my-par(id: "11a", lorem(20))