Fix FoundationModels crash, driving constraint, and disable card descriptions

- RouteDescriptionGenerator: reuse session, cap tokens at 60, greedy
  sampling, prewarm with prompt prefix, serial request queue with
  rate-limit retry and circuit breaker
- Disable AI/template descriptions on trip option cards
- GameDAGRouter: fix off-by-one in canTransition driving constraint
  (daysBetween+1) so multi-day cross-city routes aren't rejected
- CanonicalSyncService: wrap SyncStatusMonitor in #if DEBUG

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Trey T
2026-03-27 12:42:21 -05:00
parent aa6477b886
commit 87b9971714
3 changed files with 207 additions and 95 deletions

View File

@@ -315,6 +315,7 @@ struct TripOptionsView: View {
TripDetailView(trip: trip)
}
.onAppear {
RouteDescriptionGenerator.shared.prewarm()
if isDemoMode && !hasAppliedDemoSelection {
hasAppliedDemoSelection = true
// Auto-select "Most Games" sort after a delay
@@ -491,7 +492,11 @@ struct TripOptionCard: View {
@Environment(\.colorScheme) private var colorScheme
@State private var aiDescription: String?
@State private var isLoadingDescription = false
private var templateDescription: String {
let input = RouteDescriptionInput(from: option, games: games)
return RouteDescriptionGenerator.shared.templateDescription(for: input)
}
private var uniqueCities: [String] {
option.stops.map { $0.city }.removingDuplicates()
@@ -571,21 +576,6 @@ struct TripOptionCard: View {
}
}
// AI-generated description (after stats)
if let description = aiDescription {
Text(description)
.font(.caption)
.foregroundStyle(Theme.textMuted(colorScheme))
.fixedSize(horizontal: false, vertical: true)
.transition(.opacity)
} else if isLoadingDescription {
HStack(spacing: 4) {
LoadingSpinner(size: .small)
Text("Generating...")
.font(.caption2)
.foregroundStyle(Theme.textMuted(colorScheme))
}
}
}
Spacer()
@@ -606,28 +596,6 @@ struct TripOptionCard: View {
}
}
.buttonStyle(.plain)
.task(id: option.id) {
// Reset state when option changes
aiDescription = nil
isLoadingDescription = false
await generateDescription()
}
}
private func generateDescription() async {
guard RouteDescriptionGenerator.shared.isAvailable else { return }
isLoadingDescription = true
// Build input from THIS specific option
let input = RouteDescriptionInput(from: option, games: games)
if let description = await RouteDescriptionGenerator.shared.generateDescription(for: input) {
Theme.Animation.withMotion(.easeInOut(duration: 0.3)) {
aiDescription = description
}
}
isLoadingDescription = false
}
}