Initial commit: Jarvis iOS app
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>
This commit is contained in:
147
docs/KICKOFF_PROMPT.md
Normal file
147
docs/KICKOFF_PROMPT.md
Normal file
@@ -0,0 +1,147 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user