#6 fix gallery preview after canceling a previous pick
iOS CI / Build and Test SwiftUI App (push) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build Unsigned iOS IPA (push) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build macOS ZIP (push) Canceled after 0s

- Stage picked media only when the picker is dismissed with a
  confirmed selection instead of on every selection change, so
  canceling restores an empty binding and never schedules a preview.
- Reset the selection binding after dismissal and recreate the picker
  via a reset token, so a stale selection can no longer leak into the
  next presentation and break its preview/send flow.
- Add verify.sh invariants for the picker state machine.
This commit is contained in:
wt committed 2026-08-29 02:17:03 +07:00
1 parent 4566b59ff1
commit b54113f0e4
2 files changed
+23 -8

No files matched your search

+15 -8
View File
@@ -68,6 +68,7 @@ struct ChatView: View {
@State private var captureAttemptID = UUID()
@State private var captureStartTask: Task<Void, Never>?
@State private var captureSuspendsArchiveSync = false
@State private var pickerResetToken = UUID()
@State private var timelineEntries: [ChatTimelineEntry] = []
/// Messages of this conversation straight from the SwiftData store,
@@ -125,6 +126,7 @@ struct ChatView: View {
_captureAttemptID = State(initialValue: UUID())
_captureStartTask = State<Task<Void, Never>?>(initialValue: nil)
_captureSuspendsArchiveSync = State(initialValue: false)
_pickerResetToken = State(initialValue: UUID())
_timelineEntries = State<[ChatTimelineEntry]>(initialValue: [])
}
@@ -226,10 +228,20 @@ struct ChatView: View {
matching: .any(of: [.images, .videos]),
preferredItemEncoding: .current
)
// A fresh picker identity per attempt: clearing the selection while
// the picker is presented desynchronizes its internal state, and the
// stale selection then leaks into the next presentation.
.id(pickerResetToken)
.onChange(of: showingMediaPicker) { _, isPresented in
if !isPresented {
schedulePendingAttachmentPreview()
}
guard !isPresented else { return }
// On Done the binding holds the chosen items; on Cancel SwiftUI
// restores the pre-presentation value (kept empty below), so
// staging runs only for a confirmed selection.
let items = pickedMediaItems
pickedMediaItems = []
pickerResetToken = UUID()
guard !items.isEmpty else { return }
Task { await stagePickedMedia(items) }
}
#if os(iOS)
.fullScreenCover(
@@ -259,10 +271,6 @@ struct ChatView: View {
.ignoresSafeArea()
}
#endif
.onChange(of: pickedMediaItems) { _, items in
guard !items.isEmpty else { return }
Task { await stagePickedMedia(items) }
}
.onChange(of: draft) { _, text in
model.updateComposerActivity(text, in: liveConversation)
}
@@ -1364,7 +1372,6 @@ struct ChatView: View {
var sourceURLs: [URL] = []
var failedItemCount = 0
defer {
pickedMediaItems = []
isPreparingAttachments = false
sourceURLs.forEach(discardPickedMediaFile)
}