Files
jarvis/CLAUDE.md
Robin Kutesa a476e426f3
Some checks failed
CI / Build · macos-14 · Debug (push) Has been cancelled
CI / Build · macos-15 · Debug (push) Has been cancelled
CI / Build · macos-14 · Release (push) Has been cancelled
CI / Build · macos-15 · Release (push) Has been cancelled
docs: rewrite CLAUDE.md with architecture rules and current project state
Establishes the core contract: RSS → backend cache → API → frontend.
Frontend must never fetch RSS. Updates project structure (all views now
built), pill→tag mapping, key decisions, and adds reporting requirement.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 02:36:19 +03:00

5.4 KiB

Jarvis iOS — Claude Code

What this is

Jarvis is an iOS app (SwiftUI, SwiftData, iOS 17+) that displays ranked news stories served from a self-hosted backend. The backend ingests RSS, clusters articles into stories, ranks them by signal score, and serves cached results via REST + WebSocket.

The app is a cached news intelligence client. It is not an RSS reader.


Architecture rule

RSS feeds → backend ingestion → cache/database → API → frontend

The frontend must never fetch RSS feeds directly. The frontend must never parse, deduplicate, or rank articles. The frontend consumes cached backend data only.


Backend responsibilities (not the app's job)

  • RSS fetching, validation, timeout handling
  • Feed health checks and dead-feed detection
  • Article normalization and deduplication
  • Story clustering and ranking
  • Freshness, interestingness, and signal scoring
  • API response caching and last-known-good fallback

The app trusts these to be done. It does not replicate them.


Frontend responsibilities

  • Load cached stories immediately on open (SwiftData)
  • Display stories with freshness indicators and source attribution
  • Show connection/refresh state without blocking the UI
  • Show stale-cache label when backend data is old
  • Paginate via cursor (nextCursor)
  • Never show an empty state when the cache has data

Performance targets:

  • App opens with cached news: instant
  • Home feed API response: < 1 second
  • Refresh response: < 2 seconds
  • Story open: < 500ms
  • Background refresh does not block UI

If any of these fail, treat it as a bug.


Frontend validation checklist

Before marking any feed/story feature complete, verify:

  • Cached news loads immediately without waiting for RSS
  • Refresh does not freeze the UI
  • Empty state does not appear when cache has data
  • Stale cache is labeled (age visible)
  • Fresh stories rise above old ones
  • Categories are balanced across the feed
  • Source attribution is correct and links work
  • Offline state shows last-known-good content

Project structure

Jarvis/
  Models/Models.swift          — Codable structs + SwiftData models
  Networking/APIClient.swift   — REST layer (actor-based)
  Networking/WebSocketManager.swift — WS + reconnect backoff
  Store/StoryStore.swift       — central ObservableObject, drives all views
  Store/StoryStore+Supplement.swift — background section supplement fetches
  Store/ServerSettings.swift   — persists host to UserDefaults
  Store/CacheMaintenance.swift — SwiftData eviction
  Views/
    Home/SignalFeedView.swift  — main feed (All digest, pill pills, sub-sections)
    Home/StoryRowView.swift    — story card
    Reader/ArticleReaderView.swift — full article, cached badge
    Story/StoryDetailView.swift   — cluster detail + timeline
    Feeds/FeedManagerView.swift   — feed list + health states
    Feeds/AddFeedSheet.swift
    Settings/SettingsView.swift
    Settings/NotificationsView.swift
    Shared/Theme.swift         — StoryPill enum, NewsSection, design tokens
    Shared/SignalStripe.swift
    Shared/SourceChips.swift
    Shared/CachedBadge.swift
    Shared/ConnectionBanner.swift
    Onboarding/OnboardingView.swift
  JarvisApp.swift
API/contract.md               — REST + WebSocket API spec

Design tokens

  • Accent: KisaniOrange = #FF5C00
  • Background: #000000 (pure black)
  • Surface: #111111 / #1A1A1A
  • All views: .preferredColorScheme(.dark) unless user overrides in settings

Key decisions

  • Story is the user-facing object. Never expose Cluster to UI.
  • Pipeline: Article → Cluster → Story
  • Signal score is server-computed; scoreBreakdown is exposed for display.
  • WebSocket pushes {storyId, score} only; app re-fetches the full object via REST.
  • WS reconnect: exponential backoff 1s → 2s → 4s → max 60s.
  • Pagination: cursor-based via nextCursor.
  • StoryStore.setPill() resets lastSyncedAt to bypass the 30s rate-limit on filter switch.
  • sectionSupplement fetches populate sparse regional sections in the All digest independently.

Pill → tag mapping

Pill Backend tags queried
All — (no filter, full ranked feed)
F1 formula-1
Sport sports, esports
Tech technology, artificial-intelligence, machine-learning, cloud-computing, homelab, cybersecurity, security, programming-and-software-development, privacy-and-data-protection, web-design-and-ui-ux, wordpress-and-web-development, robotics
East Africa east-africa, uganda (Uganda sub-section pinned first)
Southern Africa south-africa, namibia, botswana, zimbabwe, zambia, mozambique, southern-africa
Canada canada
US united-states

Reporting requirement

At the end of any significant work session, generate a report covering:

  1. Executive summary — is the app usable, fast, serving cached news?
  2. Issues found — backend, frontend, cache, feed, ranking, data quality
  3. Fixes made — file, function, reason, result
  4. Measurements — before/after for response times, article counts, dead feeds
  5. Remaining risks — nothing hidden
  6. Next actions — highest-value improvements
  7. Status — one of: NOT READY / PARTIALLY READY / READY FOR TESTING / READY FOR RELEASE

Never claim READY FOR RELEASE without evidence from logs, API responses, or timing measurements.