SwiftUI client for a self-hosted RSS news-correlation platform: signal feed, story detail, article reader, feed manager, and LAN⇄Tailscale connectivity. Project generated from project.yml via XcodeGen. Includes CI build matrix (macOS 14/15 × Debug/Release), issue templates, backlog, and API/backend handoff docs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
|