Skip to content
Merged
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
61 changes: 61 additions & 0 deletions Sources/UI/AssistantDragReorder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import SwiftUI
import UniformTypeIdentifiers

/// Drag/drop reorder for assistant tiles. Uses the legacy `.onDrag` /
/// `.onDrop` API with the standard `public.text` UTI and an NSString
/// payload — the modern Transferable + custom-UTType path silently
/// dropped on macOS, and so did `.ownProcess` NSItemProvider visibility
/// + bespoke UTI. (List ancestor + Button wrapper + visibility +
/// custom UTI declaration each independently kill the drop.)
struct AssistantDragReorderModifier: ViewModifier {
let isEnabled: Bool
let assistantID: String
let onReorder: (String) -> Void

@State private var isTargeted = false

func body(content: Content) -> some View {
if isEnabled {
content
.overlay {
if isTargeted {
RoundedRectangle(cornerRadius: JinRadius.medium, style: .continuous)
.stroke(Color.accentColor, lineWidth: 2)
}
}
.onDrag {
NSItemProvider(object: assistantID as NSString)
}
.onDrop(of: [.text], isTargeted: $isTargeted) { providers in
guard let provider = providers.first else { return false }
let target = assistantID
let handler = onReorder
provider.loadObject(ofClass: NSString.self) { object, _ in
guard let nsString = object as? NSString else { return }
let sourceID = nsString as String
guard !sourceID.isEmpty, sourceID != target else { return }
DispatchQueue.main.async {
handler(sourceID)
}
}
return true
}
} else {
content
}
}
}

extension View {
func assistantDragReorder(
isEnabled: Bool,
assistantID: String,
onReorder: @escaping (String) -> Void
) -> some View {
modifier(AssistantDragReorderModifier(
isEnabled: isEnabled,
assistantID: assistantID,
onReorder: onReorder
))
}
}
29 changes: 29 additions & 0 deletions Sources/UI/ContentView+AssistantManagement.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,35 @@ extension ContentView {
assistantPendingDeletion = nil
}

/// Reorders `sourceID` so it skips past `targetID` in the direction it was
/// dragged: forward drags land the source immediately *after* the target,
/// backward drags land it immediately *before*. Returns `true` if the
/// order changed and was persisted.
@discardableResult
func reorderAssistant(sourceID: String, onto targetID: String) -> Bool {
guard sourceID != targetID else { return false }
var ordered = assistants
guard let sourceIdx = ordered.firstIndex(where: { $0.id == sourceID }),
let targetIdx = ordered.firstIndex(where: { $0.id == targetID }) else {
return false
}

let item = ordered.remove(at: sourceIdx)
ordered.insert(item, at: targetIdx)

let now = Date()
var changed = false
for (newOrder, assistant) in ordered.enumerated() where assistant.sortOrder != newOrder {
assistant.sortOrder = newOrder
assistant.updatedAt = now
changed = true
}
guard changed else { return false }

try? modelContext.save()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle save errors instead of silently ignoring them.

Using try? silently discards save failures, which means the function can return true even when changes weren't persisted. This creates a data-loss risk where the UI shows the new order but it's lost on app restart.

Proposed fix: propagate or log save errors
-        try? modelContext.save()
-        return true
+        do {
+            try modelContext.save()
+            return true
+        } catch {
+            print("⚠️ Failed to persist assistant reorder: \(error)")
+            return false
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try? modelContext.save()
do {
try modelContext.save()
return true
} catch {
print("⚠️ Failed to persist assistant reorder: \(error)")
return false
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/UI/ContentView`+AssistantManagement.swift at line 95, The silent
discard of save errors via "try? modelContext.save()" can make the function
return true even when persistence failed; replace it with a do-catch that calls
"try modelContext.save()" and either propagate the thrown error (make the
enclosing function throws) or catch and log the error (e.g., with os_log or a
logger) and return false on failure so callers know the save did not succeed;
update the surrounding function signature/return logic accordingly to ensure
failures are not ignored.

return true
}

// MARK: - Sidebar Layout Helpers

var displayedAssistants: [AssistantEntity] {
Expand Down
Loading
Loading