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>
APIClient:
- markAllRead(storyIds💊) — POST /me/read/bulk with device ID, timestamp,
source label, and pill name so backend can record context
Models:
- PaginatedStories gains unreadCount, readSuppressedCount, lastReadSyncAt,
activePill — backend now returns these so the UI can report honest state
SignalFeedView:
- Header "…" menu replaces the standalone settings button; menu contains
"Mark all as read" (disabled while pending or when nothing is unread) and
a Settings item
- markAllRead() captures all visible unread IDs, optimistically inserts
ReadStory for each, shows toast ("Marked N stories as read"), then fires
the bulk API; on failure deletes the optimistic ReadStory records and
shows an honest error toast ("Sync failed. Stories may reappear until retry.")
- Toast auto-dismisses after 3.5s, appears at the bottom of the ZStack
with a slide-up + opacity animation
Cross-pill suppression was already in place — backend applies
unread_story_condition() to every /stories request regardless of tags,
so stories marked read in All disappear from Tech, Sport, etc. on the
next pill fetch.
Co-Authored-By: Kutesir <tqwyy79vzn@privaterelay.appleid.com>
- APIClient: generate and persist a stable device UUID (jarvis.device.id
in UserDefaults); attach as X-Jarvis-Device-Id header on every request
- Add markStoryRead / markStoryUnread — POST/DELETE /stories/:id/read with
device_id, read_at, and source fields
- Wire both calls into all three read-toggle sites: SignalFeedView,
ArticleReaderView, StoryDetailView
- Add postVoid helper for fire-and-forget POST calls (no response body needed)
- Pass include_read param on stories fetch for future server-side filtering
- CacheMaintenance: recentHours 84→72 (mirrors backend 72h story window);
markerHours 96→192 so read suppression survives a full weekly news cycle
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- East Africa pill: merges Uganda + east-africa tags; Uganda pinned first via sub-sections
- Southern Africa pill: covers SA, Namibia, Botswana, Zimbabwe, Zambia, Mozambique; SA/Namibia/Botswana prioritized
- StoryStore.setPill now resets lastSyncedAt to bypass 30s rate-limit on filter switch
- sectionSupplement background fetch populates sparse regional sections in All digest
- SignalFeedView digestContent: flat hero+scroll path for pills without subSections (was showing max 5)
- Theme: removed standalone Uganda pill, wired new sub-section layouts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- listen(): capture `task` as local `t` before the Sendable receive closure
so the @MainActor-isolated property isn't referenced inside a Sendable context
- startPing(): dispatch to @MainActor inside the timer block before accessing `task`
- scheduleReconnect(): capture `self` weakly in the reconnect Task
XcodeGen regenerated to include BackgroundRefreshManager.swift in the project.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Don't blank the feed on refresh: keep current stories until new data
arrives, and ignore benign URLError.cancelled (-999) so a network blip
no longer wipes the list. Coalesce WebSocket story.created bursts into a
single debounced refresh.
- Empty state now distinguishes polling, couldn't-reach-server (with the
error + host + Retry), offline, and genuinely-empty.
- Track read stories in SwiftData (ReadStory); mark a story read when opened
and drop/dim its signal stripe across the feed and Latest/Saved/Search.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>