Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit 4f65f8381c849f2c96a80c4b270199948d0f4956
parent ca110d06051901cc1eab9488daa942e0ba9af149
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Wed,  7 Oct 2026 23:33:28 -0400

citations: a cite label may hold an editorial [insertion]

CITE_LINK_RE rejected any `]` in a label, so `[“told [the mayor] so”](cite:id)`
was invisible to extractCiteRefs and to everything built on it: numbering,
the page's citation map (the label rendered as plain text), the per-section
reference lists, validation of an undefined id and the MCP's report reader.
markdown-to-jsx already links such a label. The label now takes one level of
balanced brackets; a lone `]` is still not a link.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Diffstat:
Mcommon/components/citations/citations.test.ts | 3+++
Mcommon/lib/citations/citations.test.ts | 10++++++++++
Mcommon/lib/citations/inline.ts | 8+++++---
Mcommon/lib/report/report.test.ts | 4+++-
Mcommon/lib/report/views.test.ts | 2+-
Mexport/CHANGELOG.md | 1+
6 files changed, 23 insertions(+), 5 deletions(-)

diff --git a/common/components/citations/citations.test.ts b/common/components/citations/citations.test.ts @@ -194,6 +194,9 @@ test("cited markdown: cite links become inline cites, other links stay links, co assert.match(html, /\[5\]/); assert.match(html, /<a [^>]*href="https:\/\/example.org\/z" target="_blank" rel="noopener noreferrer"[^>]*>elsewhere<\/a>/); assert.match(html, /<code[^>]*>\[x\]\(cite:c01\)<\/code>/); + // a label holding an editorial [insertion] is still a cite, with its number + const bracketed = render(h(CitedMarkdown, { citations: byId, children: "She said [“told [the mayor] so”](cite:c01)." })); + assert.match(bracketed, /data-inline-cite="c01"[^]*?>“told \[the mayor\] so”<\/a>[^]*?\[1\]/); // a link to a place on the same page stays in the tab const jump = render(h(CitedMarkdown, { citations: {}, children: "[the claims](#claims)" })); assert.match(jump, /<a [^>]*href="#claims"[^>]*>the claims<\/a>/); diff --git a/common/lib/citations/citations.test.ts b/common/lib/citations/citations.test.ts @@ -245,6 +245,16 @@ test("extractCiteRefs: every cite link in order, with labels and offsets; other assert.deepEqual(extractCiteRefs("[empty](cite:)").map((r) => r.id), [""]); }); +test("extractCiteRefs: a label may hold an editorial [insertion], never a lone ]", () => { + const md = "She said [“the plan is not what [the mayor] signed”](cite:p01), then [x ] y](cite:p02) and [a [b] [c] d](cite:p03)."; + const refs = extractCiteRefs(md); + assert.deepEqual(refs.map((r) => [r.id, r.label]), [ + ["p01", "“the plan is not what [the mayor] signed”"], + ["p03", "a [b] [c] d"], + ]); + assert.equal(md.slice(refs[0].offset, refs[0].offset + 2), "[“"); +}); + test("extractCiteRefs: a cite link in code is text about the syntax", () => { const md = [ "Write `[label](cite:id)` to cite, like [this](cite:c01).", diff --git a/common/lib/citations/inline.ts b/common/lib/citations/inline.ts @@ -28,9 +28,11 @@ export type CiteRef = { offset: number; }; -// `[label](cite:id)`. The label may not contain `]`; the id runs to the first -// `)` or space (an empty id is still a ref, so validation can name it). -const CITE_LINK_RE = /\[([^\]]*)\]\(\s*cite:([^)\s]*)\s*\)/g; +// `[label](cite:id)`. The label may hold one level of balanced brackets — an +// editorial insertion in a quote, `[“told [the mayor] so”](cite:id)`, which +// markdown-to-jsx links too — but not a lone `]`; the id runs to the first `)` +// or space (an empty id is still a ref, so validation can name it). +const CITE_LINK_RE = /\[((?:[^[\]]|\[[^[\]]*\])*)\]\(\s*cite:([^)\s]*)\s*\)/g; // Fenced blocks: a line opening with ``` or ~~~ (up to three spaces in) to // the line closing it with at least as many of the same, or the end. diff --git a/common/lib/report/report.test.ts b/common/lib/report/report.test.ts @@ -94,7 +94,7 @@ test("every reference must resolve: listed citations, source sentences, cite lin k1.citations = ["c01", "c99", "c01"]; k1.sourceQuote = { citation: "c01" }; // not a source citation k1.findings = "See [gone](cite:c98) and [blank](cite:)."; - f.summary = "Also [missing](cite:zz)."; + f.summary = "Also [missing](cite:zz) and [“told [the mayor] so”](cite:zy)."; const ps = validateReport(f); assert.deepEqual(paths(ps).sort(), [ "sections[0].claims[0].citations[1]", @@ -104,8 +104,10 @@ test("every reference must resolve: listed citations, source sentences, cite lin "sections[0].claims[0].sourceQuote.citation", "subject.source", "summary", + "summary", ]); assert.ok(ps.some((p) => /\[gone\]\(cite:c98\) names no citation/.test(p.message))); + assert.ok(ps.some((p) => /cite:zy\) names no citation/.test(p.message))); assert.ok(ps.some((p) => /lists "c01" twice/.test(p.message))); assert.ok(ps.some((p) => /names a video citation/.test(p.message))); }); diff --git a/common/lib/report/views.test.ts b/common/lib/report/views.test.ts @@ -78,7 +78,7 @@ const report: Report = { { id: "two", title: "Two", - body: "A body citing [a post](cite:p1).", + body: "A body citing [“a [quoted] post”](cite:p1).", claims: [{ id: "c2", text: "Claim two.", verdict: "PARTLY", citations: ["a1"] }], }, ], diff --git a/export/CHANGELOG.md b/export/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog ## [Unreleased] +- **A quote with an editorial insertion is still a citation.** An inline citation whose quoted words hold brackets, `[“told [the mayor] so”](cite:id)`, is numbered, previews and links like any other, is listed in the reference list and its section's, and validation names its id when the report lacks it; it used to render as plain text. A label with a lone `]` is still not a link. The MCP's report reader counts it too. Needs `reports prepare` and a rebuild and deploy of each site with reports. - **A report can carry a video.** `report.json` `video` (`{ "src": "video.mp4", "poster": "poster.jpg", "caption": "…" }`, files in the report's directory: an mp4, a png/jpg/webp poster, a one-line caption) plays at the head of the report's page, under its header. Composing the site refuses a report whose video or poster is missing, or whose video is over the 24 MiB publish limit. Needs a rebuild and deploy of the site. - **Search reads every English track of a video, and the transcript switches tracks.** Where a video has another English caption track whose words differ from its transcript — the uploaded captions beside the original audio's, a regional or auto-translated track — a query matches it too: a hit only that track holds says so ("in uploaded captions") and opens the transcript on that track at that moment, and a word both say is found once, in the transcript. The transcript reader shows a small "Track:" switcher beside the mode buttons on such a video; the transcript stays the default, and the choice rides on the share link (`vt`). Downloads and Copy MD take the track on show. Needs an index build and a rebuild and deploy of each site. - **A citation of a Wayback Machine copy links its original and the copy.** A cited record downloaded from a Wayback capture shows "Original (may be gone)", the original at the cited second where its platform takes one, and "Wayback Machine copy, <capture date>", the capture page, which plays. Its moment link is the capture: a capture URL never takes a time param.