Skip to content

Architecture Overview

diff·log is a SvelteKit SPA with optional cloud sync capabilities.

Tech Stack

Layer Technology
Frontend SvelteKit with Svelte 5 runes
Styling Global CSS (app.css) + scoped component styles
Build Vite via SvelteKit (Cloudflare adapter)
Backend SvelteKit API routes on Cloudflare Pages
Database Cloudflare D1 (SQLite)
AI Multi-provider (Anthropic, DeepSeek, Gemini, Perplexity, Serper — see AI Pipeline)

Client Architecture

Module Structure

src/
├── routes/                    # SvelteKit file-based routing
│   ├── +layout.svelte         # Root layout (CSS, View Transitions)
│   ├── +page.svelte           # Dashboard
│   ├── about/                 # Landing, privacy, terms
│   ├── setup/                 # Profile creation/editing wizard
│   ├── profiles/              # Profile management, sync, sharing
│   ├── archive/               # Past diffs list
│   ├── stars/                 # Bookmarked paragraphs
│   ├── generate/              # Diff generation page
│   ├── d/[id]/                # Public diff view
│   ├── design/                # Design system (dev only)
│   └── api/                   # Server endpoints
├── lib/
│   ├── stores/                # Svelte 5 state management
│   │   ├── profiles.svelte.ts # Profile CRUD
│   │   ├── history.svelte.ts  # Diff history, streaks
│   │   ├── stars.svelte.ts    # Bookmarks
│   │   ├── sync.svelte.ts     # Cloud sync state
│   │   ├── ui.svelte.ts       # Transient UI state
│   │   ├── operations.svelte.ts # Cross-domain composite operations
│   │   └── persist.svelte.ts  # localStorage/sessionStorage helpers
│   ├── components/            # Reusable Svelte components
│   ├── utils/                 # Pure utility functions
│   │   ├── providers.ts       # AI provider configuration
│   │   ├── llm.ts             # Multi-provider LLM abstraction
│   │   ├── search.ts          # Web search providers
│   │   ├── feeds.ts           # Feed fetching and curation
│   │   ├── prompt.ts          # Prompt construction
│   │   ├── sync.ts            # Sync utilities, types
│   │   ├── crypto.ts          # Client-side encryption
│   │   ├── markdown.ts        # Markdown rendering
│   │   └── ...                # api, constants, time, pricing, etc.
│   └── actions/               # Svelte actions
│       ├── clickOutside.ts    # Click-outside detection
│       └── generateDiff.ts    # Diff generation orchestration
└── app.css                    # Global styles and design system

Store Architecture

Domain-driven store modules using Svelte 5 $state() runes. State is exposed via accessor objects with get/set value(). Derived state uses functions. Actions are plain functions that mutate state.

// Reading state
const profile = getProfile();
const history = getHistory();

// Mutating state
addDiff(entry);
updateProfile({ name: 'New Name' });

localStorage keys: difflog-profiles, difflog-histories, difflog-bookmarks, difflog-active-profile, difflog-pending-sync.

Server Architecture

SvelteKit API Routes

API endpoints are +server.ts files under src/routes/api/:

src/routes/api/
├── feeds/+server.ts              # Fetch developer news feeds
├── diff/[id]/public/+server.ts   # Public diff view
├── profile/
│   ├── create/+server.ts         # Create/update profile
│   ├── [id]/+server.ts           # Get/update/delete profile
│   └── [id]/
│       ├── content/+server.ts    # Download encrypted content
│       ├── password/+server.ts   # Update password
│       ├── status/+server.ts     # Sync status check
│       └── sync/+server.ts       # Upload content
├── share/[id]/+server.ts         # Public profile info
├── auth.ts                       # Shared auth helpers
└── 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,
  keys_hash TEXT,
  password_salt 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 AI Providers

    U->>C: Click "Generate"
    C->>C: Resolve unmapped custom items (curation provider)
    par Parallel fetch
        C->>AI: Web search for profile topics (search provider)
        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 (curation provider)
    AI-->>C: Filtered feed items
    C->>C: Build prompt with web results + curated feeds
    C->>AI: Generate diff (synthesis provider)
    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