Files
jarvis/CLAUDE.md

154 lines
5.4 KiB
Markdown
Raw Permalink Normal View History

# 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.