first-reference-note-number is a CSL number variable. For note styles (as opposed to in-text styles), the number of a preceding note containing the first reference to the item should be assigned by the CSL processor.
In the example below, the CSL style displays first-reference-note-number for every citation, but Typst never assigns the variable.
<citation>
<layout suffix=".">
<text variable="title"/>
<text value=", "/>
<text variable="first-reference-note-number"/>
</layout>
</citation>
@article{a, title = {A} }
@article{b, title = {B} }
Full code
#set page(height: auto)
#set footnote(numbering: "①")
#show footnote.entry: it => {
counter(footnote).display(at: it.note.location())
[ ]
it.note.body
}
#set enum(numbering: "①")
A@a, B@b, A@a, A@a.
= Expected output
+ A, 1.
+ B, 2.
+ A, 1.
+ A, 1.
#bibliography(
title: [Actual output],
bytes("
@article{a, title = {A} }
@article{b, title = {B} }
"),
style: bytes(
```xml
<?xml version="1.0" encoding="utf-8"?>
<style xmlns="http://purl.org/net/xbiblio/csl" version="1.0" class="note">
<info>
<title/>
<id/>
</info>
<citation>
<layout suffix=".">
<text variable="title"/>
<text value=", "/>
<text variable="first-reference-note-number"/>
</layout>
</citation>
<bibliography>
<layout>
<text value="(irrelevant)"/>
</layout>
</bibliography>
</style>
```.text,
),
)
The CSL test suite contains several tests for first-reference-note-number, but according to the following logic, none of them can be tested in this repo.
|
let can_test = case.bib_entries.is_none() |
|
&& case.bib_section.is_none() |
|
&& case.citations.is_none() |
|
&& case.citation_items.as_ref().is_none_or(|cites| { |
This issue affects the gb-7714-2015-note style.
Additional tests
It looks like that the behaviour of first-reference-note-number and position differs from Typst and Hayagriva.
In the following test, the output of Hayagriva doesn't match the expectation either, but at least first-reference-note-number is not empty.
CSL test case
>>===== MODE =====>>
citation
<<===== MODE =====<<
>>===== RESULT =====>>
(omitted)
<<===== RESULT =====<<
>>===== CSL =====>>
<style
xmlns="http://purl.org/net/xbiblio/csl"
class="note"
version="1.0">
<info>
<id />
<title />
<updated>2009-08-10T04:49:00+09:00</updated>
</info>
<citation>
<layout suffix=".">
<text variable="title" suffix=", "/>
<text variable="locator"/>
<text value=", "/>
<text variable="first-reference-note-number"/>
<text value=" — Position: "/>
<group delimiter=", ">
<choose><if position="first"><text value="first"/></if></choose>
<choose><if position="ibid"><text value="ibid"/></if></choose>
<choose><if position="ibid-with-locator"><text value="ibid-with-locator"/></if></choose>
<choose><if position="subsequent"><text value="subsequent"/></if></choose>
<choose><if position="near-note"><text value="near-note"/></if></choose>
</group>
</layout>
</citation>
</style>
<<===== CSL =====<<
>>===== CITATION-ITEMS =====>>
[
[{ "id": "a" }],
[{ "id": "b" }],
[{ "id": "a" }],
[{ "id": "a" }],
[{ "id": "b" }]
]
<<===== CITATION-ITEMS =====<<
>>===== INPUT =====>>
[
{
"id": "a",
"title": "A"
},
{
"id": "b",
"title": "B"
}
]
<<===== INPUT =====<<
>>===== VERSION =====>>
1.0
<<===== VERSION =====<<
Equivalent Typst code
#set page(height: auto)
A@a, B@b, A@a, A@a, B@b.
#bibliography(
bytes("
@article{a, title = {A} }
@article{b, title = {B} }
"),
style: bytes(
```xml
<?xml version="1.0" encoding="utf-8"?>
<style
xmlns="http://purl.org/net/xbiblio/csl"
class="note"
version="1.0">
<info>
<id />
<title />
<updated>2009-08-10T04:49:00+09:00</updated>
</info>
<citation>
<layout suffix=".">
<text variable="title" suffix=", "/>
<text variable="locator"/>
<text value=", "/>
<text variable="first-reference-note-number"/>
<text value=" — Position: "/>
<group delimiter=", ">
<choose><if position="first"><text value="first"/></if></choose>
<choose><if position="ibid"><text value="ibid"/></if></choose>
<choose><if position="ibid-with-locator"><text value="ibid-with-locator"/></if></choose>
<choose><if position="subsequent"><text value="subsequent"/></if></choose>
<choose><if position="near-note"><text value="near-note"/></if></choose>
</group>
</layout>
</citation>
<bibliography>
<layout>
<text value="(irrelevant)"/>
</layout>
</bibliography>
</style>
```.text,
),
)
Expected output:
A, 1 — Position: first.
B, 2 — Position: first.
A, 1 — Position: subsequent, near-note.
A, 1 — Position: ibid, subsequent, near-note.
B, 2 — Position: subsequent, near-note.
Output of Typst v0.15.1:
A, — Position: first.
B, — Position: first.
A, — Position: subsequent.
A, — Position: ibid, subsequent.
B, — Position: subsequent.
Output of cargo test --features csl-json local for Hayagriva:
A, 1 — Position: first.
B, 1 — Position: first.
A, 1 — Position: subsequent, near-note.
A, 1 — Position: ibid, subsequent, near-note.
B, 1 — Position: subsequent, near-note.
Update on 2026-07-21
When testing against the CSL test suite, the note_number passed to Hayagriva is always Some(1). As a result, Hayagriva always resolves first-reference-note-number to 1.
Changing Some(1) to Some(n + 1) would make the output of Hayagriva matches the expectation.
However, citeproc-pass.txt doesn't change, because all relevant test cases are excluded by tests/citeproc.rs.
diff --git a/src/csl/mod.rs b/src/csl/mod.rs
index 6221859..0005d64 100644
--- a/src/csl/mod.rs
+++ b/src/csl/mod.rs
@@ -2532,7 +2532,7 @@ impl<'a> CiteProperties<'a> {
/// citation or bibliography entry.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
struct CertainCiteProperties {
- /// The number of the footnote this citation appears in.
+ /// The number of the footnote this citation appears in, starting at 1.
///
/// We can determine this because it depends on citation order only. May be
/// none if the current style is in-text.
diff --git a/tests/citeproc.rs b/tests/citeproc.rs
index c827311..ad81d69 100644
--- a/tests/citeproc.rs
+++ b/tests/citeproc.rs
@@ -541,7 +541,7 @@ where
let mut driver: BibliographyDriver<'_, csl_json::Item> = BibliographyDriver::new();
let mut output = String::new();
if let Some(cites) = &case.citation_items {
- for cite in cites {
+ for (n, cite) in cites.into_iter().enumerate() {
driver.citation(CitationRequest::new(
cite.iter()
.map(|i| {
@@ -572,7 +572,7 @@ where
&style,
None,
locales,
- Some(1),
+ Some(n + 1),
));
}
} else {
first-reference-note-numberis a CSL number variable. For note styles (as opposed to in-text styles), the number of a preceding note containing the first reference to the item should be assigned by the CSL processor.In the example below, the CSL style displays
first-reference-note-numberfor every citation, but Typst never assigns the variable.Full code
The CSL test suite contains several tests for
first-reference-note-number, but according to the following logic, none of them can be tested in this repo.hayagriva/tests/citeproc.rs
Lines 483 to 486 in 5a71313
This issue affects the
gb-7714-2015-notestyle.Additional tests
It looks like that the behaviour of
first-reference-note-numberandpositiondiffers from Typst and Hayagriva.In the following test, the output of Hayagriva doesn't match the expectation either, but at least
first-reference-note-numberis not empty.CSL test case
Equivalent Typst code
Expected output:
Output of Typst v0.15.1:
Output of
cargo test --features csl-json localfor Hayagriva:Update on 2026-07-21
When testing against the CSL test suite, the
note_numberpassed to Hayagriva is alwaysSome(1). As a result, Hayagriva always resolvesfirst-reference-note-numberto1.hayagriva/tests/citeproc.rs
Line 575 in 5a71313
Changing
Some(1)toSome(n + 1)would make the output of Hayagriva matches the expectation.However,
citeproc-pass.txtdoesn't change, because all relevant test cases are excluded bytests/citeproc.rs.