Skip to content

feat(ios): persist mobile conversations and drafts#120

Merged
tcballard merged 4 commits into
mainfrom
codex/ios-conversation-store
Jul 24, 2026
Merged

feat(ios): persist mobile conversations and drafts#120
tcballard merged 4 commits into
mainfrom
codex/ios-conversation-store

Conversation

@tcballard

Copy link
Copy Markdown
Contributor

Summary

Implements the conversation-store boundary in Phase 2 of
WF-ROADMAP-0016.

Adds:

  • versioned native SwiftData persistence behind an asynchronous
    ConversationStore protocol;
  • durable threads, messages, active conversation, per-thread drafts, compact
    route receipts, retention, export, and deletion;
  • restored thread-first iPhone/iPad UI states and bounded storage failure
    recovery;
  • deterministic model/store tests and WF-ADR-0049.

Scope

Included

  • Codable, Sendable conversation/message/workspace/export snapshots;
  • actor-isolated SwiftData access through @ModelActor;
  • version-one VersionedSchema and SchemaMigrationPlan;
  • production persistent container and in-memory startup fallback;
  • active-thread and new-chat draft restoration;
  • multi-turn append behavior and chronological thread lists;
  • thread selection, swipe deletion, clear-all confirmation, retention pruning,
    and JSON sharing;
  • scene-transition draft flush plus debounced active editing saves;
  • Xcode project regeneration for the new source and test files.

Excluded

  • provider execution, streaming, or assistant responses;
  • API keys, OAuth, Keychain, or provider credentials;
  • CloudKit/iCloud sync;
  • conversation import;
  • full-text search;
  • paired Mac behavior;
  • macOS persistence changes.

Product / Architecture Decisions

  • Chose SwiftData for v0.2 native storage and kept it behind
    ConversationStore; SwiftUI and routing code never own a model context.
  • Chose one atomic encoded payload per thread while message/provider contracts
    are still evolving. Metadata needed for list and retention queries remains
    directly indexed by SwiftData.
  • Declared the first versioned schema and migration plan immediately so future
    schema changes add versions rather than rewriting v1.
  • Kept credentials explicitly outside every snapshot, record, export, log, and
    error path.
  • Chose a sanitized in-memory fallback when durable container initialization
    fails. The UI warns that data is not durable rather than crashing or claiming
    it was saved.
  • Export is deterministic, versioned JSON sorted by creation time and UUID.

User-Facing Contract

CLI

No CLI behavior changes.

Human Output

  • Chats survive relaunch and restore the previously active thread.
  • Drafts survive relaunch and are retained per active thread.
  • The drawer and Threads screen show chronological saved conversations.
  • Selecting a thread restores its transcript, receipt, and draft.
  • Threads support swipe deletion.
  • Settings offers 30-day, 90-day, or Forever retention.
  • Settings can prepare/share a conversation export and clear all conversations
    after confirmation.
  • Storage failures display bounded recovery copy without exposing paths or raw
    errors.

JSON Output

Conversation export uses:

{
  "schemaVersion": 1,
  "threads": [
    {
      "id": "UUID",
      "title": "First prompt-derived title",
      "createdAt": "ISO-8601",
      "updatedAt": "ISO-8601",
      "messages": [],
      "draft": ""
    }
  ]
}

Messages may contain a compact routeReceipt. Exports never contain provider
credentials or raw provider envelopes.

Exit Codes

No exit-code changes.

Verification

Ran

xcodegen generate \
  --spec ios/WayfinderIOS/project.yml \
  --project ios/WayfinderIOS

swift format lint \
  --recursive \
  ios/WayfinderIOS/WayfinderIOS \
  ios/WayfinderIOS/WayfinderIOSTests

git diff --check

SWIFTPM_MODULECACHE_OVERRIDE=/private/tmp/WayfinderIOS-Store-ModuleCache \
CLANG_MODULE_CACHE_PATH=/private/tmp/WayfinderIOS-Store-ClangCache \
swift build \
  --build-tests \
  --package-path ios/WayfinderIOS \
  --scratch-path /private/tmp/WayfinderIOS-Store-SwiftPM \
  --triple arm64-apple-ios18.0-simulator \
  --sdk /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk

Covered

  • SwiftData thread and workspace round trip;
  • newest-first list ordering;
  • active-thread deletion clearing workspace state;
  • cutoff retention preserving current threads;
  • deterministic, versioned export and credential-field exclusions;
  • clear-all removing threads and workspace draft;
  • AppModel restore across independent instances;
  • unsent new-chat draft restoration without fabricating a thread;
  • multiple turns appending to one active thread;
  • 30-day retention on restore;
  • existing local/hosted routing, privacy exclusion, empty prompt, and new-chat
    reset behavior;
  • app and test-product compilation for the arm64 iOS simulator target.

The matching local executable Simulator runtime is still unavailable on this
Mac. Apple Mobile CI remains the authoritative simulator execution gate.

Review Path

  1. decisions/WF-ADR-0049-mobile-conversation-persistence.md
  2. ios/WayfinderIOS/WayfinderIOS/ConversationStore.swift
  3. ios/WayfinderIOS/WayfinderIOS/AppModel.swift
  4. ios/WayfinderIOS/WayfinderIOS/WayfinderIOSApp.swift
  5. Chat, drawer, Threads, and Settings wiring
  6. ConversationStoreTests.swift and AppModelTests.swift
  7. generated Xcode project and roadmap/README updates

Notes For Reviewer

Pay particular attention to the atomic thread-payload choice, actor isolation,
startup fallback wording, retention cutoff behavior, and the fact that this
slice persists routing previews without claiming provider execution.

The SwiftData schema is v1. Future changes must add a schema version and
migration stage rather than modifying the historical model in place.

Implementation Process

Implemented with AI assistance under the roadmap contract; final scope, review,
and acceptance decisions remain with the maintainer.

@tcballard
tcballard marked this pull request as ready for review July 24, 2026 13:44
@tcballard
tcballard merged commit b2eda46 into main Jul 24, 2026
4 checks passed
@tcballard
tcballard deleted the codex/ios-conversation-store branch July 24, 2026 13:44
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