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>
6.1 KiB
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
-
SignalFeedView — the home screen
- Wordmark
JarviswithJin #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
StoryRowViewsorted bysignalScoredescending - Pull to refresh calls
store.loadStories(refresh: true) - Infinite scroll: call
store.loadMore()when last row appears - Tap row → navigate to
StoryDetailView
- Wordmark
-
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
-
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
-
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
-
FeedManagerView — feed health and management
- Wordmark
Jarvisheader + "+ 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
- Wordmark
Design rules — non-negotiable
- Background:
Color.black/#000000everywhere - 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
APIClientdirectly — always go throughStoryStoreor a dedicated@StateObjectViewModel - 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: StoryStorein views that need stories - Use
@EnvironmentObject var ws: WebSocketManagerfor connection state
Xcode project setup
- Create new Xcode project: iOS App, named Jarvis, Swift, SwiftUI
- Set minimum deployment target: iOS 17.0 (required for SwiftData)
- Add all provided
.swiftfiles to the project - In
Assets.xcassetsadd a new Color Set namedKisaniOrange:- Any / Dark:
#FF5C00
- Any / Dark:
- Add
Assets.xcassetsColor SetAppBackground:#000000 - 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
- Read
CLAUDE.md - Create the Xcode project and add all existing files
- Build
SignalStripe.swift,SourceChips.swift,CachedBadge.swift— shared components used by multiple views - Build
StoryRowView.swift - Build
SignalFeedView.swift - Build
StoryDetailView.swift - Build
ArticleReaderView.swift - Build
FeedManagerView.swift+AddFeedSheet.swift - Run on simulator, fix layout issues, verify dark mode renders correctly
- 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.