rename: Jarvis -> Jervis target, scheme, and folder throughout
Some checks are pending
CI / Build · Debug (push) Waiting to run
CI / Build · Release (push) Waiting to run

The bundle-ID and display-name renames weren't enough — the actual
Xcode target/product name was still "Jarvis", which drives CFBundleName,
Xcode's Organizer archive list, the .xcodeproj filename, and the scheme
name. Renamed all the way through:

- project.yml: top-level name, target key, PRODUCT_NAME, source/info paths
- Jarvis/ -> Jervis/ (source folder, git-tracked as renames, no content
  diffs on the moved files)
- .gitignore, CI workflows, README, CONTRIBUTING: Jarvis.xcodeproj /
  scheme Jarvis -> Jervis.xcodeproj / scheme Jervis

Verified: xcodebuild -scheme Jervis succeeds, produces Jervis.app,
CFBundleName/CFBundleExecutable/CFBundleDisplayName all read "Jervis".
CFBundleIdentifier intentionally stays com.kisani.jarvis (per prior
decision — bundle ID isn't user-facing anywhere including Organizer).

Swift type names (JarvisApp, JarvisWordmark, etc.) and the Gitea repo
name/URL are unchanged — pure internal source identifiers, not user or
developer-facing product identity.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Kutesir
2026-07-13 03:49:31 +03:00
parent 3a6f1bbdd8
commit bd632a1592
46 changed files with 20 additions and 19 deletions

View File

@@ -1,272 +0,0 @@
// Models.swift
// Jarvis all Codable structs matching the API contract
import Foundation
import SwiftData
// MARK: - Signal Score
struct SignalScore: Codable, Hashable {
let total: Int
let sourceAuthority: Int
let freshness: Int
let localRelevance: Int
let crossSourceConfirmation: Int
let topicImportance: Int
enum CodingKeys: String, CodingKey {
case total = "signalScore"
case sourceAuthority, freshness, localRelevance
case crossSourceConfirmation, topicImportance
}
}
// MARK: - Source
struct StorySource: Codable, Hashable, Identifiable {
let id: String
let name: String
let url: String
let publishedAt: Date
let isBreaking: Bool
}
// MARK: - Timeline Entry
struct TimelineEntry: Codable, Hashable, Identifiable {
let articleId: String
let source: String
let headline: String
let publishedAt: Date
let isBreaking: Bool
var id: String { articleId }
}
// MARK: - Story Summary (list view)
struct StorySummary: Codable, Identifiable, Hashable {
let id: String
let headline: String
let summary: String
let topic: String
/// Canonical category slugs from the backend. Optional so decoding tolerates
/// the transition while the backend rolls out `tags[]`; falls back to `topic`.
let tags: [String]?
let signalScore: Int
let scoreBreakdown: ScoreBreakdown
let sourceCount: Int
let sources: [StorySource]
let consensus: String?
let conflict: String?
let updatedAt: Date
let createdAt: Date
/// When the cluster first appeared. `updatedAt` bumps on every article the
/// backend attaches to the cluster (even a loosely-related one), so it can
/// read "1 hr ago" on stories that are actually weeks old `firstSeenAt`
/// is the honest age. Optional so decoding tolerates older API responses.
let firstSeenAt: Date?
}
struct ScoreBreakdown: Codable, Hashable {
let sourceAuthority: Int
let freshness: Int
let localRelevance: Int
let crossSourceConfirmation: Int
let topicImportance: Int
}
// MARK: - Story Detail (full, with timeline)
struct StoryDetail: Codable, Identifiable {
let id: String
let headline: String
let summary: String
let topic: String
let signalScore: Int
let scoreBreakdown: ScoreBreakdown
let sourceCount: Int
let consensus: String?
let conflict: String?
let timeline: [TimelineEntry]
let updatedAt: Date
let createdAt: Date
}
// MARK: - Article (full content, for offline cache)
struct Article: Codable, Identifiable {
let id: String
let storyId: String
let source: String
let sourceUrl: String
let headline: String
let body: String
let imageUrl: String?
let author: String?
let publishedAt: Date
}
// MARK: - Feed
struct Feed: Codable, Identifiable {
let id: String
let name: String
let url: String
let health: FeedHealth
let pollIntervalSeconds: Int
let failureCount: Int
let lastFetchedAt: Date?
let articleCountToday: Int
}
enum FeedHealth: String, Codable {
case active, failing, dead
}
// MARK: - Server Health
struct ServerHealth: Codable {
let status: String
let version: String
let storiesCount: Int
let feedsCount: Int
let uptime: Int
}
// MARK: - Paginated Response
struct PaginatedStories: Codable {
let data: [StorySummary]
let nextCursor: String?
let hasMore: Bool
let total: Int
let unreadCount: Int?
let readSuppressedCount: Int?
let lastReadSyncAt: Date?
let activePill: String?
}
// MARK: - WebSocket Events
enum WSEventType: String, Codable {
case storyUpdated = "story.updated"
case storyCreated = "story.created"
case storyStale = "story.stale"
case feedHealth = "feed.health"
case ping, pong
}
struct WSEvent: Codable {
let type: WSEventType
let storyId: String?
let feedId: String?
let signalScore: Int?
let sourceCount: Int?
let headline: String?
let topic: String?
let health: FeedHealth?
let failureCount: Int?
let updatedAt: Date?
let createdAt: Date?
}
// MARK: - SwiftData cached models
@Model
final class CachedStory {
@Attribute(.unique) var id: String
var headline: String
var summary: String
var topic: String
/// Canonical backend tags, so offline pill filtering stays accurate.
/// Defaulted for lightweight migration of existing cached rows.
var tags: [String] = []
var signalScore: Int
var sourceCount: Int
var consensus: String?
var conflict: String?
var updatedAt: Date
/// Original publish timestamp. Defaults to updatedAt for rows cached before
/// this field was added SwiftData handles the automatic migration.
var createdAt: Date = Date()
/// When the cluster first appeared the honest age, unlike `updatedAt` which
/// bumps whenever the backend attaches another article. Defaults to `updatedAt`
/// for rows cached before this field was added.
var firstSeenAt: Date = Date()
var cachedAt: Date
init(from summary: StorySummary) {
self.id = summary.id
self.headline = summary.headline
self.summary = summary.summary
self.topic = summary.topic
self.tags = summary.tags ?? []
self.signalScore = summary.signalScore
self.sourceCount = summary.sourceCount
self.consensus = summary.consensus
self.conflict = summary.conflict
self.updatedAt = summary.updatedAt
self.createdAt = summary.createdAt
self.firstSeenAt = summary.firstSeenAt ?? summary.updatedAt
self.cachedAt = Date()
}
}
@Model
final class ReadStory {
@Attribute(.unique) var id: String
var readAt: Date
init(id: String) {
self.id = id
self.readAt = Date()
}
}
@Model
final class SavedStory {
@Attribute(.unique) var id: String
var savedAt: Date
init(id: String) {
self.id = id
self.savedAt = Date()
}
}
@Model
final class SeenStory {
@Attribute(.unique) var id: String
var seenAt: Date
init(id: String) {
self.id = id
self.seenAt = Date()
}
}
@Model
final class CachedArticle {
@Attribute(.unique) var id: String
var storyId: String
var source: String
var headline: String
var body: String
var imageUrl: String?
var author: String?
var publishedAt: Date
var cachedAt: Date
init(from article: Article) {
self.id = article.id
self.storyId = article.storyId
self.source = article.source
self.headline = article.headline
self.body = article.body
self.imageUrl = article.imageUrl
self.author = article.author
self.publishedAt = article.publishedAt
self.cachedAt = Date()
}
}