Skip to content

Public Diff Sharing

Individual diffs can be shared publicly via a URL, allowing anyone to view a specific diff without authentication.

Overview

Aspect Detail
URL Format /d?{diffId} (e.g., /d?abc123)
Requirement Profile must be synced
Storage Plaintext JSON (not encrypted)
Caching 24-hour CDN cache

How It Works

Storage Format

Public and private diffs are distinguished by their storage format in the database:

Public diff:  {"content": "...", "title": "...", "generated_at": "..."}  (JSON)
Private diff: U2FsdGVkX1...base64...  (encrypted blob)

The server detects public diffs by checking if encrypted_data starts with {.

Share Flow

sequenceDiagram
    participant U as User
    participant C as Client
    participant S as Server

    U->>C: Click eye icon → "Share publicly"
    C->>C: Set diff.isPublic = true
    C->>S: POST /api/profile/{id}/sync
    Note over C,S: Diff sent as plaintext JSON<br/>(not encrypted)
    S->>S: Store in diffs table
    S->>S: Purge CDN cache (if configured)
    C->>U: Show public URL

Unshare Flow

sequenceDiagram
    participant U as User
    participant C as Client
    participant S as Server

    U->>C: Click "Unshare"
    C->>C: Set diff.isPublic = false
    C->>S: POST /api/profile/{id}/sync
    Note over C,S: Diff sent as encrypted blob
    S->>S: Store encrypted version
    S->>S: Purge CDN cache
    C->>U: Show private status

UI

The share status is displayed with an eye icon in the diff header:

Icon State Description
👁 (open) Public Anyone with the URL can view
👁‍🗨 (closed) Private Only accessible via sync

Clicking the icon opens a dropdown:

  • Private diff: "Share publicly" button (requires active sync session)
  • Public diff: Shows URL, copy button, and "Unshare" button

Sync Required

The share dropdown only appears for synced profiles. Users must have an active sync password to modify share status.

API

GET /api/diff/{id}/public

Retrieve a public diff. Returns 404 for private diffs.

Response (200):

{
  "id": "abc123",
  "content": "# Your Dev Digest\n\n...",
  "title": "Weekly Update",
  "generated_at": "2026-01-28T10:00:00Z",
  "profile_name": "Chris"
}

Response (404):

{ "error": "Diff not found" }

Cache Headers:

Cache-Control: public, max-age=86400
Cache-Tag: diff-{id}

Cache Invalidation

When a diff is modified or unshared, the server purges the CDN cache using Cloudflare's Cache Tag API. Requires CF_ZONE_ID and CF_API_TOKEN environment variables.

Client Implementation

Store Methods

// Mark diff as public (triggers sync)
shareDiff(diffId: string): boolean

// Mark diff as private (triggers sync)
unshareDiff(diffId: string): boolean

// Check if diff is public
isDiffPublic(diffId: string): boolean

// Get shareable URL
getPublicDiffUrl(diffId: string): string  // → "https://difflog.app/d?{id}"

Sync Serialization

During sync, diffs are serialized based on their isPublic flag:

if (diff.isPublic) {
  // Store as plaintext JSON
  const { isPublic, ...diffData } = diff;
  encrypted_data = JSON.stringify(diffData);
} else {
  // Store as encrypted blob
  encrypted_data = await encryptData(diff, password, salt);
}

On download, the format is detected and isPublic is restored:

if (encrypted_data.startsWith('{')) {
  diff = JSON.parse(encrypted_data);
  diff.isPublic = true;
} else {
  diff = await decryptData(encrypted_data, password, salt);
  diff.isPublic = false;
}

Security Considerations

Public Content

Public diffs are not encrypted. The full diff content (markdown) is stored in plaintext and accessible to anyone with the URL.

  • Diff IDs are UUIDs — not guessable, but not secret if shared
  • Profile name is included in the public response
  • API keys and other profile data remain encrypted
  • Stars (bookmarks) are always encrypted