fix: raise citation markers and stop adjacent ones merging

The prompt asks for [1][2] when a sentence rests on two sources, and raised
digits carry no separator of their own, so the pair was drawn as 12: one
number, with nothing to say where the first citation stopped and the second
began. Reported from a real answer, where two sources came out as one glom.

Markers are now matched as a run, so [1][2] and [1, 2] both arrive as raised
digits separated by a comma, and a run containing a number we were never shown
is left exactly as written. A newline still ends a run, so two lines of
citations never become one sentence.

The marker itself is raised too: [1] is drawn as a superscript link, which is
what a citation mark is, while the entry under the answer keeps the plain
number at baseline where a list is easier to scan.
This commit is contained in:
2026-10-07 16:19:42 +08:00
parent fcc6d26c9d
commit eff0d4ea05
7 changed files with 103 additions and 25 deletions
+4
View File
@@ -300,6 +300,10 @@ The whole pass is plain TypeScript with no Vicinae imports, so it unit tests wit
Footnotes deserve a note of their own, because the window cannot draw them. Vicinae parses markdown with `CMARK_OPT_DEFAULT` and attaches only the table and strikethrough extensions, so a `[^1]` arrives as literal text. The citation pass therefore does the whole job: it links the marker, drops a footnote definition that would only repeat an entry the tools already registered, and builds the list from those entries, which is also what stops the list from being something a model can quietly invent.
In a sentence the marker is raised, so a claim resting on a search result reads as `rain is likely¹` rather than with the brackets at full size. It is a Unicode superscript digit rather than a `<sup>` tag, because a link label is plain text that nothing downstream can break, and a digit is raised by design in every font. The entry under the answer keeps the plain number, `[1] Rain in Cebu`, where being able to scan a list matters more than the convention.
Markers the model wrote touching are separated as they are drawn, because `¹²` reads as twelve, or as twenty one, and either way as one number. The prompt asks for exactly that touching shape, so `[1][2]` and the same numbers written apart both arrive as `¹, ²`.
Numbers are handed out where the tools run, once per turn, and printed in the tool result the model reads. So `[3]` means one page for the model and for the reader, three searches in one turn do not each start again at `1`, and a page found by a search and read again later keeps the number it already had. A marker stays as written unless every number in it is a source with a destination this window should open: half a link reads as a citation that works, and a `javascript:` address out of a search result is a real vulnerability rather than a rendering quirk.
The list shows the sources the answer cited, falling back to everything the turn consulted when it cited nothing, since that is the one case where the list is the only evidence of what was read. It waits for a citation before appearing at all, so it never arrives in full and then shrinks.