Skip to content

Latest commit

 

History

History
336 lines (268 loc) · 14.1 KB

File metadata and controls

336 lines (268 loc) · 14.1 KB

CLAUDE.md — TechPulse Project

RSS Tech News Aggregator with AI Classification — Electron Desktop Widget


Project Overview

TechPulse is a desktop widget (Electron) that aggregates RSS feeds from tech news sites, classifies each article into categories using AI (Claude API or any local OpenAI-compatible runtime), and displays them in a compact, always-on-top interface.

Target machine: Intel Core Ultra 5 238V (Lunar Lake), Intel AI Boost NPU, Intel Arc 130V GPU, Windows 11.


Model Strategy

Use OpusPlan mode as default (/model opusplan). This automatically routes:

  • Opus → planning, architecture decisions, reasoning
  • Sonnet → code generation, implementation, edits

Task-by-Model Assignment

Below is the explicit assignment for each project task. When working on a specific task, switch to the recommended model if not already using OpusPlan.

OPUS — Complex reasoning, architecture, prompt engineering

Task Why Opus
AI classification prompt design & iteration Prompt engineering requires deep reasoning about edge cases, category boundaries, multilingual titles
AI Provider orchestrator logic Complex fallback chains, error handling, auto-detection of runtimes — needs careful state management
IPC bridge architecture Security-sensitive: context isolation, preload scripts, type-safe channel definitions
SQLite schema design & migrations Data modeling with deduplication constraints, indexes, cross-feed normalization
Electron window management Widget mode (alwaysOnTop, frameless, tray), window state persistence, edge cases on Windows
Debugging complex issues Multi-process debugging (Main ↔ Renderer), race conditions in RSS refresh + AI classification pipeline
Security review API key storage, CSP headers, contextIsolation, no nodeIntegration in renderer
Performance optimization Batch sizing, virtual scrolling, SQLite query optimization, memory leaks in long-running widget
Architecture decisions & trade-offs When facing a "how should we..." question, always use Opus

SONNET — Implementation, UI, boilerplate, tests

Task Why Sonnet
Project scaffolding electron-vite setup, package.json, tsconfig, vite.config — well-known patterns
React components (UI) CategoryTabs, ArticleList, ArticleCard, FeedManager, SettingsPanel, StatusBar — standard React + Tailwind
CSS / Tailwind styling Visual styling, dark/light theme, animations, responsive widget layout
RSS Service implementation rss-parser integration, fetch + parse — straightforward async code
Claude Provider implementation Anthropic SDK call — simple API wrapper
Local Provider implementation fetch() to OpenAI-compatible endpoint — REST client pattern
Database service (CRUD) better-sqlite3 queries, insert/update/delete — routine DB operations
Scheduler service node-cron setup — minimal logic
IPC handler implementations Once bridge is designed (Opus), implementing each handler is mechanical
Feed defaults & presets Static data: RSS URLs, local runtime presets — config files
Electron-builder config YAML config for packaging — known pattern
Unit tests Jest/Vitest test files — pattern-based generation
Documentation & README Markdown generation from known context
Bug fixes (simple) Typos, missing imports, straightforward logic errors
Refactoring Extract function, rename, move file — mechanical transformations

Decision Flowchart

New task arrives
    │
    ├── "How should we design/architect X?" ──→ OPUS
    ├── "Why is X broken / not working?"
    │   ├── Simple (missing import, typo) ──→ SONNET
    │   └── Complex (race condition, state) ──→ OPUS
    ├── "Implement X component/service" ──→ SONNET
    ├── "Write the prompt for AI classification" ──→ OPUS
    ├── "Add a button / style / animation" ──→ SONNET
    ├── "Review security of X" ──→ OPUS
    └── Default ──→ SONNET (90% of tasks)

Tech Stack

  • Runtime: Electron 33+ (with Vite)
  • Frontend: React 18 + TypeScript + Tailwind CSS
  • Build: electron-vite (Vite for both main & renderer)
  • RSS: rss-parser (npm)
  • AI Cloud: @anthropic-ai/sdk (Claude Sonnet API)
  • AI Local: Any OpenAI-compatible REST endpoint (Ollama, OpenVINO OVMS, LM Studio, GAIA, Foundry Local, llama.cpp)
  • Database: better-sqlite3
  • Scheduler: node-cron
  • Packaging: electron-builder

Project Structure

techpulse/
├── CLAUDE.md                          ← You are here
├── package.json
├── vite.config.ts
├── electron-builder.yml
├── src/
│   ├── main/                          # Electron Main Process
│   │   ├── index.ts                   # App entry, window creation, tray
│   │   ├── ipc-handlers.ts            # IPC bridge definitions
│   │   ├── services/
│   │   │   ├── rss.service.ts         # Fetch + parse RSS feeds
│   │   │   ├── ai.service.ts          # AI orchestrator (provider switching, fallback)
│   │   │   ├── claude.provider.ts     # Anthropic API provider
│   │   │   ├── local.provider.ts      # Unified local provider (OpenAI-compat)
│   │   │   ├── scheduler.service.ts   # Cron-based refresh
│   │   │   └── db.service.ts          # SQLite operations
│   │   └── utils/
│   │       ├── dedup.ts               # Article deduplication (URL normalization)
│   │       ├── feed-defaults.ts       # Default RSS feed list
│   │       └── local-presets.ts       # Runtime presets (Ollama, OVMS, LM Studio...)
│   ├── renderer/                      # React Frontend
│   │   ├── App.tsx
│   │   ├── index.html
│   │   ├── components/
│   │   │   ├── CategoryTabs.tsx
│   │   │   ├── ArticleList.tsx
│   │   │   ├── ArticleCard.tsx
│   │   │   ├── FeedManager.tsx
│   │   │   ├── SettingsPanel.tsx
│   │   │   ├── StatusBar.tsx
│   │   │   └── TitleBar.tsx           # Custom frameless titlebar
│   │   ├── hooks/
│   │   │   ├── useArticles.ts
│   │   │   ├── useFeeds.ts
│   │   │   └── useSettings.ts
│   │   └── styles/
│   │       └── globals.css
│   ├── shared/                        # Types shared Main ↔ Renderer
│   │   ├── types.ts
│   │   └── categories.ts
│   └── preload/
│       └── index.ts                   # Secure context bridge
└── resources/
    ├── icon.png
    └── tray-icon.png

Database Schema (SQLite)

4 tables: feeds, articles, categories, settings.

Key constraints:

  • articles has UNIQUE(feed_id, guid) for per-feed deduplication
  • articles.category stores the slug from categories.slug
  • articles.ai_provider tracks which provider classified ('claude' | 'local')
  • settings is key-value with these critical keys:
    • ai_provider: 'claude' | 'local'
    • local_preset: 'ollama' | 'openvino' | 'lmstudio' | 'gaia' | 'foundry' | 'llamacpp' | 'custom'
    • local_api_url: URL of local runtime
    • local_api_format: 'ollama' | 'openai'
    • local_model_name: model identifier on the runtime

AI Classification

Provider Architecture

Two providers, one interface:

interface AIProvider {
  name: 'claude' | 'local';
  classify(articles: { id: number; title: string; description?: string }[]): Promise<ClassificationResult[]>;
  isAvailable(): Promise<boolean>;
}
  • ClaudeProvider: Uses @anthropic-ai/sdk, calls Claude Sonnet
  • LocalProvider: Unified REST client, speaks both Ollama native API and OpenAI-compatible API
    • Has presets for: Ollama (:11434), OpenVINO OVMS (:8000), LM Studio (:1234), GAIA (:8899), Foundry (:5272), llama.cpp (:8080)
    • User can also set a custom endpoint

Auto-fallback

If primary provider fails → try the other. Log the fallback. Never silently fail.

Classification Categories (10)

ai-ml, hardware, os, security, software, cloud, mobile, gaming, business, science

Batch Processing

Default batch size: 25 articles per API call. Classify only unclassified articles (WHERE category IS NULL).


Electron Configuration

Window

  • width: 420, height: 680 (default widget size)
  • frame: false (custom titlebar)
  • alwaysOnTop: true (widget mode)
  • resizable: true (min 320x400, max 600xscreen)
  • contextIsolation: true, nodeIntegration: false

Tray

  • Context menu: Show/Hide, Refresh, Settings, Quit
  • Tooltip: article count summary

Security

  • All Main ↔ Renderer communication via contextBridge.exposeInMainWorld
  • No remote module
  • API keys stored in SQLite (local file), never in renderer process
  • CSP headers in index.html

Coding Conventions

TypeScript

  • Strict mode enabled
  • No any — use proper types from src/shared/types.ts
  • Async/await everywhere (no raw .then())
  • Error handling: try/catch with typed errors, never swallow errors silently

React

  • Functional components only, with hooks
  • Tailwind for all styling (no separate CSS modules)
  • No inline styles except dynamic values (opacity, colors from DB)

Naming

  • Files: kebab-case for utils, PascalCase for components (ArticleCard.tsx, dedup.ts)
  • Variables/functions: camelCase
  • Types/Interfaces: PascalCase, prefixed with 'I' only for interfaces if ambiguous
  • DB columns: snake_case
  • IPC channels: domain:action format (articles:get-by-category, feeds:add)

Git

  • Conventional commits: feat:, fix:, refactor:, docs:, chore:
  • One feature per commit
  • No large binary files (use .gitignore for node_modules, dist, *.db)

Development Phases

Phase 1 — MVP (start here)

  1. Scaffold Electron + Vite + React + Tailwind project → Sonnet
  2. Design & create SQLite schema (feeds, articles, categories, settings) → Opus then Sonnet
  3. Implement db.service.ts (all CRUD operations) → Sonnet
  4. Implement rss.service.ts (fetch + parse with rss-parser) → Sonnet
  5. Design AI classification prompt → Opus
  6. Implement claude.provider.ts → Sonnet
  7. Implement ai.service.ts (orchestrator) → Opus (fallback logic)
  8. Implement IPC handlers → Opus (design) then Sonnet (implementation)
  9. Build preload/index.ts (context bridge) → Sonnet
  10. Build UI: TitleBar, CategoryTabs, ArticleList, ArticleCard, StatusBar → Sonnet
  11. Wire up UI hooks (useArticles, useFeeds, useSettings) → Sonnet
  12. Test end-to-end: fetch RSS → classify → display → click opens browser → Opus (debug)

Phase 2 — Local AI & Feed Management

  1. Implement local.provider.ts (unified, with presets) → Opus (design) then Sonnet (code)
  2. Build SettingsPanel with provider toggle, presets, test connection → Sonnet
  3. Build FeedManager (add/remove/toggle feeds) → Sonnet
  4. Implement scheduler.service.ts (node-cron auto-refresh) → Sonnet
  5. Implement dedup.ts (cross-feed URL normalization) → Sonnet
  6. Add Tray icon + context menu → Sonnet
  7. Dark/light/system theme support → Sonnet

Phase 3 — NPU & Polish

  1. Test & optimize OpenVINO OVMS on Intel NPU → Opus (troubleshooting)
  2. Auto-detect running local runtimes at startup → Opus (logic) then Sonnet (code)
  3. Add animations & transitions → Sonnet
  4. Desktop notifications for new articles → Sonnet
  5. Keyboard shortcuts → Sonnet
  6. OPML import/export → Sonnet
  7. electron-builder packaging config → Sonnet
  8. Security review of entire app → Opus

Default RSS Feeds

Ars Technica        https://feeds.arstechnica.com/arstechnica/index
The Verge           https://www.theverge.com/rss/index.xml
TechCrunch          https://techcrunch.com/feed/
Hacker News         https://hnrss.org/frontpage
Tom's Hardware      https://www.tomshardware.com/feeds/all
Bleeping Computer   https://www.bleepingcomputer.com/feed/
The Register        https://www.theregister.com/headlines.atom
9to5Mac             https://9to5mac.com/feed/
Android Authority   https://www.androidauthority.com/feed/
Phoronix            https://www.phoronix.com/rss.php
VentureBeat         https://venturebeat.com/feed/
Wired               https://www.wired.com/feed/rss
MIT Tech Review     https://www.technologyreview.com/feed/
KrebsOnSecurity     https://krebsonsecurity.com/feed/

Local AI Runtime Presets

const LOCAL_PRESETS = {
  ollama:   { url: 'http://localhost:11434', format: 'ollama',  model: 'mistral'                        },
  openvino: { url: 'http://localhost:8000',  format: 'openai',  model: 'OpenVINO/Phi-3.5-mini-instruct' },
  lmstudio: { url: 'http://localhost:1234',  format: 'openai',  model: 'loaded-model'                   },
  gaia:     { url: 'http://localhost:8899',  format: 'openai',  model: 'llama3'                         },
  foundry:  { url: 'http://localhost:5272',  format: 'openai',  model: 'phi-3.5-mini'                   },
  llamacpp: { url: 'http://localhost:8080',  format: 'openai',  model: 'default'                        },
};

Quick Commands

# Development
npm run dev              # Start Electron + Vite HMR

# Build & Package
npm run build            # Compile TypeScript + bundle
npm run package          # electron-builder → installer

# Database
# Schema auto-created on first run by db.service.ts

Important Notes

  • Language: Code and comments in English. UI text in English (i18n later).
  • No overengineering: Start simple, iterate. No Redux, no Prisma — just React hooks + better-sqlite3.
  • Widget first: The app must feel light. No heavy frameworks. Fast startup.
  • Offline-capable: Cache articles in SQLite. If no AI provider available, show articles without categories.
  • Full architecture document: See architecture-rss-aggregator.md for detailed schemas, wireframes, pipeline diagrams, and cost analysis.