Architecture Overview¶
diff·log is built as a multi-page app with optional cloud sync capabilities.
Tech Stack¶
| Layer | Technology |
|---|---|
| Frontend | Alpine.js |
| Styling | Custom CSS with CSS variables |
| Bundling | Bun HTML imports |
| Backend | Cloudflare Pages Functions |
| Database | Cloudflare D1 (SQLite) |
| AI | Anthropic Claude API (see AI Pipeline) |
Client Architecture¶
Module Structure¶
The client code is organized into focused modules:
src/
├── app.ts # Entry point (Alpine plugin setup)
├── store.ts # Alpine store (state management)
├── components/
│ ├── index.ts # Barrel export
│ ├── dashboard.ts # Main dashboard component
│ ├── profiles.ts # Profile management component
│ ├── setup.ts # Setup wizard component
│ └── share-profile.ts
└── lib/
├── sync.ts # Sync service
├── time.ts # Time utilities
├── api.ts # Fetch utilities
├── constants.ts # Shared constants
├── crypto.ts # Encryption
├── prompt.ts # Prompt building
├── feeds.ts # Feed fetching
└── markdown.ts # Markdown rendering
Alpine.js Store¶
The central state management (src/store.ts) uses Alpine's $persist plugin for localStorage persistence:
Alpine.store('app', {
// Persisted state
profiles: Alpine.$persist({}).as('difflog-profiles'),
histories: Alpine.$persist({}).as('difflog-histories'),
bookmarks: Alpine.$persist({}).as('difflog-bookmarks'),
activeProfileId: Alpine.$persist(null).as('difflog-active-profile'),
pendingSync: Alpine.$persist({}).as('difflog-pending-sync'),
// Session state (not persisted)
syncing: false,
syncError: null,
syncStatus: null,
// Computed
get profile() { return this.profiles[this.activeProfileId]; },
get history() { return this.histories[this.activeProfileId] || []; },
get stars() { return this.bookmarks[this.activeProfileId] || []; },
// Helper methods for templates
get starCountLabel() { ... },
formatTimeAgo(dateStr) { ... },
});
Page Structure¶
Each page is a full HTML document that works standalone:
pages/
├── splash.html # Landing page (/welcome)
├── setup.html # Profile setup wizard (/setup)
├── dashboard.html # Main app (/)
├── archive.html # Past diffs list (/archive)
├── stars.html # Bookmarked items (/stars)
└── profiles.html # Profile management (/profiles)
CSS View Transitions provide smooth cross-document navigation.
Server Architecture¶
Cloudflare Pages Functions¶
API endpoints are TypeScript functions in the functions/ directory:
functions/
├── api/
│ ├── generate.ts # Generate diff via Claude
│ ├── profile/
│ │ ├── create.ts # Create/update profile
│ │ ├── [id].ts # Get/update/delete profile
│ │ └── [id]/
│ │ ├── content.ts # Download encrypted content
│ │ ├── status.ts # Sync status check
│ │ └── sync.ts # Upload content
│ └── share/
│ └── [id].ts # Public profile info
└── types.ts # Shared types
D1 Database Schema¶
CREATE TABLE profiles (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
password_hash TEXT NOT NULL,
encrypted_api_key TEXT NOT NULL,
salt TEXT NOT NULL,
languages TEXT, -- JSON array
frameworks TEXT, -- JSON array
tools TEXT, -- JSON array
topics TEXT, -- JSON array
depth TEXT DEFAULT 'standard',
custom_focus TEXT,
resolved_sources TEXT, -- JSON object
diffs_hash TEXT,
stars_hash TEXT,
content_updated_at TEXT,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
-- Rate limiting
failed_attempts INTEGER DEFAULT 0,
lockout_until TEXT,
last_failed_at TEXT
);
CREATE TABLE diffs (
id TEXT PRIMARY KEY,
profile_id TEXT NOT NULL,
encrypted_data TEXT NOT NULL,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (profile_id) REFERENCES profiles(id) ON DELETE CASCADE
);
CREATE TABLE stars (
id TEXT PRIMARY KEY,
profile_id TEXT NOT NULL,
encrypted_data TEXT NOT NULL,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (profile_id) REFERENCES profiles(id) ON DELETE CASCADE
);
Data Flow¶
Generating a Diff¶
sequenceDiagram
participant U as User
participant C as Client
participant F as /api/feeds
participant AI as Claude API
U->>C: Click "Generate"
C->>C: Resolve unmapped custom items (Haiku)
par Parallel fetch
C->>AI: Web search for profile topics (Sonnet)
C->>F: Fetch feeds (grouped by source)
end
AI-->>C: Web search results
F-->>C: HN, Lobsters, Reddit, GitHub, Dev.to items
C->>AI: Curate HN/Lobsters for relevance (Haiku)
AI-->>C: Filtered feed items
C->>C: Build prompt with web results + curated feeds
C->>AI: Generate diff (Sonnet)
AI-->>C: Markdown response
C->>C: Store markdown in history
C->>C: Render HTML client-side
C->>C: Track as modified
Note over C: Auto-sync if password cached
See AI Pipeline for details on web search, feed curation, and source resolution.
Client-Side Rendering
Diffs store only markdown content. HTML is rendered client-side on display using renderDiff(). This reduces storage by ~50% and keeps paragraph indices (data-p attributes) always in sync with the current renderer.
Profile Lifecycle¶
stateDiagram-v2
[*] --> Local: Create Profile
Local --> Shared: Share (set password)
Shared --> Synced: Import on another device
Synced --> Synced: Auto-sync changes
Local --> [*]: Delete
Shared --> [*]: Delete
Key Files¶
Entry & State¶
| File | Purpose |
|---|---|
src/app.ts |
Entry point — Alpine plugin setup, imports store and components |
src/store.ts |
Alpine store — profile/history/sync state management |
Components¶
| File | Purpose |
|---|---|
src/components/dashboard.ts |
Main dashboard — diff generation, display, starring |
src/components/profiles.ts |
Profile management — switch, import, share, sync |
src/components/setup.ts |
Setup wizard — profile creation/editing |
src/components/share-profile.ts |
Share profile page component |
src/components/index.ts |
Barrel export for all components |
Libraries¶
| File | Purpose |
|---|---|
src/lib/sync.ts |
Sync service — upload, download, change tracking |
src/lib/time.ts |
Time utilities — timeAgo(), daysSince(), formatDate() |
src/lib/api.ts |
Fetch utilities — fetchJson(), postJson(), ApiError |
src/lib/constants.ts |
Shared constants — LANGUAGES, FRAMEWORKS, DEPTHS, etc. |
src/lib/crypto.ts |
Encryption utilities — AES-GCM, PBKDF2 |
src/lib/prompt.ts |
Claude prompt construction |
src/lib/feeds.ts |
Feed fetching, AI source resolution, and curation |
src/lib/markdown.ts |
Client-side markdown rendering with data-p indices |
Styles¶
| File | Purpose |
|---|---|
styles.css |
All styles (dark theme) |