docs: rewrite CLAUDE.md with architecture rules and current project state
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>
This commit is contained in:
188
CLAUDE.md
188
CLAUDE.md
@@ -1,53 +1,153 @@
|
||||
# Jarvis — Claude Code Handoff
|
||||
# Jarvis iOS — Claude Code
|
||||
|
||||
## What this is
|
||||
Jarvis is an iOS app (SwiftUI + SwiftData) that connects to a self-hosted RSS
|
||||
correlation platform. It displays news stories ranked by signal score, caches
|
||||
full article content for offline reading, and receives live updates over WebSocket.
|
||||
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.
|
||||
|
||||
## Architecture
|
||||
- REST on launch → WebSocket for live updates
|
||||
- Server computes signal scores — app only renders them
|
||||
- Full offline support via SwiftData cache
|
||||
- No auth — local network / self-hosted only
|
||||
The app is a **cached news intelligence client**. It is not an RSS reader.
|
||||
|
||||
## Files produced so far
|
||||
- `API/contract.md` — full REST + WebSocket API spec
|
||||
- `Jarvis/Models/Models.swift` — all Codable structs + SwiftData models
|
||||
- `Jarvis/Networking/APIClient.swift` — REST layer (actor-based)
|
||||
- `Jarvis/Networking/WebSocketManager.swift` — WS connection + reconnect backoff
|
||||
- `Jarvis/Store/StoryStore.swift` — central ObservableObject, drives all views
|
||||
- `Jarvis/Store/ServerSettings.swift` — persists server host to UserDefaults
|
||||
- `Jarvis/JarvisApp.swift` — app entry point, SwiftData container
|
||||
- `Jarvis/Views/RootTabView.swift` — tab navigation
|
||||
- `Jarvis/Views/Onboarding/OnboardingView.swift` — server setup screen
|
||||
---
|
||||
|
||||
## Views still needed
|
||||
- `SignalFeedView` — home screen, story list sorted by signal score
|
||||
- `StoryDetailView` — cluster detail, timeline, consensus/conflict
|
||||
- `ArticleReaderView` — full article, cached badge, more from cluster
|
||||
- `FeedManagerView` — feed list, health states, add/delete
|
||||
- `LatestView`, `SavedView`, `SearchView` — secondary tabs
|
||||
## 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
|
||||
- Primary accent: `KisaniOrange` = #FF5C00 (add to Assets.xcassets)
|
||||
- Background: pure black #000000
|
||||
- Surface: #111111, #1A1A1A
|
||||
- All views: `.preferredColorScheme(.dark)`
|
||||
|
||||
## Key decisions already made
|
||||
- Story = user-facing product object (never expose Cluster to UI)
|
||||
- Pipeline: Article → Cluster → Story
|
||||
- Signal score is server-computed, breakdown exposed as `scoreBreakdown`
|
||||
- WebSocket pushes lightweight events (storyId + score only), app refetches full object via REST
|
||||
- WS reconnect: exponential backoff 1s → 2s → 4s → max 60s
|
||||
- Pagination: cursor-based via `nextCursor`
|
||||
- Accent: `KisaniOrange` = `#FF5C00`
|
||||
- Background: `#000000` (pure black)
|
||||
- Surface: `#111111` / `#1A1A1A`
|
||||
- All views: `.preferredColorScheme(.dark)` unless user overrides in settings
|
||||
|
||||
## To start in Claude Code
|
||||
```bash
|
||||
cd jarvis
|
||||
# Create new Xcode project named Jarvis, iOS target
|
||||
# Drag all .swift files into the project
|
||||
# Add KisaniOrange (#FF5C00) to Assets.xcassets as a Color Set
|
||||
# Set minimum deployment target to iOS 17 (SwiftData requirement)
|
||||
```
|
||||
---
|
||||
|
||||
## 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.
|
||||
|
||||
Reference in New Issue
Block a user