# Jarvis iOS — Claude Code ## What this is Jarvis is an iOS app (SwiftUI, SwiftData, iOS 17+) that displays ranked news stories served from a self-hosted backend. The backend ingests RSS, clusters articles into stories, ranks them by signal score, and serves cached results via REST + WebSocket. The app is a **cached news intelligence client**. It is not an RSS reader. --- ## Architecture rule ``` RSS feeds → backend ingestion → cache/database → API → frontend ``` **The frontend must never fetch RSS feeds directly.** **The frontend must never parse, deduplicate, or rank articles.** **The frontend consumes cached backend data only.** --- ## Backend responsibilities (not the app's job) - RSS fetching, validation, timeout handling - Feed health checks and dead-feed detection - Article normalization and deduplication - Story clustering and ranking - Freshness, interestingness, and signal scoring - API response caching and last-known-good fallback The app trusts these to be done. It does not replicate them. --- ## Frontend responsibilities - Load cached stories immediately on open (SwiftData) - Display stories with freshness indicators and source attribution - Show connection/refresh state without blocking the UI - Show stale-cache label when backend data is old - Paginate via cursor (`nextCursor`) - Never show an empty state when the cache has data **Performance targets:** - App opens with cached news: instant - Home feed API response: < 1 second - Refresh response: < 2 seconds - Story open: < 500ms - Background refresh does not block UI If any of these fail, treat it as a bug. --- ## Frontend validation checklist Before marking any feed/story feature complete, verify: - [ ] Cached news loads immediately without waiting for RSS - [ ] Refresh does not freeze the UI - [ ] Empty state does not appear when cache has data - [ ] Stale cache is labeled (age visible) - [ ] Fresh stories rise above old ones - [ ] Categories are balanced across the feed - [ ] Source attribution is correct and links work - [ ] Offline state shows last-known-good content --- ## Project structure ``` Jarvis/ Models/Models.swift — Codable structs + SwiftData models Networking/APIClient.swift — REST layer (actor-based) Networking/WebSocketManager.swift — WS + reconnect backoff Store/StoryStore.swift — central ObservableObject, drives all views Store/StoryStore+Supplement.swift — background section supplement fetches Store/ServerSettings.swift — persists host to UserDefaults Store/CacheMaintenance.swift — SwiftData eviction Views/ Home/SignalFeedView.swift — main feed (All digest, pill pills, sub-sections) Home/StoryRowView.swift — story card Reader/ArticleReaderView.swift — full article, cached badge Story/StoryDetailView.swift — cluster detail + timeline Feeds/FeedManagerView.swift — feed list + health states Feeds/AddFeedSheet.swift Settings/SettingsView.swift Settings/NotificationsView.swift Shared/Theme.swift — StoryPill enum, NewsSection, design tokens Shared/SignalStripe.swift Shared/SourceChips.swift Shared/CachedBadge.swift Shared/ConnectionBanner.swift Onboarding/OnboardingView.swift JarvisApp.swift API/contract.md — REST + WebSocket API spec ``` --- ## Design tokens - Accent: `KisaniOrange` = `#FF5C00` - Background: `#000000` (pure black) - Surface: `#111111` / `#1A1A1A` - All views: `.preferredColorScheme(.dark)` unless user overrides in settings --- ## Key decisions - `Story` is the user-facing object. Never expose `Cluster` to UI. - Pipeline: `Article → Cluster → Story` - Signal score is server-computed; `scoreBreakdown` is exposed for display. - WebSocket pushes `{storyId, score}` only; app re-fetches the full object via REST. - WS reconnect: exponential backoff 1s → 2s → 4s → max 60s. - Pagination: cursor-based via `nextCursor`. - `StoryStore.setPill()` resets `lastSyncedAt` to bypass the 30s rate-limit on filter switch. - `sectionSupplement` fetches populate sparse regional sections in the All digest independently. --- ## Pill → tag mapping | Pill | Backend tags queried | |---|---| | All | — (no filter, full ranked feed) | | F1 | `formula-1` | | Sport | `sports`, `esports` | | Tech | `technology`, `artificial-intelligence`, `machine-learning`, `cloud-computing`, `homelab`, `cybersecurity`, `security`, `programming-and-software-development`, `privacy-and-data-protection`, `web-design-and-ui-ux`, `wordpress-and-web-development`, `robotics` | | East Africa | `east-africa`, `uganda` (Uganda sub-section pinned first) | | Southern Africa | `south-africa`, `namibia`, `botswana`, `zimbabwe`, `zambia`, `mozambique`, `southern-africa` | | Canada | `canada` | | US | `united-states` | --- ## Reporting requirement At the end of any significant work session, generate a report covering: 1. **Executive summary** — is the app usable, fast, serving cached news? 2. **Issues found** — backend, frontend, cache, feed, ranking, data quality 3. **Fixes made** — file, function, reason, result 4. **Measurements** — before/after for response times, article counts, dead feeds 5. **Remaining risks** — nothing hidden 6. **Next actions** — highest-value improvements 7. **Status** — one of: `NOT READY` / `PARTIALLY READY` / `READY FOR TESTING` / `READY FOR RELEASE` Never claim READY FOR RELEASE without evidence from logs, API responses, or timing measurements.