
Building a Citation System Users Actually Trust
Explore how to build a citation system that users trust through span highlighting, source verification, and clear attribution.
Trust in digital content is fragile. A user clicking a citation expects a deterministic outcome: the text they see on screen is exactly what the source says. When this mapping fails—due to formatting drift, link rot, or lazy scraping—the system loses credibility. Building a citation system that users trust is not a matter of good UX design; it is an exercise in data integrity, precise text indexing, and verifiable source linkage.
This post explores the engineering principles behind building such a system. We will look at how to handle span highlighting at the DOM level, how to verify sources using content hashing, and how to structure the data flow to ensure attribution remains accurate over time.
The Problem with Hyperlinks
A citation system must solve more than just navigation; it must solve for accuracy. Traditional hyperlinks are weak citations. They point to a resource, not a specific fact. If a user clicks a link to a Wikipedia page, they must scan the entire page to find the relevant paragraph. This cognitive load erodes trust because the system is asking the user to do the work of verification.
A trusted citation system must solve two problems:
- Precision: The UI must highlight the exact text in the source document that supports the claim.
- Verification: The system must prove that the highlighted text actually exists in the source and has not been altered since the citation was created.
Mechanism: Span Highlighting in the DOM
The core challenge in span highlighting is mapping human-readable text to machine-readable offsets. HTML and Markdown are tree structures, not linear strings. When you select text in a browser, the browser provides a Range object, but storing that range is dangerous because DOM mutations (e.g., adding ads, lazy-loading images) can shift character offsets, breaking the citation.
Step 1: Normalize to Text Content Nodes
To build a stable index, you must ignore markup and treat the content as a stream of text nodes. Consider this HTML fragment:
<div class="article">
<p>The sky is <strong>blue</strong>.</p>
<p>Grass is green.</p>
</div>A naive string search might look for "blue" and find it at index 15. But if the <strong> tag is removed dynamically, the index shifts. Instead, we traverse the DOM to collect all TEXT_NODE children and calculate their cumulative byte or character offsets.
function getTextNodes(rootNode) {
const nodes = [];
let cumulativeOffset = 0;
const walker = document.createTreeWalker(
rootNode,
NodeFilter.SHOW_TEXT,
null,
false
);
let currentNode;
while ((currentNode = walker.nextNode())) {
nodes.push({
node: currentNode,
length: currentNode.textContent.length,
offset: cumulativeOffset
});
cumulativeOffset += currentNode.textContent.length;
}
return nodes;
}Note: In production, you must handle UTF-16 surrogate pairs and normalize Unicode (NFC) to ensure consistent character counting across browsers.
Step 2: Create Stable References
Instead of storing raw DOM offsets, we assign a unique ID to each text node or segment. This ID is persisted in the database. When the UI renders, it queries the database for citations associated with those IDs.
Because DOM node instances are not persistent across re-renders, the frontend must either inject stable data-citation-id attributes during initial render or re-scan the DOM to map persisted DB IDs back to current nodes. This decoupling of structure from content is essential for stability, ensuring that even if the DOM tree changes, the logical reference to the text remains valid.
Verification: Content Hashing
A URL is not enough. URLs rot. More importantly, URLs change. A trusted source verification strategy must prove that the content at the URL matches the content in your database.
The Verification Pipeline
- Fetch: When a citation is created or periodically during background jobs, fetch the source content.
- Extract: Isolate the specific paragraph or sentence being cited.
- Hash: Compute a SHA-256 hash of the normalized text.
- Store: Save the hash alongside the citation metadata.
async function verifySource(sourceUrl, targetText) {
const response = await fetch(sourceUrl);
const html = await response.text();
// Parse HTML and extract the specific paragraph
// This step is complex and requires a robust parser like DOMPurify + Cheerio
const extractedText = extractParagraph(html, targetText);
// Normalize whitespace and case
const normalized = extractedText.trim().toLowerCase().replace(/\s+/g, ' ');
const targetNormalized = targetText.trim().toLowerCase().replace(/\s+/g, ' ');
// Check for exact match first
if (normalized === targetNormalized) {
return { status: 'verified', confidence: 1.0 };
}
// If not exact, compute hash for storage
const hashBuffer = await crypto.subtle.digest('SHA-256',
new TextEncoder().encode(normalized)
);
// Convert ArrayBuffer to hex string for storage
const hashHex = Array.from(new Uint8Array(hashBuffer))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
return { status: 'mismatch_risk', hash: hashHex };
}Why Hashing Matters
If a source page is edited later, the hash will change. Your system can detect this mismatch during periodic checks. When a user clicks a citation, you can show a warning: "This source has been modified since the citation was created." This transparency builds trust. Users appreciate knowing when the ground truth has shifted.
Data Flow: Attribution Structure
The data model must support bidirectional lookup: from content to source, and from source to content. Clear attribution is the backbone of this flow.
The CitationNode Interface
interface CitationNode {
id: string; // UUID for the citation instance
sourceId: string; // ID of the source document in your DB
sourceUrl: string; // Canonical URL
textHash: string; // SHA-256 hash of the cited text
originalText: string; // The exact text snippet (for display)
context: string; // Surrounding text for disambiguation
timestamp: number; // When this citation was verified
confidence: number; // 0.0 to 1.0 based on verification strength
}Rendering the Highlight
On the frontend, the citation component receives the CitationNode data. It does not perform verification; it only renders.
function CitationHighlight({ citation }) {
const handleClick = () => {
openSourceModal(citation.sourceUrl, citation.originalText);
};
return (
<span
className="citation-span"
onClick={handleClick}
title={`Verified source: ${citation.sourceUrl}`}
>
{citation.originalText}
</span>
);
}The key here is that the originalText is stored in the database, not generated dynamically. This ensures that even if the source page changes, the user sees exactly what was cited at the time of creation.
User Feedback Loop
No system is perfect. Users will encounter mismatches. A trusted system must allow users to report errors easily to maintain user trust.
The Report Mechanism
Add a subtle "Report Issue" button next to each citation. When clicked, it captures:
- The current state of the source page (screenshot or DOM snapshot).
- The user's description of the error.
- The current hash of the source page.
This data is sent to a moderation queue. Automated systems can re-verify the hash. If the hash changed significantly, the citation is flagged for review. If the user provides a corrected text snippet, it can be used to update the textHash and originalText fields, improving the system's accuracy over time.
Conclusion
Building a citation system that users trust requires moving beyond simple hyperlinking. It demands a rigorous approach to text normalization, stable referencing, and content verification. By implementing span highlighting that decouples content from structure, verifying sources with cryptographic hashes, and maintaining a clear data flow for attribution, you create a system where user trust is engineered, not assumed.
The result is not just a feature; it is a foundation for credible, reliable digital content. Users can click, verify, and move on, confident that the information they consume is accurate and attributable.
Related posts
Building a Deep Research Agent: Multi-Agent Guide
A technical walkthrough for ML engineers on constructing a multi-agent research system that handles synthesis and citations.
SAML Metadata Exchange and Trust Establishment
A technical examination of SAML metadata exchange, covering trust establishment, entity descriptors, certificate rollover, and metadata refresh mechanisms.
Prompt Assembly, Citations, and Grounding
Explore prompt assembly, citations, and grounding techniques for RAG systems. Learn how context ordering and attribution improve accuracy and trust.