Show Rules for Deeply Updating Bodies of Various Elements

Hello Typsters,

I often have to deal with documents where every heading, paragraph, list item, enum item, figure caption, really every chunk of text, has to be labeled with some kind of marker. For some element types this is totally straightforward, for some I’m left wondering if there’s a better way, and for others I have no idea how to achieve this. For headings and semantic paragraphs this is pretty easy. For this:

#let labelThings(lab : "D", x) = {
  show par: it => block([*(#lab)* ] + it.body)
  show heading : it => block([*(#lab)* ] + it.body)
  x
}

#labelThings[
  = Heading1
  #lorem(10)
]

I get something like:

(I’m only allowed to attach two images as a new user, so take my word for the fact that this works as expected)

For figures, I have to “rebuild” the default caption rendering in order to get what I want, but it works. For this:

#let labelThings(lab : "D", x) = {
  show par: it => block([*(#lab)* ] + it.body)
  show heading : it => block([*(#lab)* ] + it.body)
  show figure.caption: it => block([*(#lab)* ] + it.supplement + [ ] + it.numbering + [: ] + it.body)
  x
}

#labelThings[
  = Heading1
  #lorem(10)
  #figure(caption: [Some Figure],
    block(box(stroke:1pt, inset: 1em, [Some Figure])))
]

I get something like:

The fact that I can only update the entire figure caption with a show rule, rather than strictly the caption contents feels sort of like I’m holding it wrong, but I haven’t worked out a different way to do it.

Lists and enums are where things get strange. If I leave these show rules as-is and add some list and enum elements like so:

#let labelThings(lab : "D", x) = {
  show par: it => block([*(#lab)* ] + it.body)
  show heading : it => block([*(#lab)* ] + it.body)
  show figure.caption: it => block([*(#lab)* ] + it.supplement + [ ] + it.numbering + [: ] + it.body)
  x
}

#labelThings[
  = Heading1
  #lorem(10)
  #figure(caption: [Some Figure],
    block(box(stroke:1pt, inset: 1em, [Some Figure])))
  - First item
  - Second item
    - Subsecond item
  - Third item
    - Subthird item
      - Subsubthird item
  + First enum
  + Second enum
    + Subsecond enum
  + Third enum
    + Subthird enum
      + Subsubthird enum
]

I get something unexpected:

By elimination it’s the par rule that’s causing this. It seems that list and enum elements that have children become paragraphs, but not those without children. I’ve played around with show rules on list and list.item, but similar to figure captions I suspect that the only way I’d be able to render what I want is to make a show rule that re-implements the default rendering.

So, my questions are:

  1. Is there a better way to recursively add something to every text chunk in a body of content like this?
  2. Is there a better way to reach into a figure caption’s body or list/enum item’s body and modify it?
  3. Why do list/enum items become paragraphs when they have children? Is there a good reason for this or is it a bug?

Re-reading the manual sheds some light on 3. The docs for the par function say:

… by adding a parbreak after some content in a container, you can force it to become a paragraph even if it’s just one word. This is, for example, what non-tight lists do to force their items to become paragraphs.

This doesn’t make much sense to me in the context of semantic paragraphs. Why should some list items be semantic paragraphs while other’s shouldn’t be, and why should list tightness have to do with semantic paragraphs? I suppose this made perfect sense before semantic paragraphs were a thing? An example of this:

#show par: it => block(box(stroke: 1pt + blue, inset: 2pt, it))

- Tight list first item, this is not a paragraph.
- Tight list second item, this is a paragraph.
  - Tight list subsecond item, this is a paragraph.
    - Tight list subsubsecond item, this is not a paragraph.
- Tight list third item, this is not a paragraph.

= Break the list

- Loose list first item, this is a paragraph.

  - Loose list second item, this is a paragraph.

    - Loose list subsecond item. This is a paragraph.

      - Loose list subsubsecond item. This is not a paragraph.

- Loose list third item, this is a paragraph.

Gives: