specs
Promote to Canonical Source
One command turns a source you'll cite again — a book, a report — into a rich record with its file and full text in your vault.
Promote to Canonical Source
Why Care?
A memo might cite forty sources, but only a handful deserve to live on: the industry report everyone quotes, the book that frames the argument. Today Cite Wide saves every citation the same thin way, a footnote line and a URL. Promote to canonical source marks the few that matter and gives them a complete, reusable record, so the next piece that cites them starts with good data instead of a bare link.
This is the "Promote to Canonical Source" button that [[Maximize-Data-Collection-on-Cannonical-Sources]] names as future work. It writes the schema defined in [[Lossless-Citation-Standards]].
The command
Promote to canonical source (command ID promote-to-canonical-source) is an editor command.
Find the citation. The cursor is on an inline
[^id]or on its[^id]:definition line. Otherwise, show a notice and stop.Gather what's knowable without AI:
Parse the footnote for title (a quoted or linked title), URL, year or date, and the trailing publisher/author segments.
Read the existing light file, if
Citations/<id>.mdexists. Keep its fields andfilesUsedIn.Tier 1, the free metadata extractor. Port Metafetch's
src/services/directFetchService.tsinto Cite Wide as a copy, not an import, so each plugin stays standalone. Credit Metafetch in a header comment. It parses<meta>tags with no dependencies: Open Graph,article:*,author, the scholarlycitation_*tags (including repeatedcitation_author),<title>, and the favicon, and normalizes author names and dates. Use onerequestUrlGET withthrow: falseand a 10 s timeout.Tier 2, Jina Reader (
r.jina.ai, already wired inurlCitationService; the key is optional but raises rate limits). Use it when tier 1 leaves the title or authors empty, and always for the text import below. Firecrawl is a possible later provider, behind its own optional key; not in v1.
Confirm in a modal with these fields prefilled and editable:
title
subtitle
authors, one per line
date published
publisher
publication type: book, report, paper, article, web page, video, or other
URL
Then Promote or Cancel.
Capture the content (checkbox in the modal, on by default):
The original file. If the URL serves a downloadable document (
content-typePDF, EPUB, DOCX, PPTX, or XLSX, or a URL ending in one of those), save its bytes toCitations/_files/<id>.<ext>and record the path indownloaded_content_path.The source text, as markdown. Import it through Jina Reader, which handles both web pages and PDFs, into
Citations/_text/<id>.md, a plain note with a backlink to the citation, and record that path insource_text_path. If Jina is unavailable, skip the import and say so in the notice; the citation is still promoted.Both are skipped quietly when there's no URL.
Write
Citations/<id>.md. Create it or upgrade it in place viaprocessFrontMatter. The body is kept if it exists.
Fields written (v1)
The canonical file keeps every light-format key (hexId, title, url, referenceText, usageCount, filesUsedIn, …), so existing Dataview queries in examples/ keep working. It adds these:
| Field | Source |
canonical: true | constant; how queries tell canonical from light |
internal_uuid | generated v4 UUID, write-once: never regenerated if present |
reference_hexcode | the citation ID |
default_slug | kebab-case of the title |
title, subtitle, authors (list), date_published (ISO, partial OK) | modal |
publisher, publisher_url (site root of the URL) | modal / derived |
publication_type | modal |
first_accessed_at_url | the URL; write-once |
date_added | today; write-once |
date_fetched | ISO timestamp of the fetch, if it succeeded (replaces date_recently_accessed, which meant the same thing) |
piece_og_image | og:image |
cited_in_files | mirrors filesUsedIn |
publisher_favicon_url | <link rel="icon">: SVG first, then the largest PNG up to 96 px, else /favicon.ico |
publisher_app_icon_url | largest apple-touch-icon, else msapplication-TileImage, else an icon of 120 px or more |
publisher_logo_url | the trademark/wordmark: JSON-LD Organization / publisher logo, else og:logo, else itemprop="logo" |
publisher_mask_icon_url, publisher_mask_icon_color | <link rel="mask-icon">, the single-color SVG mark, and its color |
publisher_brand_color | <meta name="theme-color">, else msapplication-TileColor |
publisher_web_manifest_url | <link rel="manifest"> |
Brand assets come from the publisher's homepage when the source page can't supply them: the source is a document (a PDF has no <head>), the fetch failed or hit a bot-check, or the page declared neither an app icon nor a logo. Page-level assets win, and the homepage fills the gaps. They're refreshed on each promote but never blanked.
| downloaded_content_path | Citations/_files/<id>.<ext>, when a file was downloaded |
| source_text_path | Citations/_text/<id>.md, when text was imported (an addition to the standard: the markdown text, distinct from structured_data_path's JSON) |
Promoting again is safe. Write-once fields keep their values, and the user's edits from the modal win over fetched values.
Phase 2 (specified, not built): the enrichment agent
Once a canonical source has its text, an LLM agent (Claude or GPT, with the operator's key) enriches it. It would be a separate command, "Enrich canonical source", so promotion stays fast, free, and deterministic.
Summarize the source into the citation file's body, under
## Summary.Link it into the vault. Wikilink the people, organizations, concepts, and tools it mentions, preferring notes that already exist. The agent gets an inventory of existing note titles and aliases, and creates a new link only when nothing fits.
Tag it, preferring existing tags. The agent gets the vault's tag list (from
metadataCache) and reuses tags before inventing new ones. This is also wherepublisher_typeandtagsfrom the standard get filled.Instructions are a prompt file. It's bundled in the plugin as the default, and a vault-side override can replace it, following the Perplexed preambles pattern: vault files override, and bundled defaults keep the plugin working on its own.
Open questions for that phase: which provider and model by default; how large a vault inventory to send (title list vs. retrieval); and whether to show a review diff before writing.
Out of scope for v1
The enrichment agent (Phase 2 above).
AI-only fields (
publisher_type,tags,edition_or_version, theapi_*fields) andstructured_data_path.Firecrawl as a provider.
Bulk promotion.
Acceptance
Tests, red first:
footnote parsing on real vault shapes (the OpenCloud report line, a book, a plain URL)
the ported tier-1 extractor (multi-author
citation_author, both attribute orders, entities, favicon)content capture: a PDF response is saved to
_files/with the right extension; Jina text goes to_text/with a backlink; a Jina failure still promotesfield assembly (write-once fields kept on re-promote; light keys preserved;
canonical: trueset)command registration (15 commands)
Every gate: tests, tsc, ESLint 0/0, build,
CI=true pnpm@10 install --frozen-lockfile.Operator check in Obsidian: promote
[^80nyxu]inVocabulary/Open Source Software.mdand inspectCitations/80nyxu.md.