@pierre/diffs is an open source diff and code rendering library. Built on Shiki for syntax highlighting and theming, super customizable, and packed with features. Made by The Pierre Computer Company.
Currently v1.3.0-beta.4
Enable edit mode in any File or FileDiff component with the EditorProvider. Includes support for selection management, auto-indention, undo history, find-in-file, lint markers, and more. Pairs nicely with @pierre/trees for AUI style experiences. Explore everything edit can do.
16 unmodified lines1718192021222324252625 unmodified lines525354555657585960616263646565 unmodified lines13113213313413513613713813914014114214314417 unmodified lines16216316416516616716816917017117217317417517617717817918018118218318418518663 unmodified lines25025125225325425525625725825926026126226326426526618 unmodified lines28528628728828929029129229325 unmodified lines31932032132232332432532632750 unmodified lines3783793803813823833843853864 unmodified lines16 unmodified linesgetSessionGitStatus,getSessionPaths,} from './mockData';export type ThemeType = 'light' | 'dark';// The editor's stylesheet flattens every line number to one neutral colour// (`--diffs-editor-line-number-fg`) and is injected as an unlayered <style>,// so it overrides the library's per-line colouring (which lives in @layer// base). We adopt this extra, higher-specificity unlayered sheet into the25 unmodified lines// component, so it's created in an effect and torn down on session change.function ChangesTree({session,activePath,themeType,onSelect,}: {session: AuiSession;activePath: string | null;themeType: ThemeType;onSelect: (path: string) => void;}) {const containerRef = useRef<HTMLDivElement | null>(null);const treeRef = useRef<FileTree | null>(null);65 unmodified lines};}, [session]);// Inline color-scheme beats the tree's `:host { color-scheme: light dark }`,// flipping its light-dark() colours with our toggle.useEffect(() => {if (containerRef.current != null) {containerRef.current.style.colorScheme = themeType;}}, [themeType, session]);// Keep the highlighted row matched to the active file.useEffect(() => {const tree = treeRef.current;17 unmodified linesreturn <div ref={containerRef} className="aui-tree" />;}export interface AgentUiProps {// Theme is controlled by the parent so the toggle can live outside the// component (the homepage section renders its own button group).themeType: ThemeType;// Highlight themes the surrounding worker pool was initialized with. Defaults// to the shared homepage pool's themes.theme?: { dark: string; light: string };// Server-rendered diff HTML keyed by file path. When present the matching// FileDiff hydrates from this markup (already syntax-highlighted) instead of// waiting on the client worker, which also avoids an SSR/client mismatch.prerenderedDiffs?: Record<string, string>;}export function AgentUi({themeType,theme = DEFAULT_THEMES,prerenderedDiffs,}: AgentUiProps) {const session = AUI_SESSIONS[0];const [activePath, setActivePath] = useState<string | null>(() => session.changedFiles[0]?.path ?? null63 unmodified lines: null,[activeFile, editKey]);// Server-rendered, already-highlighted HTML for the active diff. The snapshot// is generated in dark mode, so only reuse it while the demo is in dark mode// (otherwise a freshly opened file would flash dark before re-highlighting).// It's also only safe when the file is unedited so the markup matches// `fileDiff`.const activePrerenderedHTML =themeType === 'dark' &&activePath != null &&editsRef.current.get(editKey) == null? prerenderedDiffs?.[activePath]: undefined;const breadcrumbSegments = activePath != null ? activePath.split('/') : [];18 unmodified lines}, [activePath]);return (<EditorProvider editor={editor}><div className="aui" data-theme-type={themeType} data-embedded="true"><div className="aui-body"><section className="aui-center"><header className="aui-center-header"><nav className="aui-breadcrumb" aria-label="File path">25 unmodified linesfileDiff={fileDiff}className="aui-surface"options={{theme,themeType,disableFileHeader: true,overflow: 'wrap',diffStyle: 'unified',}}50 unmodified lines</div><ChangesTreesession={session}activePath={activePath}themeType={themeType}onSelect={openFile}/></aside></div>4 unmodified lines16 unmodified lines171819202122232425 unmodified lines50515253545556575859606165 unmodified lines12712812913013113213313413513613713813914017 unmodified lines15815916016116216316416516616716816917017117217317417517617763 unmodified lines24124224324424524624724824925025125218 unmodified lines27127227327427527627727827925 unmodified lines30530630730830931031131231350 unmodified lines3643653663673683693703714 unmodified lines16 unmodified linesgetSessionGitStatus,getSessionPaths,} from './mockData';// The editor's stylesheet flattens every line number to one neutral colour// (`--diffs-editor-line-number-fg`) and is injected as an unlayered <style>,// so it overrides the library's per-line colouring (which lives in @layer// base). We adopt this extra, higher-specificity unlayered sheet into the25 unmodified lines// component, so it's created in an effect and torn down on session change.function ChangesTree({session,activePath,onSelect,}: {session: AuiSession;activePath: string | null;onSelect: (path: string) => void;}) {const containerRef = useRef<HTMLDivElement | null>(null);const treeRef = useRef<FileTree | null>(null);65 unmodified lines};}, [session]);// Inline color-scheme beats the tree's `:host { color-scheme: light dark }`,// pinning its light-dark() colours to the demo's dark mode.useEffect(() => {if (containerRef.current != null) {containerRef.current.style.colorScheme = 'dark';}}, [session]);// Keep the highlighted row matched to the active file.useEffect(() => {const tree = treeRef.current;17 unmodified linesreturn <div ref={containerRef} className="aui-tree" />;}export interface AgentUiProps {// Highlight themes the surrounding worker pool was initialized with. Defaults// to the shared homepage pool's themes.theme?: { dark: string; light: string };// Server-rendered diff HTML keyed by file path. When present the matching// FileDiff hydrates from this markup (already syntax-highlighted) instead of// waiting on the client worker, which also avoids an SSR/client mismatch.prerenderedDiffs?: Record<string, string>;}// The demo is always dark: the snapshot is prerendered dark and matching it// avoids theme flashing, so there is no light/dark toggle.export function AgentUi({ theme = DEFAULT_THEMES, prerenderedDiffs }: AgentUiProps) {const session = AUI_SESSIONS[0];const [activePath, setActivePath] = useState<string | null>(() => session.changedFiles[0]?.path ?? null63 unmodified lines: null,[activeFile, editKey]);// Server-rendered, already-highlighted HTML for the active diff. Only safe// when the file is unedited so the markup matches `fileDiff`.const activePrerenderedHTML =activePath != null && editsRef.current.get(editKey) == null? prerenderedDiffs?.[activePath]: undefined;const breadcrumbSegments = activePath != null ? activePath.split('/') : [];18 unmodified lines}, [activePath]);return (<EditorProvider editor={editor}><div className="aui" data-theme-type="dark" data-embedded="true"><div className="aui-body"><section className="aui-center"><header className="aui-center-header"><nav className="aui-breadcrumb" aria-label="File path">25 unmodified linesfileDiff={fileDiff}className="aui-surface"options={{theme,themeType: 'dark',disableFileHeader: true,overflow: 'wrap',diffStyle: 'unified',}}50 unmodified lines</div><ChangesTreesession={session}activePath={activePath}onSelect={openFile}/></aside></div>4 unmodified lines
Choose from stacked (unified) or split (side-by-side). Both use CSS Grid and Shadow DOM under the hood, meaning fewer DOM nodes and faster rendering.
11 unmodified lines121314151617181920212223242526272829303132333435363730 unmodified lines11 unmodified linesThemesType,} from '../types';export function createSpanFromToken(token: ThemedToken) {const element = document.createElement('div');const style = getTokenStyleObject(token);element.style = stringifyTokenStyle(style);return element;}export function createRow(line: number) {const row = document.createElement('div');row.dataset.line = `${line}`;const lineColumn = document.createElement('div');lineColumn.dataset.columnNumber = '';lineColumn.textContent = `${line}`;const content = document.createElement('div');content.dataset.columnContent = '';row.appendChild(lineColumn);row.appendChild(content);return { row, content };}30 unmodified lines11 unmodified lines121314151617181920212223242526272829303132333430 unmodified lines11 unmodified linesThemesType,} from '../types';export function createSpanFromToken(token: ThemedToken) {const element = document.createElement('span');const style = token.htmlStyle ?? getTokenStyleObject(token);element.style = stringifyTokenStyle(style);element.textContent = token.content;element.dataset.span = ''return element;}export function createRow(line: number) {const row = document.createElement('div');row.dataset.line = `${line}`;const content = document.createElement('div');content.dataset.columnContent = '';row.appendChild(content);return { row, content };}30 unmodified lines
Your diffs, your choice. Render changed lines with classic diff indicators (+/–), full-width background colors, or vertical bars. You can even highlight inline changes—character or word based—and toggle line wrapping, hide numbers, and more.
1234567891011121314const std = @import("std");const Allocator = std.heap.page_allocator;const ArrayList = std.ArrayList;pub fn main() !void {const stdout_writer_instance = std.io.getStdOut().writer();try stdout_writer_instance.print("Hi You, {s}! Welcome to our application.\n", .{"World"});var list = ArrayList(i32).init(allocator);defer list.deinit();const configuration_options = .{ .enable_logging = true, .max_buffer_size = 1024, .timeout_milliseconds = 5000 };_ = configuration_options;}12345678910111213141516171819const std = @import("std");const GeneralPurposeAllocator = std.heap.GeneralPurposeAllocator;const ArrayList = std.ArrayList;pub fn main() !void {var gpa = GeneralPurposeAllocator(.{}){};defer _ = gpa.deinit();const allocator = gpa.allocator();const stdout_writer_instance = std.io.getStdOut().writer();try stdout_writer_instance.print("Hello There, {s}! Welcome to the updated Zig application.\n", .{"Zig"});var list = ArrayList(i32).init(allocator);defer list.deinit();try list.append(42);const configuration_options = .{ .enable_logging = true, .max_buffer_size = 2048, .timeout_milliseconds = 10000, .retry_count = 3 };_ = configuration_options;}
Render conflicts through a dedicated diff primitive that treats current and incoming sections as structured additions/deletions without running text diffing. Resolve by choosing current, incoming, or both changes and preview the updated file instantly.
19 unmodified lines20212223232425262714 unmodified lines42434444454647454647484950515253545521 unmodified lines19 unmodified linesexport async function createSession(userId: string) { await cleanupExpiredSessions(userId);
|| <<<<<<< HEAD const data = {======= const sessionData = { source: 'web',>>>>>>> feature/oauth-session-source provider: 'password', userId, expiresAt: Date.now() + SESSION_TTL,14 unmodified lines if (oldest) await invalidateSession(oldest.id); }
|| <<<<<<< HEAD await db.auditLog.create({ event: 'session.created', userId, });======= await db.sessionEvent.create({ type: 'audit-log', data: { sessionId: session.id, type: 'created', source: sessionData.source ?? 'credentials', }, });>>>>>>> feature/oauth-session-source
return { session, token };}21 unmodified lines@pierre/diffs provide a flexible annotation framework for injecting additional content and context. Use it to render your own line comments, annotations from CI jobs, and other third-party content.
2 unmodified lines34567789101112131414151617181919202122 2 unmodified linesfrom typing import Optional
SECRET_KEY = "your-secret-key"
def create_token(user_id: str, expires_in: int = 3600) -> str:def create_token(user_id: str, role: str = "user", expires_in: int = 3600) -> str: payload = { "sub": user_id, "role": role, "exp": time.time() + expires_in } return jwt.encode(payload, SECRET_KEY, algorithm="HS256")
def verify_token(token: str) -> Optional[str]:def verify_token(token: str) -> Optional[dict]: try: payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"]) if payload["exp"] < time.time(): return None return payload["sub"] return {"user_id": payload["sub"], "role": payload["role"]} except jwt.InvalidTokenError: return NoneShould we validate the role parameter? We could restrict it to a set of allowed values.
Good idea, maybe use a Literal type or an enum.
Agreed, we should also update verify_token to return the role.
Should we validate the role parameter? We could restrict it to a set of allowed values.
Good idea, maybe use a Literal type or an enum.
Agreed, we should also update verify_token to return the role.
Annotations can also be used to build interactive code review interfaces similar to AI-assisted coding tools like Cursor. Use it to track the state of each change, inject custom UI like accept/reject buttons, and provide immediate visual feedback.
4 unmodified lines567891091011121314152 unmodified lines4 unmodified lines <title>Welcome</title></head><body> <header> <h1>Welcome</h1> <p>Thanks for visiting</p> <h1>Welcome to Our Site</h1> <p>We're glad you're here</p> <a href="/about" class="btn">Learn More</a> </header> <footer> <p>© Acme Inc.</p> </footer>2 unmodified linesTurn on line selection with enableLineSelection: true. When enabled, clicking a line number will select that line. Click and drag to select multiple lines, or hold Shift and click to extend your selection. You can also control the selection programmatically. Also selections will elegantly manage the differences between split and unified views.
16 unmodified lines1718192021222324252627282930313233343536373839404110 unmodified lines16 unmodified lines}~Vector() {delete[] data;data = nullptr;}void push_back(const T& value) {if (length >= capacity) {reserve(capacity * 2);}data[length++] = value;}T& operator[](size_t index) {if (index >= length) {throw std::out_of_range("Index out of bounds");}return data[index];}size_t size() const { return length; }bool empty() const { return length == 0; }void reserve(size_t newCapacity) {10 unmodified lines16 unmodified lines171819202122232425262728293031323334353637383940414243444510 unmodified lines16 unmodified lines}~Vector() {delete[] data;}void push_back(const T& value) {if (length >= capacity) {size_t newCap = capacity == 0 ? 1 : capacity * 2;reserve(newCap);}data[length++] = value;}T& operator[](size_t index) {return data[index];}void clear() {length = 0;}T& front() { return data[0]; }T& back() { return data[length - 1]; }size_t size() const { return length; }bool empty() const { return length == 0; }void reserve(size_t newCapacity) {10 unmodified lines
Attach hover callbacks to individual syntax tokens with onTokenEnter and onTokenLeave. Here we’ve added a static CSS knowledge index to show information-rich tooltips on hover. Try hovering over CSS properties, values, and at-rules below. See the Token Hooks docs for the full API.
1234567891011121314151617181920212223242526272829303132333435.card-grid {display: flex;flex-direction: column;flex-wrap: wrap;gap: 1rem;max-width: 960px;margin-inline: auto;}.card-grid .card {display: flex;align-items: center;gap: 0.75rem;padding: 1rem;background-color: var(--color-surface);border-radius: 8px;border: 1px solid transparent;}.card-grid .card:hover {border: 1px solid var(--color-border);box-shadow: 0 2px 8px rgb(0 0 0 / 0.08);}@media (min-width: 640px) {.card-grid {flex-direction: row;}}@media (min-width: 1024px) {.card-grid {max-width: 1200px;}}12345678910111213141516171819202122232425262728293031323334353637383940414243@layer components {.card-grid {container: cards / inline-size;display: grid;grid-template-columns: 1fr;gap: 1rem;max-width: 960px;margin-inline: auto;margin-trim: in-flow;}.card-grid .card {display: flex;align-items: center;gap: 0.75rem;padding: 1rem;background-color: var(--color-surface);border-radius: 8px;border: 1px solid transparent;&:hover {border: 1px solid var(--color-border);box-shadow: 0 2px 8px rgb(0 0 0 / 0.08);}&:focus-visible {outline: 2px solid var(--color-accent);}}@container cards (min-width: 640px) {.card-grid {grid-template-columns: repeat(2, 1fr);}}@container cards (min-width: 1024px) {.card-grid {grid-template-columns: repeat(3, 1fr);max-width: 1200px;}}}
We built @pierre/diffs on top of Shiki for syntax highlighting and general theming. Our components automatically adapt to blend in with your theme selection, including across color modes.
1234456767891011101112use std::io;
fn main() { println!("What is your name?"); println!("Enter your name:"); let mut name = String::new(); io::stdin().read_line(&mut name).unwrap(); println!("Hello, {}", name.trim()); io::stdin().read_line(&mut name).expect("read error"); println!("Hello, {}!", name.trim());}
fn add(x: i32, y: i32) -> i32 { return x + y;fn add(a: i32, b: i32) -> i32 { a + b}Love the Pierre themes? Install our Pierre Theme pack with light and dark flavors, or learn how to build your own Shiki themes.
@pierre/diffs adapts to any font, font-size, line-height, and even font-feature-settings you may have set. Configure font options with your preferred CSS method globally or on a per-component basis.
38 unmodified lines394041424344454344474546474810 unmodified lines38 unmodified lines if !exists { return nil, false }
now := time.Now() expired := now.After(item.ExpiresAt) if expired { if time.Now().After(item.ExpiresAt) { delete(c.items, key) c.onEviction(key, item.Value) return nil, false }
return item.Value, true10 unmodified linesSwap between the built-in hunk separator styles and a CSS-only custom variant to preview how collapsed chunks are displayed.
6 unmodified linesExpand all78910111213141519 unmodified linesExpand all35363738394041424315 unmodified linesExpand all59606162636465666713 unmodified linesExpand all6 unmodified linesExpand allsummary.push('checkpoint-02');summary.push('checkpoint-03');summary.push('checkpoint-04');summary.push('checkpoint-05');summary.push('phase:boot');summary.push('checkpoint-07');summary.push('checkpoint-08');summary.push('checkpoint-09');summary.push('checkpoint-10');19 unmodified linesExpand allsummary.push('checkpoint-30');summary.push('checkpoint-31');summary.push('checkpoint-32');summary.push('checkpoint-33');summary.push('phase:mid');summary.push('checkpoint-35');summary.push('checkpoint-36');summary.push('checkpoint-37');summary.push('checkpoint-38');15 unmodified linesExpand allsummary.push('checkpoint-54');summary.push('checkpoint-55');summary.push('checkpoint-56');summary.push('checkpoint-57');summary.push('phase:tail');summary.push('checkpoint-59');summary.push('checkpoint-60');summary.push('checkpoint-61');summary.push('checkpoint-62');13 unmodified linesExpand all6 unmodified linesExpand all78910111213141519 unmodified linesExpand all35363738394041424315 unmodified linesExpand all596061626364656667686913 unmodified linesExpand all6 unmodified linesExpand allsummary.push('checkpoint-02');summary.push('checkpoint-03');summary.push('checkpoint-04');summary.push('checkpoint-05');summary.push('phase:boot-ready');summary.push('checkpoint-07');summary.push('checkpoint-08');summary.push('checkpoint-09');summary.push('checkpoint-10');19 unmodified linesExpand allsummary.push('checkpoint-30');summary.push('checkpoint-31');summary.push('checkpoint-32');summary.push('checkpoint-33');summary.push(`phase:mid-${tasks.length}`);summary.push('checkpoint-35');summary.push('checkpoint-36');summary.push('checkpoint-37');summary.push('checkpoint-38');15 unmodified linesExpand allsummary.push('checkpoint-54');summary.push('checkpoint-55');summary.push('checkpoint-56');summary.push('checkpoint-57');if (tasks.length > 0) {summary.push(`phase:tail-${tasks[0].id}`);}summary.push('checkpoint-59');summary.push('checkpoint-60');summary.push('checkpoint-61');summary.push('checkpoint-62');13 unmodified linesExpand all
Customize your File or FileDiff headers with two options: inserting additional header metadata, or a fully custom header rendered inside the built-in data-diffs-header shell.
4 unmodified lines5678910111112131213141516171819201921222324254 unmodified lines
let apiBaseURL: URL let timeout: TimeInterval let maxRetries: Int let enableLogging: Bool
private init() { self.apiBaseURL = URL(string: "https://api.example.com")! self.timeout = 30.0 self.maxRetries = 3 self.apiBaseURL = URL(string: "https://api.example.com/v2")! self.timeout = 60.0 self.maxRetries = 5 self.enableLogging = true }
func headers() -> [String: String] { return [ "Content-Type": "application/json", "Accept": "application/json" "Accept": "application/json", "X-API-Version": "2.0" ] }}You can also pass any two files in @pierre/diffs to diff them. This is especially useful when comparing across generative snapshots where linear history isn't always available. Edit the CSS files below to see the diff.
1233567845678.pizza { display: flex; justify-content: center; align-items: center; gap: 8px; padding: 12px; border-radius: 8px; background: #fff; gap: 12px; padding: 16px; border-radius: 12px; background: var(--surface);}Collectively, our team brings over 150 years of expertise designing, building, and scaling the world's largest distributed systems at Cloudflare, Coinbase, Discord, GitHub, Reddit, Stripe, X, and others.