User problem
Many RSS readers rely on cloud sync or lack offline support. Signal Desk keeps your feeds and articles local, encrypted behind your system, without mandatory accounts or network dependency.
Desktop RSS Reader
Signal Desk is a minimal viable product that fetches RSS 2.0 and Atom feeds, caches them offline, and provides a distraction-free reading pane. The app keeps networking, XML parsing, and file I/O in the main process behind a typed preload bridge so the renderer stays unprivileged.
Signal Desk is a minimal, local-first RSS desktop app that prioritizes security, offline resilience, and clean architecture over feature breadth.
Many RSS readers rely on cloud sync or lack offline support. Signal Desk keeps your feeds and articles local, encrypted behind your system, without mandatory accounts or network dependency.
Developers, researchers, and knowledge workers who prefer desktop tools, value privacy, and want to read feeds without tracking or interruptions.
Fetch and parse RSS 2.0 and Atom feeds via HTTP validators (ETag/Last-Modified), cache articles locally, display them in a clean reader, mark as read, and export/import archives.
Signal Desk v1.0.0 ships as a desktop-only MVP. The app is production-ready for core workflows but does not yet include folders, advanced search, AI organization, or cloud sync.
Signal Desk follows Electron's three-process model with explicit boundaries. The renderer is fully unprivileged; the main process owns all I/O, networking, and XML parsing; and a typed preload bridge is the only bridge.
Renderer (React UI)
└─> window.rss (typed preload bridge via contextBridge)
└─> Main Process
├─ IPC handlers: rss:fetchFeeds, rss:addFeed, rss:toggleRead, etc.
├─ HTTP fetching with ETag/Last-Modified validators
├─ XML parsing (fast-xml-parser)
├─ Cache file I/O (<userData>/signal-desk/feeds.json)
├─ External URL handling (shell.openExternal)
└─ Background auto-refresh (20-minute timer)
Owns all privileged work: networking via net.fetch, cache file I/O, RSS/Atom XML parsing, and opening external URLs. Registers guarded IPC handlers and runs a background refresh timer. Denies external navigation and new windows in the renderer.
Exposes a single typed API, window.rss, via contextBridge (type RssApi in shared/types.ts). No raw IPC, Node, or filesystem access reaches the renderer. Each method is a guarded IPC call.
An unprivileged React 18 app that calls only window.rss methods. Displays feed list, article list, and reader pane. Runs with contextIsolation: true, sandbox: true, and nodeIntegration: false.
Domain logic: fetch with HTTP validators, parse RSS 2.0 and Atom, strip HTML to safe plain text, deduplicate articles with stable SHA-256 ids, cap cache at 300 articles per feed, preserve read state, and handle atomic writes and migrations.
A versioned JSON file at <userData>/signal-desk/feeds.json stores all feeds and articles. Versioning ensures safe schema migrations. Atomic writes prevent partial data loss on failure. Export archives use the same versioned format.
Context isolation and sandbox prevent renderer escape. Feed HTML is stripped to plain text, never rendered with dangerouslySetInnerHTML. Feed URLs must be HTTP(S) without embedded credentials. HTTP connections require explicit user confirmation.
Each feature is built around offline-first resilience and security. Articles are cached before display, feeds fail gracefully, and the reader respects user read state.
HTTP GET with ETag and Last-Modified validators; servers return 304 Not Modified if content is unchanged. Avoids redundant downloads and respects server bandwidth.
fast-xml-parser parses both formats into a unified article schema. Malformed feeds are handled gracefully; valid articles are cached even if parsing fails partway.
Articles persist in a versioned JSON cache. A failed refresh never drops what you've already fetched. The cache survives app restarts and network outages.
Mark articles as read per feed or globally. Read state is persisted and survives refresh. Unread count is derived from cache state for accuracy.
Export all feeds and articles to a versioned JSON file via native save dialog. Import from a saved archive via native open dialog. Preserves feed metadata and read state.
Web Speech API provides native TTS for the selected article. Controls for play, pause, and stop. No network call or cloud service required.
Minimal dependencies chosen for stability, security, and native desktop support. No cloud services or network dependencies beyond public RSS feeds.
net.fetch, file I/O, shell.openExternalSignal Desk enforces hard boundaries between privileged and unprivileged code. These constraints ensure the app remains resilient to renderer escape and feed injection attacks.
contextIsolation: true)sandbox: true)nodeIntegration: false)dangerouslySetInnerHTMLshell.openExternal, not in-apprss:* IPC methods are registeredThe project uses deterministic tests and CLI checks to prevent regressions. All verification commands must pass before shipping a release.
npm start # Launch dev app npm run lint # ESLint on .ts, .tsx npm run typecheck # tsc --noEmit npm test # vitest run npm run package # Package without installers npm run make # Build installers
Distribution: npm run make builds native installers via Electron Forge — Squirrel (Windows), ZIP (macOS), and .deb/.rpm (Linux). The app ships only as a desktop binary, never as a web deployment.
The roadmap expands Signal Desk from a basic feed list into a full reading workstation. Each item builds on the existing architecture and versioned-migration pattern.