Skip to content

Latest commit

 

History

History
173 lines (131 loc) · 6.14 KB

File metadata and controls

173 lines (131 loc) · 6.14 KB

Xdigest -- Design Spec

What it is

A macOS menu bar app that filters your X (Twitter) For You feed against your bookmarks, surfaces high-signal posts, and serves them in a local reader accessible from any device on the network.

Dependency

  • Claude Code (user must have it installed with active subscription)

Architecture

Data-centric, layered, Unix-style pipeline. Each module does one thing. Data flows one direction.

BirdService -> ScorerService -> DigestService -> ServerService -> Browser / WKWebView
     |               |               |               |
  [Tweet]      [ScoredPost]      [Digest]        [HTML]

Data Layer (no dependencies)

Shared types used across all modules. Plain structs, Codable.

  • Tweet -- id, author, text, createdAt, media, avatarUrl, isRepost, originalAuthor, originalId
  • Bookmark -- same as Tweet (bookmarks are tweets)
  • ScoredPost -- tweet, score, reason, tags
  • Digest -- date, sections (each section has a timestamp and an array of ScoredPosts)

Service Layer (each does one thing)

BirdService -- fetches data from X

  • Input: command (bookmarks, home, following)
  • Output: [Tweet]
  • Wraps the bird CLI binary (bundled with the app)
  • Handles --json-full parsing, tweet normalization (full text, avatars, URL entities, repost detection)
  • Caches bookmarks and following locally

ScorerService -- scores tweets against bookmarks

  • Input: [Bookmark], [Tweet]
  • Output: [ScoredPost]
  • Wraps claude -p --model opus --output-format json --json-schema
  • Returns score, reason, and tags per post
  • Dedup: filters out tweets already seen (by tweet ID and original ID for reposts)

DigestService -- assembles a digest from scored posts

  • Input: [ScoredPost]
  • Output: Digest (structured data)
  • Handles section grouping by timestamp
  • Handles repost dedup (if original already shown, skip the repost)

ServerService -- serves the reader

  • Input: Digest
  • Output: HTTP server on a local port
  • Serves the HTML reader (embedded in the app)
  • API endpoints: /, /api/digest, /api/mtime, /api/generate, /proxy
  • Video proxy with range request support

Orchestration Layer

DigestPipeline -- wires fetch -> score -> assemble

  • Coordinates BirdService, ScorerService, DigestService
  • Manages the seen cache
  • Called by the app when user clicks "Generate Digest"

App Layer (thin shell)

  • Menu bar icon with: Generate Digest, Open Reader, Quit
  • WKWebView window pointing to localhost (the native reader)
  • On launch: start ServerService, show menu bar icon
  • Optional: show QR code for iPhone/iPad to connect

Reader Features (HTML)

  • Typora Night theme (dark, #363B40 background)
  • Foldable timestamp sections
  • Repost indicator (original author shown, retweeter credited above)
  • Search: keyword + tag matching (tags generated by Claude at scoring time)
  • Floating action buttons: search, home, generate
  • Video proxy for X media playback
  • Hot reload: polls for new posts, inserts at top without page reload
  • Responsive: compact padding on iPhone, full on desktop

Testing Strategy

TDD for data and service layers. No mocks. Use real JSON fixtures from bird.

  • BirdService: feed it a real bird JSON fixture, verify normalized Tweet output
  • ScorerService: feed it bookmarks + tweets, verify ScoredPost output has scores and tags
  • DigestService: feed it ScoredPosts, verify Digest sections are correct, reposts deduped
  • ServerService: start real server, make real HTTP requests, verify responses
  • Integration: run the full pipeline with fixtures, verify end-to-end output

Module Structure

Xdigest/
  Package.swift
  Sources/
    XdigestApp/           -- App layer (menu bar, WKWebView)
    XdigestCore/          -- Data layer (shared types)
    BirdService/          -- Fetch tweets
    ScorerService/        -- Score with Claude
    DigestService/        -- Assemble digest
    ServerService/        -- HTTP server + reader HTML
  Tests/
    BirdServiceTests/     -- Real fixtures, no mocks
    ScorerServiceTests/
    DigestServiceTests/
    ServerServiceTests/
    IntegrationTests/     -- Full pipeline
  Fixtures/
    bookmarks.json        -- Real bird output for testing
    home.json
    scored.json

Error Design

Inspired by universalbuild -- typed errors, pipeline halts on first failure, error tells you the stage and the cause.

Error Type

enum XdigestError: Error, CustomStringConvertible {
    // BirdService
    case birdNotFound
    case birdFetchFailed(command: String, stderr: String)
    case birdOutputInvalid(command: String, detail: String)

    // ScorerService
    case claudeNotFound
    case claudeScoringFailed(exitCode: Int, stderr: String)
    case claudeOutputInvalid(detail: String)

    // DigestService
    case digestAssemblyFailed(reason: String)

    // ServerService
    case serverPortInUse(port: Int)
    case serverStartFailed(port: Int, reason: String)
}

Each case names the stage (Bird, Claude, Digest, Server) and carries the diagnostic data needed to locate the problem.

Pipeline Composition

The pipeline chains async throwing functions. First failure stops everything:

let tweets = try await birdService.fetchHome(count: 100)
let bookmarks = try await birdService.fetchBookmarks()
let scored = try await scorerService.score(tweets, against: bookmarks)
let digest = try digestService.assemble(scored)
try await serverService.serve(digest)

birdNotFound -> user knows to install bird. claudeScoringFailed(exitCode: 1, stderr: "...") -> user sees exactly what Claude reported. serverPortInUse(port: 8408) -> user knows another instance is running.

Error Surfacing

Errors are shown in the menu bar dropdown or as a macOS notification. The full error (stage + detail) is logged. The user always knows what went wrong and where.

Unix Principles Applied

  1. Each module does one thing well
  2. Data (JSON/structs) is the universal interface between modules
  3. Pipeline composition: the output of one module is the input of the next
  4. Text streams: bird outputs JSON, Claude outputs JSON, the server outputs HTML
  5. Separation of mechanism and policy: services are mechanism, the pipeline is policy