Skip to content

GRDB performance optimization + Recently Viewed feature#244

Merged
chrisballinger merged 13 commits intomasterfrom
ai-improvements
Apr 12, 2026
Merged

GRDB performance optimization + Recently Viewed feature#244
chrisballinger merged 13 commits intomasterfrom
ai-improvements

Conversation

@chrisballinger
Copy link
Copy Markdown
Member

Summary

  • Eliminate N+1 query patterns across PlayaDB — batch getFavorites, fetchRecentlyViewed, fetchFavoriteEvents, event↔occurrence joins (10 call sites), and object resolution (40→4 queries)
  • Add 5 missing database indexes for hosted events, composite metadata, and recently viewed queries
  • Batch ensureMetadata into single write transactions (3-4 transactions → 1)
  • Recently Viewed feature — new browsable history screen with type filtering, search, sort by recent/distance, swipe-to-remove, clear all, and map view
  • View history on detail screens — first viewed and last viewed dates displayed in footer
  • New firstViewed field on ObjectMetadata with schema migration for existing databases

Test plan

  • Build succeeds with no new warnings
  • Favorites list loads quickly (batch queries)
  • Recently Viewed screen shows viewed items sorted by most recent
  • Sort by distance works when location is available
  • Swipe to remove a single recent item
  • Clear All removes all history with confirmation
  • Map button shows recently viewed items on map
  • Detail screen footer shows "View History" with first/last viewed dates
  • Type filter (All/Art/Camps/Events/Vehicles) works correctly
  • Search within recently viewed history works
  • Camp/art detail pages load hosted events quickly (indexed + batch)
  • Event list views load occurrences efficiently (batch helpers)

🤖 Generated with Claude Code

chrisballinger and others added 13 commits April 6, 2026 21:31
Performance: Eliminate N+1 query patterns across PlayaDB
- Batch getFavorites(), fetchRecentlyViewed(), fetchFavoriteEvents() (N+1 → 5 queries)
- Add 5 missing indexes (hosted_by_camp, located_at_art, composite metadata, etc.)
- Batch ensureMetadata() into single write transaction
- Add fetchObjects(byUIDs:) for batch object resolution (40 queries → 4)
- Add two batch helpers for event↔occurrence joins, applied to 10 call sites
- Remove redundant .including(all:) eager loads that weren't decoded

Recently Viewed: Full browsing history feature
- New screen in More with type filter, search, sort by recent/distance
- Swipe-to-remove individual items and Clear All with confirmation
- Map view of all recently viewed items
- firstViewed/lastViewed dates shown on detail screens
- New ObjectMetadata.firstViewed field with schema migration
- New PlayaDB methods: fetchRecentlyViewedWithDates, clearLastViewed, clearAllRecentlyViewed

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
8 AI workflows (For You, Surprise Me, Day Planner, Adventure Generator,
Camp Crawl, Golden Hour Art, What Did I Miss, Schedule Optimizer) with
per-workflow configuration knobs, transparent step progress, and
whimsical playa-themed messages. Replaces free-form chat with a
structured workflow browser. Adds 7 new search tools, detail levels
for context budget management, and intent classification.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…d sort

- Fix event cells in Recently Viewed to show host camp/art thumbnail
  via EventRecentRow instead of bare minimal layout
- Enrich .event(EventObject) detail view to show full content: host
  image, map, schedule, host relationship, event type, and footer
  (previously only showed title/description/location/URL)
- Fix setLastViewed for EventObjectOccurrence to track parent event UID
  so events viewed via occurrences appear correctly in recents
- Add "First Viewed" sort option to Recently Viewed list
- Show distance-only subtitles in recents cells (no last-viewed timestamps)
- Add fetchOccurrences(forEventUID:) to PlayaDB for internal use
- Enable XcodeBuildMCP device workflow and document in CLAUDE.md

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove NavigationView wrapper and NavigationLink from AIGuideView,
  delegate all navigation to UIKit via closures to avoid nested
  navigation controllers
- Add onSelectWorkflow closure to AIGuideView, onNavigateToDetail
  closure to WorkflowDetailView
- MoreViewController pushes workflow detail as separate UIHostingController
  onto the existing UIKit navigation stack
- Fix AI summary text showing on wrong cells by adding .id(uid) to
  resolvedObjectRow for proper SwiftUI identity tracking
- Auto-retry all workflow failures up to 3 times (was 2, limited types)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace parallel arrays in @generable structs with paired structs so
UIDs/names stay correctly associated with their tips/reasons. The LLM
generates two independent arrays that can drift out of alignment.

- CampCrawlWorkflow: GenerableCampStop pairs uid+tip
- AdventureWorkflow: GenerableSelectedStop pairs uid+reason,
  GenerableAdventureTip pairs stopName+tip
- GoldenHourWorkflow: GenerableGoldenHourStop pairs uid+reason
- DayPlanWorkflow: GenerableDayPlanNote pairs eventName+note
- ScheduleOptimizerWorkflow: GenerableScheduleNote pairs eventName+note

Narrative merge steps now match by name instead of array index.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add retryWithCandidateFiltering() utility that isolates problematic
  candidates via binary search: tries full set, then halves, then
  individual items to find and exclude ones that trigger guardrails
- Apply to CampCrawl, Adventure, and GoldenHour workflows so the
  curation step recovers instead of retrying the whole workflow
- DayPlan and ScheduleOptimizer narrative steps now match notes to
  events by name instead of array index
- Show actual error descriptions in DEBUG builds (step + final error)
- Clear step history between outer retries to avoid wall of repeated steps
- Retry all errors up to 3 times (not just specific error types)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add @focusstate to text fields in WorkflowDetailView
- Dismiss keyboard when tapping Generate button
- Dismiss on background tap via .onTapGesture
- Dismiss interactively while scrolling via .scrollDismissesKeyboard

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add DatePicker to WorkflowDetailView for dayPlanner config
- Default to current date clamped to festival range via
  YearSettings.dayWithinFestival
- Thread startDate through ViewModel → orchestrator → WorkflowContext
- Day Planner now requires user input before auto-starting

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use numeric IDs instead of full UIDs in LLM prompts — saves ~30 tokens
per candidate. Map back to real UIDs after selection.

- DayPlanner: numeric IDs, stripped descriptions, selectedNumbers
- CampCrawl: numeric IDs in GenerableCampStop, compact formatting
- Adventure: numeric IDs in GenerableSelectedStop, brief formatting
- GoldenHour: numeric IDs in GenerableGoldenHourStop, compact art list
- ScheduleOptimizer: trimmed conflict prompts
- Serendipity/WhatDidIMiss: switched to brief detail level, shorter instructions

All workflows now fit comfortably within the on-device model's 4096
token context window even with 15-20 candidates.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New WorkflowUtilities.swift with four shared utilities:
- resolveObject/resolveObjects/resolveDiscoveryItems: Single place for
  the 4-branch UID→object resolution (was copy-pasted in 4 workflows)
- buildRoute: Shared route building with coordinate resolution, route
  optimization, and walk time calculation (was ~40 lines in 3 workflows)
- buildNumberedList: Numeric ID map creation for token-efficient prompts
- mergeNotesByName: LLM note→entry merging by name match

Refactored all 8 workflows to use shared utilities:
- Adventure, CampCrawl, GoldenHour: use buildRoute (~40 lines each → ~5)
- Adventure: uses mergeNotesByName for tip merging
- Serendipity, WhatDidIMiss, GeneralChat: use resolveDiscoveryItems
  (removed 3 identical private resolveItems methods)
- DayPlan, ScheduleOptimizer: use mergeNotesByName

~200 lines of duplication eliminated. Future changes to route building,
object resolution, or note merging only need to happen in one place.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add withContextWindowRetry() utility that catches
  LanguageModelSession.GenerationError.exceededContextWindowSize and
  halves the candidate count automatically until it fits
- Apply to DayPlanner selection step (was failing at 7476/4096 tokens)
- Replace all string-based error matching with typed pattern matching
  on LanguageModelSession.GenerationError cases (exceededContextWindowSize,
  guardrailViolation, unsupportedLanguageOrLocale, rateLimited,
  assetsUnavailable)
- Add isContextWindowError() and isGuardrailError() typed helpers

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ch/recents to EventObjectOccurrence

- Correct corrupted event occurrence times from PlayaEvents API during import
  (negative and excessive durations fixed by preserving time-of-day on correct date)
- Add UpdateInfo tracking fields (fetchStatus, fetchDate, ingestionDate, etc.)
  with schema migration and reactive observation via GRDB ValueObservation
- DataUpdatesView now shows both YapDB and PlayaDB sections with re-import support
- Migrate GlobalSearch and RecentlyViewed to use EventObjectOccurrence with
  EventRowView and lazy host resolution for camp/art context
- Add re-import and occurrence duration validation tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…nd timezone

- Merge ObjectRowView + MediaObjectRowView into a single ObjectRowView with
  built-in RowAssetsLoader for thumbnail/color loading
- Delete EventRowView — all object types now use the same row layout
- Add thumbnailObjectID to DisplayableObject protocol so events automatically
  resolve host camp/art thumbnails at the data model layer
- Add shared EventObjectOccurrence.timeDescription(now:) with proper
  Burning Man timezone (PDT) to fix time display inconsistency between
  list rows and detail page
- Fix duplicate event occurrences in import: skip both EventObject AND
  occurrences for duplicate UIDs in API data

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@chrisballinger chrisballinger merged commit d4553f9 into master Apr 12, 2026
0 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant