Phase 5 — Saved jobs persistence and revalidation
Status: Implemented
Date: 2026-10-12
Scope
- Persist saved Indeed listings with SwiftData
- Dedupe on save by
indeedJobKey
- Keep unavailable jobs visible with status; Open on Indeed still offered
- Background revalidation (24h stale interval)
- Settings: clear local saved jobs + JSON export
Architecture
Save toggle (Search / Saved)
→ SavedJobsStore (SwiftDataSavedJobsStore | MockSavedJobsStore)
→ SavedJobEntity (unique indeedJobKey + JobListing JSON snapshot)
→ UI refresh via ViewModel version bumps
Launch / Saved Jobs appear
→ SavedJobsRevalidator.runPass()
→ JobValidationService (source signal → optional HEAD)
→ updateListing(availability, lastVerifiedAt)
What shipped
| Component |
Path |
| Model container |
Persistence/PersistenceController.swift |
| SwiftData entity |
Persistence/SavedJobEntity.swift |
| Persisted store |
Persistence/SwiftDataSavedJobsStore.swift |
| Revalidator |
Persistence/SavedJobsRevalidator.swift |
| Protocol |
SavedJobsStore (+ updateListing, clearAll) |
| Live DI |
AppDependencies.live → SwiftData + HTTP HEAD for saved revalidation |
| Preview DI |
AppDependencies.preview → MockSavedJobsStore |
| Settings |
Clear local data + export JSON |
| Tests |
indeed_ai_job_searchTests/SavedJobsPersistenceTests.swift |
Policies
| Policy |
Value |
| Dedupe key |
indeedJobKey (unique SwiftData attribute) |
| Snapshot |
Full JobListing JSON in entity |
| Revalidation interval |
24 hours (SavedJobsRevalidator.defaultStaleInterval) |
| Unavailable rows |
Kept; muted/strikethrough via existing JobRowView |
| Search validation |
Unchanged — source signals only (no HEAD during search) |
| Saved revalidation |
Source signal + HTTPJobValidationService HEAD when unknown |
Acceptance checklist
Out of scope (Phase 6+)
- Persisted job alerts / notification history
- Cloud sync / iCloud