From a476e426f3e54f2a432f5523d35de5537d680427 Mon Sep 17 00:00:00 2001 From: Robin Kutesa Date: Mon, 22 Jun 2026 02:36:19 +0300 Subject: [PATCH] docs: rewrite CLAUDE.md with architecture rules and current project state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- CLAUDE.md | 188 +++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 144 insertions(+), 44 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 3d92d22..4e52f95 100644 --- a/CLAUDE.md +++ b/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.