Merge origin/main into feat-spotify

Resolve conflict in ContentView.swift by keeping both TodosView and SettingsView tabs.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-10 21:05:54 +00:00
7 changed files with 472 additions and 28 deletions

View File

@@ -376,6 +376,7 @@ final class ContextOrchestrator: NSObject, ObservableObject {
if pois.isEmpty {
logger.info("no points of interests found")
}
// POIs are FYI-only; do not compete for the right-now winner.
for poi in pois.prefix(2) {
let subtitle = poiSubtitle(for: poi)
let confidence = min(max(poi.confidence, 0.0), 1.0)
@@ -393,7 +394,6 @@ final class ContextOrchestrator: NSObject, ObservableObject {
actions: ["DISMISS"]
)
poiItems.append(item)
rightNowCandidates.append(.init(item: item, confidence: confidence, isEligibleForRightNow: true))
}
case .failure(let error):
fetchFailed = true
@@ -451,10 +451,17 @@ final class ContextOrchestrator: NSObject, ObservableObject {
return
}
let eligibleUnsuppressed = rightNowCandidates.filter { ranked in
!store.isSuppressed(id: ranked.item.id, type: ranked.item.type, now: nowEpoch)
let poiCandidateCount = rightNowCandidates.filter { $0.item.type == .poiNearby }.count
if poiCandidateCount > 0 {
logger.warning("dropping poi candidates from right-now ranking count=\(poiCandidateCount)")
}
let eligibleUnsuppressed = rightNowCandidates
.filter { $0.item.type != .poiNearby }
.filter { ranked in
!store.isSuppressed(id: ranked.item.id, type: ranked.item.type, now: nowEpoch)
}
let winnerSelection = ranker.pickWinner(from: eligibleUnsuppressed, now: nowEpoch, context: userContext)
let winnerItem = winnerSelection?.item ?? FeedEnvelope.allQuiet(now: nowEpoch).feed[0]