specs
Enrich Citations
One command fills in every citation's missing title, author, date, publisher, and brand assets — and shows you every change before it writes.
Enrich Citations
Why Care?
Citation files fill in slowly and unevenly: whatever a footnote happened to contain, plus whatever an old extraction wrote, junk included. On 2026-10-06 a one-off run over the lossless vault took author coverage from 43 to 70 of 70 and filled 49 reference texts, 63 publishers, and dozens of logos and dates. That run lived in throwaway scripts outside Obsidian. This spec moves it into Cite Wide, so anyone can run it, safely, any time.
See [[Enriching-the-Lossless-Vault-Citations]] for what that run taught us. Every rule below comes from something that went wrong there.
Commands
| Command | ID | Scope |
| Enrich all citations | enrich-all-citations | Every .md file in the citations folder |
| Enrich this citation | enrich-this-citation | The citation under the cursor ([^id] or [^id]:), or the open file when it lives in the citations folder |
Pipeline (per citation)
Read the citation file's frontmatter (
metadataCache).Find usage. Scan the vault's markdown files (
vault.getMarkdownFiles(), which includes symlinked folders) for[^id]:definition lines and[^id]uses. Do it once per run, building an index for all citations; never once per citation.Fetch. Use the existing functions unchanged:
parseFootnote,fetchTier1,needsTier2→fetchTier2,needsPublisherBrand→fetchPublisherBrand+mergeBrand,isYouTubeUrl→fetchYouTubeChannel, thenbuildPrefill. URL precedence follows the prefill.Plan. A pure function,
planCitationChanges(current, fetched, usage) → FieldChange[], where aFieldChangeis{ key, from, to, reason }. No I/O, so it can be fully unit-tested.Review. A modal lists the citations with proposed changes, showing old → new per field and a checkbox per citation (all on by default). Nothing is written until the user clicks Apply.
Write. Use
processFrontMatter, applying only the approved changes.Report. Create a note,
<citations folder>/_reports/Enrichment-<YYYY-MM-DD-HHmm>.md, listing:what changed (counts per field)
dead or blocked links (both tiers failed)
citations with no footnote anywhere
fields still empty
Planning rules (the load-bearing part)
| Field | Rule | |
title, url, date, source, referenceText | Fill when empty or junk (isJunkTitle for titles). Otherwise never overwrite. Entity-encoded titles (’) are replaced by their decoded form. | |
author | Fill when empty, or when the stored value fails isPlausibleAuthor (reading times, sentence fragments, link debris). | |
authors (list) | Add when absent; replace only when every entry fails isPlausibleAuthor. | |
publisher, publisher_url, date_published, piece_og_image, publisher_* brand fields | Add when absent; never blank a stored value. | |
date_fetched | ISO timestamp of the fetch, when tier 1 or tier 2 succeeded. One field: date_recently_accessed meant the same thing and is no longer written. | |
filesUsedIn | The notes that use [^id] today, as `[[full/path | Alias]] (obsidianLinker`). Replace when the stored list differs and at least one usage was found; keep it when none was found. |
canonical | Never set. Promotion stays a deliberate act. | |
| everything else | Untouched. |
Behavior
Concurrency: 4 fetches at a time. A progress notice shows "Enriching 12 / 70…", with a Cancel button that stops before the review step.
Re-running is safe. A second run over an enriched vault proposes zero changes, apart from
date_fetched, which is excluded from the "has changes" count.Standalone: no new dependencies; all network calls go through
requestUrl.
Acceptance
Tests are written red first:
planCitationChanges, against the real cases:an empty file plus a footnote → filled
stored junk author "3 minutes" → replaced
a real stored title kept over a fetched one
"Page not found | MRU" replaced
entity title decoded
filesUsedInpath →[[full/path|Alias]]no usage → kept
canonicalnever setan already-enriched file → no changes (idempotence)
The usage index, over a stub vault with nested and symlinked-style paths.
Report rendering, as a pure function.
The onload test expects 17 commands.
Gates: pnpm test, tsc, ESLint 0/0, and the build. The operator then runs Enrich all citations on the lossless vault and expects the review modal to propose little or nothing, since the vault was already enriched by the one-off run.