Why box("") ≠ box[] when bottom edge is set to descender?

I want to wrap the Han character 囚 with a green square, and draw blue dots on the top and bottom edge of the square.

As shown below, I’ve achieved that with box("" + place(dx: …, dy: …, circle(…))).

set text(5em, font: "Noto Serif CJK SC", lang: "zh")
set text(bottom-edge: "descender", top-edge: "ascender")

box(stroke: green)[囚]

let r = 0.08em
set circle(fill: teal.transparentize(50%))
box("" + place(dx: -0.5em - r, dy: -r, circle(radius: r)))
box("" + place(dx: -0.5em - r, dy: -1em - r, circle(radius: r)))

Note that joining place(circle) with "" is necessary. If I delete "" + from the above code, then the dots will be vertically shifted:

I don’t quite understand why "" makes difference. Is it a bug of Typst?
Any help is appreciated!

More experiments

From top to bottom:

set text(bottom-edge: "baseline", top-edge: "cap-height")
set text(bottom-edge: "descender", top-edge: "ascender")

From left to right:

[囚]
box[囚]
box(width: 1em, height: 1em, "")
box(width: 1em, height: 1em)
box(width: 1em, height: 1em)[]

box[囚]
box(place(…, circle(…)))

box[囚]
box("" + place(…, circle(…)))
Full code
#set page(height: auto, width: auto)

#let test = {
  set text(5em, font: "Noto Serif CJK SC", lang: "zh")
  set box(stroke: green)
  set circle(fill: teal.transparentize(50%))

  [囚]
  box[囚]
  box(width: 1em, height: 1em, "")
  box(width: 1em, height: 1em)
  box(width: 1em, height: 1em)[]

  let r = 0.08em
  box[囚]
  box(place(dx: -0.5em - r, dy: -r, circle(radius: r)))
  box(place(dx: -0.5em - r, dy: -1em - r, circle(radius: r)))
  box[囚]
  box("" + place(dx: -0.5em - r, dy: -r, circle(radius: r)))
  box("" + place(dx: -0.5em - r, dy: -1em - r, circle(radius: r)))
}

= baseline to cap-height (default)
#set text(bottom-edge: "baseline", top-edge: "cap-height")
#test

= descender to ascender
#set text(bottom-edge: "descender", top-edge: "ascender")
#test

(Tested with Typst v0.15.1)

An empty string will go through inline layout and may also pick up font metrics while empty content is literally nothing.

I see… So height: 1em has somewhat complicated the issue.

Boxes take the size of their contents by default but can also be sized explicitly.
Box - Typst Documentation

1. The default height of a box

If I remove height: 1em from the code in More experiments (or equivalently, changing them to height: auto), I get this:

As shown above, the default height of box("") is the distance between top and bottom edges, while the default height of box()/box[] is zero. This behaviour is easy to understand.

2. Specify an explicit height

According to the tests below, when an explicit height is given, box()/box[] grows from the baseline upwards, and box("") grows from the top edge downwards.
(Is this documented somewhere?)

baseline to cap-height:

descender to ascender:

From left to right: 囚, height: auto, height: 0em, height: 0.2em, height: 0.4em, …, height: 1em, type of box.

Test code
#set page(height: auto, width: auto)

#{
  set text(5em, font: "Noto Serif CJK SC", lang: "zh")
  set box(width: 0.2em, stroke: green)

  set raw(lang: "typc")
  show raw: set text(0.4em)

  [囚]
  box(height: auto)
  [ ]
  box(height: 0em)
  box(height: 0.2em)
  box(height: 0.4em)
  box(height: 0.6em)
  box(height: 0.8em)
  box(height: 1em)
  ` box(height: …)`

  parbreak()

  [囚]
  box(height: auto, "")
  [ ]
  box(height: 0em, "")
  box(height: 0.2em, "")
  box(height: 0.4em, "")
  box(height: 0.6em, "")
  box(height: 0.8em, "")
  box(height: 1em, "")
  ` box(height: …, "")`
}

3. place

When float is false and no vertical alignment is specified, the content is placed at the current position on the vertical axis.

Place - Typst Documentation

What is the current position on the vertical axis?
According to the red dots shown below, that for box()/box[] is the top edge of the box and depends on box.height, but that for box("") is text.bottom-edge and is independent to box.height.

(Again, it seems undocumented?)

baseline to cap-height:

descender to ascender:

Test code
#set page(height: auto, width: auto)

#{
  set text(5em, font: "Noto Serif CJK SC", lang: "zh")
  set box(width: 0.2em, stroke: green)

  set raw(lang: "typc")
  show raw: set text(0.4em)

  let r = 0.04em
  let place-dot = place(dy: -r, dx: -r, circle(radius: r, fill: red))

  [囚]
  box(height: auto, place-dot)
  [ ]
  box(height: 0em, place-dot)
  box(height: 0.2em, place-dot)
  box(height: 0.4em, place-dot)
  box(height: 0.6em, place-dot)
  box(height: 0.8em, place-dot)
  box(height: 1em, place-dot)
  ` box(height: …, place(circle(…)))`

  parbreak()

  [囚]
  box(height: auto, "" + place-dot)
  [ ]
  box(height: 0em, "" + place-dot)
  box(height: 0.2em, "" + place-dot)
  box(height: 0.4em, "" + place-dot)
  box(height: 0.6em, "" + place-dot)
  box(height: 0.8em, "" + place-dot)
  box(height: 1em, "" + place-dot)
  ` box(height: …, "" + place(circle(…)))`
}

In my original case, box(height: auto, …) is used. Without "", the box is a zero-height dot lying on the baseline, so place is also aligned to the baseline. With "", the box spans from text.bottom-edge to text.top-edge (in CJK context, its height is 1 em), and place is aligned to the bottom edge of the box, i.e. text.bottom-edge.