148 lines
6.1 KiB
Markdown
148 lines
6.1 KiB
Markdown
|
|
# 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.
|