WeWrite uses a centralized diff calculation system to provide consistent, reliable text comparison across all features. This system powers version comparisons, activity feeds, and content change tracking.
IMPORTANT: Diff previews MUST show surrounding unchanged words/text so users can understand the context in which the change occurred.
Without context, users cannot determine if a change is a simple typo fix or a significant semantic modification. For example:
Good - Shows context:
...the algorithm manipualtes → manipulates the data...
Bad - No context:
manipualtes → manipulates
- Always show surrounding text - The diff preview must include unchanged words before and after the change
- 50 characters of context - Show approximately 50 characters on each side of the change
- Break at word boundaries - Truncate at word boundaries for clean display
- Use ellipsis for truncation - Show "..." when text is truncated
- Preserve spacing - Don't aggressively trim whitespace that aids readability
...preceding context [removed text] → [added text] following context...
Where:
- Context text: Muted color (gray)
- Removed text: Red background with strikethrough
- Added text: Green background
- Diff API (
/api/diff/route.ts) - Server-side diff calculation service - Diff Service (
/utils/diffService.ts) - Client-side interface and caching - DiffPreview Component (
/components/activity/DiffPreview.tsx) - UI display component - Text Extraction (
/utils/text-extraction.ts) - Content normalization
Content Changes → Diff API → Diff Service → DiffPreview Component → User Interface
Main diff calculation function with caching and server/client compatibility. Supports title changes with special handling.
Returns:
interface DiffResult {
added: number; // Characters added
removed: number; // Characters removed
operations: DiffOperation[]; // Detailed change operations
preview: DiffPreview | null; // UI preview data
hasChanges: boolean; // Whether meaningful changes exist
}Simplified function for getting just the character counts.
Get preview data for UI display.
interface DiffPreview {
beforeContext: string; // Text before changes
addedText: string; // Text that was added
removedText: string; // Text that was removed
afterContext: string; // Text after changes
hasAdditions: boolean; // Whether additions exist
hasRemovals: boolean; // Whether removals exist
}Displays diff previews with consistent styling across the application.
Props:
currentContent- Current version contentpreviousContent- Previous version contenttextDiff- Pre-calculated diff data (optional)isNewPage- Whether this is a new page creationexpandedContext- Show more context lines (default: false)className- Additional CSS classes
Styling Standards:
- Additions: Green background (
bg-green-50 dark:bg-green-900/40) - Removals: Red background with strikethrough (
bg-red-50 dark:bg-red-900/40) - Context: Muted text color for surrounding content
- Line Clamp: 3 lines default, 6 lines with
expandedContext={true}
Shows numerical diff statistics with tooltips.
Props:
added- Number of characters addedremoved- Number of characters removedisNewPage- Whether this is a new page creationshowTooltips- Enable hover tooltips (default: true)
Title changes receive special handling to provide clear, meaningful diff displays:
API Support:
// Title change diff calculation
const titleDiff = await calculateDiff(null, null, {
oldTitle: "Old Page Title",
newTitle: "New Page Title"
});Display Format:
- Prefix: "Title: " appears before the diff content
- Styling: Title prefix uses
text-foreground font-mediumfor emphasis - Context: No ellipsis before "Title: " prefix for clean display
Integration Points:
- Page Save API: Automatically creates title change versions
- Recent Edits: Title changes appear in activity feeds
- Versions Page: Title changes show in version history
- Diff Preview: Special formatting for title-specific changes
Example Output:
Title: New Page Title (old: Old Page Title)
The system uses word-level diffing for more intelligent change detection:
- Text Extraction: Converts rich content to plain text
- Word Tokenization: Splits text into words and whitespace
- LCS Algorithm: Finds longest common subsequence
- Operation Generation: Creates add/remove/equal operations
- Context Generation: Extracts surrounding text for previews
For UI previews, the system:
- Finds the first change (most intuitive for users reading sequentially)
- Collects ALL equal text before the change and takes the last 50 characters
- Collects additions and removals from the change point
- Collects equal text after changes until 50 characters of after-context
- Breaks at word boundaries for clean display
- Preserves spacing for readability (only trims outer whitespace)
- Client-side cache: 5-minute TTL for repeated calculations
- Cache key: Hash of content for efficient lookup
- Memory management: Automatic cleanup of expired entries
- Server-side: Direct API import for SSR
- Client-side: HTTP fetch with caching
- Fallback: Simple comparison if API fails
import { calculateDiff } from '../utils/diffService';
const result = await calculateDiff(currentContent, previousContent);
console.log(`Added: ${result.added}, Removed: ${result.removed}`);import DiffPreview from '../components/activity/DiffPreview';
<DiffPreview
currentContent={current}
previousContent={previous}
expandedContext={true}
className="my-custom-styles"
/>import { diff } from '../utils/diffService';
const { added, removed } = await diff(current, previous);import { calculateDiff } from '../utils/diffService';
// Calculate diff for title change
const titleDiff = await calculateDiff(null, null, {
oldTitle: "Original Title",
newTitle: "Updated Title"
});
// Result includes special title formatting
console.log(titleDiff.preview.beforeContext); // "Title: "
console.log(titleDiff.preview.addedText); // "Updated Title"
console.log(titleDiff.preview.removedText); // "Original Title"- Page versions use diff calculation for change summaries
- Version comparisons show detailed diff previews
- Activity cards display diff statistics
- Home page recent edits show diff previews
- User activity shows change summaries
- Group activity includes diff statistics
- Content change tracking uses diff results
- Character count analytics from diff calculations
- Change pattern analysis for insights
- Default: 50 characters before/after changes
- Configurable in
/api/diff/route.ts - UI line limits: 3 lines default, 6 lines expanded
- TTL: 5 minutes (300,000ms)
- Storage: In-memory Map
- Cleanup: Automatic on access
- Empty Diff Results: Check content extraction and normalization
- Performance Issues: Verify caching is working correctly
- UI Display Problems: Check DiffPreview props and styling
- Server Errors: Review API logs for calculation failures
import { getDiffCacheStats, clearDiffCache } from '../utils/diffService';
// Check cache status
console.log(getDiffCacheStats());
// Clear cache for testing
clearDiffCache();- Semantic Diffing: Understanding of content structure
- Visual Diffing: Side-by-side comparison views
- Diff Annotations: Inline comments and explanations
- Performance Optimization: WebAssembly for large diffs
- Recent Edits System - Recent activity and diffing
- Editor Requirements - Editor functional requirements
- Line Based Editor - Line-based implementation
- Page Data and Versions - Version storage