Follow-up from typst/typst#8501.
In typst v0.15-rc1, to make several bibliographies act as one sequence (typst's group: auto), we use one BibliographyDriver for each bibliography, and chain them by with_citation_number_offset. So numbering crosses the driver boundary - and it is the only thing that does. Everything else CSL computes is per driver, only over that driver's own entries, so nothing else can see across the group.
One MWE breaks two things at once -- @a @b, both Lee, 2020, APA:
| setup |
output |
| one driver (single bibliography) |
(Lee, 2020a; 2020b) |
| two drivers + offset (grouped) |
(Lee, 2020) (Lee, 2020) |
You can tell two differences actually:
- disambiguation, the a/b suffixes; the
find_ambiguous_sets --> year-suffix loop in finish() only sees one driver's entries.
- cite-grouping,
@a @b should collapse into one parenthetical, but the collapse happens inside one single CitationRequest. Once the cites are split into different drivers, they can not be in the same request, so you get two parentheticals.
The second one is the key point: it rules out the easier fix. If we only expose the disambiguation state and inject it, we get (Lee, 2020a) (Lee, 2020b) - disambiguated, but still two parentheticals. The collapse needs both cites in one request, that is, one driver. So a per-dimension patch can not even reproduce the expected output.
I think the boundary itself is in the wrong place. It sits between bibliographies that are actually one sequence, so everything CSL computes over the whole set falls into two groups, and both are broken. One group is never shared: disambiguation, cite-grouping, and also is_first / subsequent forms and ibid (no issue yet, but same root) - each driver just can not see the others. The other group is shared, but only by hand: numbering, passed through a consumer-side offset. This glue keeps getting more work: #409 added it, #487 extends it to sorted styles, typst/typst#8476 fixed the case that an uncited bibliography does not advance the offset, and #486 proposes to move typst's "does this style use numbers" check into hayagriva - all of them are patching the same root, just one spot at a time. The fix below clears all of them at once, both the shipped ones (#8476, #409) and the in-flight ones (#487, #486).
So we can generalize what finish() already does for one bibliography to N - let one single driver own several bibliographies. Each citation item carries a target-bibliography tag, so one in-text citation can span several bibliographies and still collapse. finish() takes several BibliographyRequests and returns several RenderedBibliographys; the disambiguation and cite-grouping passes are already internal, so now they cover the whole set, and the numbering ranges just follow the bibliography order. This can be additive: keep finish() as the N=1 case, and add a finish_grouped next to it, so existing downstreams do not break. It also replaces with_citation_number_offset completely - no more offset on the consumer side, so all that bookkeeping and per-style special-casing can be removed.
But not everything should merge. Keeping the tags instead of flattening them is what lets some things stay local: sorting (each bibliography sorts its own entries), subsequent-author-substitute (the --- dash, computed over each bibliography's own sequence), and layout / item subset / title (already per-bibliography). So the shape is: one shared pass for disambiguation, cite-grouping and numbering, then N bibliographies each sorted and laid out on their own. The tag gives the driver two views - the whole set for the shared passes, and the per-bibliography split for the local ones - which a plain "merge into one driver" would throw away.
Follow-up from typst/typst#8501.
In typst v0.15-rc1, to make several bibliographies act as one sequence (typst's
group: auto), we use oneBibliographyDriverfor each bibliography, and chain them bywith_citation_number_offset. So numbering crosses the driver boundary - and it is the only thing that does. Everything else CSL computes is per driver, only over that driver's own entries, so nothing else can see across the group.One MWE breaks two things at once --
@a @b, both Lee, 2020, APA:You can tell two differences actually:
find_ambiguous_sets --> year-suffixloop infinish()only sees one driver's entries.@a @bshould collapse into one parenthetical, but the collapse happens inside one singleCitationRequest. Once the cites are split into different drivers, they can not be in the same request, so you get two parentheticals.The second one is the key point: it rules out the easier fix. If we only expose the disambiguation state and inject it, we get (Lee, 2020a) (Lee, 2020b) - disambiguated, but still two parentheticals. The collapse needs both cites in one request, that is, one driver. So a per-dimension patch can not even reproduce the expected output.
I think the boundary itself is in the wrong place. It sits between bibliographies that are actually one sequence, so everything CSL computes over the whole set falls into two groups, and both are broken. One group is never shared: disambiguation, cite-grouping, and also
is_first/ subsequent forms andibid(no issue yet, but same root) - each driver just can not see the others. The other group is shared, but only by hand: numbering, passed through a consumer-side offset. This glue keeps getting more work: #409 added it, #487 extends it to sorted styles, typst/typst#8476 fixed the case that an uncited bibliography does not advance the offset, and #486 proposes to move typst's "does this style use numbers" check into hayagriva - all of them are patching the same root, just one spot at a time. The fix below clears all of them at once, both the shipped ones (#8476, #409) and the in-flight ones (#487, #486).So we can generalize what
finish()already does for one bibliography to N - let one single driver own several bibliographies. Each citation item carries a target-bibliography tag, so one in-text citation can span several bibliographies and still collapse.finish()takes severalBibliographyRequests and returns severalRenderedBibliographys; the disambiguation and cite-grouping passes are already internal, so now they cover the whole set, and the numbering ranges just follow the bibliography order. This can be additive: keepfinish()as the N=1 case, and add afinish_groupednext to it, so existing downstreams do not break. It also replaceswith_citation_number_offsetcompletely - no more offset on the consumer side, so all that bookkeeping and per-style special-casing can be removed.But not everything should merge. Keeping the tags instead of flattening them is what lets some things stay local: sorting (each bibliography sorts its own entries), subsequent-author-substitute (the --- dash, computed over each bibliography's own sequence), and layout / item subset / title (already per-bibliography). So the shape is: one shared pass for disambiguation, cite-grouping and numbering, then N bibliographies each sorted and laid out on their own. The tag gives the driver two views - the whole set for the shared passes, and the per-bibliography split for the local ones - which a plain "merge into one driver" would throw away.