# Jarvis iOS — Claude Code Kickoff Prompt Paste this entire prompt into Claude Code to begin. --- ## Prompt You are building **Jarvis** — a SwiftUI iOS app that connects to a self-hosted RSS news correlation platform and displays stories ranked by signal score. Read `CLAUDE.md` before writing a single line of code. It contains every architecture decision already made. Do not deviate from it. --- ## Your job Build the remaining SwiftUI views. The networking layer, models, stores, and onboarding screen are already written. You are building the UI on top of them. ### Views to build 1. **SignalFeedView** — the home screen - Wordmark `Jarvis` with `J` in #FF5C00, rest white, weight 800, kerning -2 - Live/Offline connection indicator (driven by `WebSocketManager.shared.connectionState`) - Sync bar: "Synced X min ago · N cached" when live, "Last synced X min ago" when offline - Topic filter pills: All · Finance · Tech · Politics · Africa (calls `store.setTopic()`) - Column headers: Story / Signal ↓ - List of `StoryRowView` sorted by `signalScore` descending - Pull to refresh calls `store.loadStories(refresh: true)` - Infinite scroll: call `store.loadMore()` when last row appears - Tap row → navigate to `StoryDetailView` 2. **StoryRowView** — one row in the signal feed - Left edge stripe: 3pt wide, color opacity tied to signal score (97 = #FF5C00, fades to #1a1a1a at 0) - Story title, dimmed proportionally to score (97 = white, 20 = #444) - Signal score number, same fade behaviour - Source count, topic, time ago in monospaced font - Source chips (first 2 named, rest "+N") - Small green dot if story is fully cached offline 3. **StoryDetailView** — full story - Back button "Signal feed" - Category label + signal score pill (e.g. "Finance · 97 signal") - Large headline, weight 800 - Source chips - "All N articles cached · available offline" line if cached - Consensus block: orange left border, dark orange background - Conflicting block: dark red left border, dark red background (only if `conflict != nil`) - Coverage timeline: chronological list of `TimelineEntry`, first item has orange node - Tapping a timeline entry → navigate to `ArticleReaderView` 4. **ArticleReaderView** — full article - Back button showing parent story headline (truncated to 30 chars) - Source pill in #FF5C00, timestamp in monospaced - Cached badge inline (green, only if article is in SwiftData) - Large headline weight 800 - Hero image placeholder (AsyncImage with grey fallback) - Body text, #4a4a4a, size 16, line spacing 1.65 - Divider then "More from this cluster" section - 2–3 other articles from same story as tappable rows 5. **FeedManagerView** — feed health and management - Wordmark `Jarvis` header + "+ Add feed" button - Platform connection card: server URL, live/offline dot (from `WebSocketManager`) - Search bar to filter feed list - Two sections: "Healthy" and "Needs attention" - Each row: RSS icon, feed name, poll interval + article count, health dot - Health dot: green (#2a5a2a) = active, amber (#AA6600) = failing with retry countdown, red (#5a1a1a) = dead - Swipe to delete calls `APIClient.shared.deleteFeed(id:)` - "+ Add feed" sheet: URL input + name field + confirm button --- ## Design rules — non-negotiable - Background: `Color.black` / `#000000` everywhere - Accent: `Color("KisaniOrange")` = `#FF5C00` - Surface cards: `#111111`, `#1A1A1A` - All screens: `.preferredColorScheme(.dark)` - Font weight: 800 for headlines, 700 for labels, 400 for body — never use 600 - Monospaced font for: timestamps, signal scores, server URLs, feed metadata - No gradients. No shadows. Flat surfaces only. - Minimum tap target: 44pt - Signal score stripe and text both fade: score 80–100 = full orange, 50–79 = dimmed orange, 20–49 = dark brown, 0–19 = near invisible --- ## Data flow rules — non-negotiable - Views never call `APIClient` directly — always go through `StoryStore` or a dedicated `@StateObject` ViewModel - Signal scores are server-computed — never calculate or modify them in the UI - WebSocket events trigger REST refetches — the store handles this already - SwiftData (`CachedStory`, `CachedArticle`) is the offline source — query it when the server is unreachable - Use `@EnvironmentObject var store: StoryStore` in views that need stories - Use `@EnvironmentObject var ws: WebSocketManager` for connection state --- ## Xcode project setup 1. Create new Xcode project: iOS App, named **Jarvis**, Swift, SwiftUI 2. Set minimum deployment target: **iOS 17.0** (required for SwiftData) 3. Add all provided `.swift` files to the project 4. In `Assets.xcassets` add a new Color Set named `KisaniOrange`: - Any / Dark: `#FF5C00` 5. Add `Assets.xcassets` Color Set `AppBackground`: `#000000` 6. The SwiftData model container is already configured in `JarvisApp.swift` --- ## File structure to produce ``` Jarvis/ Views/ Home/ SignalFeedView.swift StoryRowView.swift Story/ StoryDetailView.swift Reader/ ArticleReaderView.swift Feeds/ FeedManagerView.swift AddFeedSheet.swift Shared/ SignalStripe.swift ← reusable 3pt stripe component SourceChips.swift ← reusable chips row CachedBadge.swift ← reusable green cached indicator ConnectionBanner.swift ← offline warning banner ``` --- ## Start here 1. Read `CLAUDE.md` 2. Create the Xcode project and add all existing files 3. Build `SignalStripe.swift`, `SourceChips.swift`, `CachedBadge.swift` — shared components used by multiple views 4. Build `StoryRowView.swift` 5. Build `SignalFeedView.swift` 6. Build `StoryDetailView.swift` 7. Build `ArticleReaderView.swift` 8. Build `FeedManagerView.swift` + `AddFeedSheet.swift` 9. Run on simulator, fix layout issues, verify dark mode renders correctly 10. Confirm WebSocket connection state drives the live/offline indicator on the home screen Do not skip steps. Do not simplify the signal stripe fade logic. Do not use placeholder colours — every hex value is specified above.