Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions Docs/2026-04-12-ai-event-summary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# AI Summary of Hosted Events on Detail Pages

## Problem
Camp and art detail pages display hosted events (next event + "See all N events" link), but there's no quick summary of what the events collectively offer. Users have to tap through individual events to understand a camp/art's programming.

## Solution
Added AI-generated event collection summaries using Apple Foundation Models (iOS 26+) with the existing workflow pipeline for guardrail/context handling.

## Key Changes

### New Generable Type
- **`iBurn/AISearch/AIAssistantModels.swift`**: Added `GenerableEventCollectionSummary` with single `summary` field

### Reusable Summary Generator
- **`iBurn/AISearch/Workflows/WorkflowUtilities.swift`**: Added `generateEventCollectionSummary(events:hostName:) async -> String?`
- Wraps `withContextWindowRetry` (halves event count on context overflow)
- Inside uses `retryWithCandidateFiltering` (filters individual events that trigger guardrails)
- Formats events with name, type code display name, and truncated description (120 chars)
- Returns `nil` on complete failure for graceful degradation
- Slightly snarky tone per user preference

### New Detail Cell Types
- **`iBurn/Detail/Models/DetailCellType.swift`**: Added `.eventSummaryLoading(hostName:)` and `.eventSummary(String, hostName:)` cases

### Cell Rendering
- **`iBurn/Detail/Views/DetailView.swift`**:
- Added `EventSummaryHeaderView` (shared between detail cells and events list)
- Uses sparkles icon + "AI SUMMARY" header + ProgressView for loading state
- Added rendering cases in `cellContent` switch and `isCellTappable`

### ViewModel Integration
- **`iBurn/Detail/ViewModels/DetailViewModel.swift`**:
- New state: `resolvedEventSummary: String?`, `isGeneratingEventSummary: Bool`
- `generateEventSummaryIfNeeded()` triggers after deferred data loads (Phase 3)
- `generateEventSummaryCells(hostName:)` returns loading/summary/empty cells
- Wired into `generateHostedEventCells` (camp/art), `generatePlayaEventCellTypes`, `generatePlayaEventOccurrenceCellTypes`

### Events List Integration
- **`iBurn/Detail/Controllers/PlayaHostedEventsViewController.swift`**: Added summary header above event list via `.task` modifier

## Three-Phase Loading
1. **Phase 1** (existing): Metadata loads, cells render immediately
2. **Phase 2** (existing): Deferred data loads (host events, images), cells refresh
3. **Phase 3** (new): AI summary generates from resolved events, cells refresh again

## Graceful Degradation
- Pre-iOS 26: `#if canImport(FoundationModels)` + `@available(iOS 26, *)` guards
- All events trigger guardrails: `retryWithCandidateFiltering` tries halves then individuals, returns nil if <2 safe
- Context overflow: `withContextWindowRetry` halves event count down to minimum 2
- Complete LLM failure: Returns nil, no summary cell shown

## Build Verification
- Build succeeds: 0 errors, 6 pre-existing warnings (none from new code)
28 changes: 28 additions & 0 deletions Docs/2026-04-12-show-tab-bar-on-push.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Show Tab Bar on All Navigation Pushes

## Problem
Several view controllers set `hidesBottomBarWhenPushed = true`, causing the tab bar to disappear when navigating to detail screens, tracks, AI guide, recently viewed, and feature flags. With the iOS 26 Liquid Glass UI, the tab bar should remain visible throughout navigation for a consistent experience.

## Solution
Removed all 5 instances of `hidesBottomBarWhenPushed = true` across 2 files.

## Changes

### `iBurn/Detail/Controllers/DetailHostingController.swift`
- Removed `self.hidesBottomBarWhenPushed = true` from init (line 67) — this affected all detail screens (art, camps, events, mutant vehicles)

### `iBurn/MoreViewController.swift`
- Removed `tracksVC.hidesBottomBarWhenPushed = true` from `pushTracksView()` (line 343)
- Removed `hostingVC.hidesBottomBarWhenPushed = true` from `pushAIGuideView()` (line 392)
- Removed `recentVC.hidesBottomBarWhenPushed = true` from `pushRecentlyViewedView()` (line 416)
- Removed `featureFlagsVC.hidesBottomBarWhenPushed = true` from `pushFeatureFlagsView()` (line 501, DEBUG only)

### Not changed
- `MainMapViewController.swift:183-184` — explicitly re-shows tab bar in `viewWillDisappear`, harmless safety net kept as-is

## Verification
- Build succeeds with 0 errors, 0 warnings
- Test by navigating to detail views, tracks, recently viewed, AI guide — tab bar should remain visible

## Branch
`show-tab-bar-on-push` from `origin/master`
14 changes: 14 additions & 0 deletions iBurn/AISearch/AIAssistantModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,18 @@ struct GenerableNearbyResponse {
var highlights: [GenerableNearbyHighlight]
}

@available(iOS 26, *)
@Generable
struct GenerableEventCollectionSummary {
@Guide(description: "1-2 short factual sentences about this host. Only reference provided data. No times or schedules.")
var summary: String
}

@available(iOS 26, *)
@Generable
struct GenerableFactCheck {
@Guide(description: "Phrases from the summary that are NOT supported by the source data. Empty if everything is accurate.", .count(0...5))
var unsupportedClaims: [String]
}

#endif
11 changes: 11 additions & 0 deletions iBurn/AISearch/AgentOrchestrator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,23 @@ final class AgentOrchestrator: @unchecked Sendable {
init(playaDB: PlayaDB, locationProvider: LocationProvider) {
self.playaDB = playaDB
self.locationProvider = locationProvider
Self.warmUpLanguageModel()
}

var isAvailable: Bool {
SystemLanguageModel.default.isAvailable
}

/// Pre-warm the on-device language model with a trivial call so it's
/// already loaded in memory when the user opens a detail page.
private static func warmUpLanguageModel() {
guard SystemLanguageModel.default.isAvailable else { return }
Task.detached(priority: .background) {
let session = LanguageModelSession(instructions: "Reply with OK.")
_ = try? await session.respond(to: Prompt("ping"))
}
}

// MARK: - Step Execution

/// Execute a single LLM step with focused tools and instructions
Expand Down
24 changes: 24 additions & 0 deletions iBurn/AISearch/EventSummaryCache.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// EventSummaryCache.swift
// iBurn
//
// In-memory cache for AI-generated event summaries, keyed by host UID.
//

import Foundation

/// Actor-based RAM cache for AI event summaries.
/// Thread-safe and matches the async/await calling pattern used by the workflow pipeline.
actor EventSummaryCache {
static let shared = EventSummaryCache()

private var cache: [String: EventSummaryContent] = [:]

func get(_ hostUID: String) -> EventSummaryContent? {
cache[hostUID]
}

func set(_ hostUID: String, content: EventSummaryContent) {
cache[hostUID] = content
}
}
Loading
Loading