Establishes the core contract: RSS → backend cache → API → frontend. Frontend must never fetch RSS. Updates project structure (all views now built), pill→tag mapping, key decisions, and adds reporting requirement. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5.4 KiB
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
Storyis the user-facing object. Never exposeClusterto UI.- Pipeline:
Article → Cluster → Story - Signal score is server-computed;
scoreBreakdownis 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()resetslastSyncedAtto bypass the 30s rate-limit on filter switch.sectionSupplementfetches 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:
- Executive summary — is the app usable, fast, serving cached news?
- Issues found — backend, frontend, cache, feed, ranking, data quality
- Fixes made — file, function, reason, result
- Measurements — before/after for response times, article counts, dead feeds
- Remaining risks — nothing hidden
- Next actions — highest-value improvements
- 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.