fix(collections): document context menu targeting + off-main AppKit calls
Sidebar document rows lived inside the collection's single List row — on macOS, a List row's own context menu wins over any nested .contextMenu deeper in that row's content, so right-clicking a document always showed the collection's menu. Swapped List for a plain ScrollView/LazyVStack (every row already does its own selection highlighting, so List wasn't buying anything here). Also pins the new document-action views to @MainActor: Download/Print call NSSavePanel/NSPrintOperation/NSPasteboard after an await, and without a fixed actor those functions could resume on a background executor — off-main AppKit calls, which is what was producing the ViewBridge/nw_connection console spam.
This commit is contained in:
@@ -66,6 +66,12 @@ struct CollectionDocumentsOutline: View {
|
||||
}
|
||||
}
|
||||
|
||||
/// `NSSavePanel`/`NSPrintOperation`/`NSPasteboard` in the action functions
|
||||
/// below must run on the main thread — without pinning this to `@MainActor`,
|
||||
/// a `Task` launched from a menu action can resume on a background executor
|
||||
/// after its first `await`, and calling those APIs off-main is what produced
|
||||
/// the ViewBridge/`nw_connection` console spam (and worse, silent failures).
|
||||
@MainActor
|
||||
private struct DocumentNodeRow: View {
|
||||
let apiClient: OutlineAPIClient
|
||||
let node: DocumentNode
|
||||
|
||||
@@ -65,20 +65,31 @@ struct CollectionsTreeView: View {
|
||||
description: Text("Collections you have access to will appear here.")
|
||||
)
|
||||
} else {
|
||||
List {
|
||||
ForEach(viewModel.collections) { collection in
|
||||
CollectionTreeRow(
|
||||
apiClient: viewModel.apiClient,
|
||||
collection: collection,
|
||||
isExpanded: expandedCollectionIDs.contains(collection.id),
|
||||
isSelected: selectedCollection?.id == collection.id,
|
||||
selectedDocumentID: selectedDocumentID,
|
||||
onToggle: { toggle(collection) },
|
||||
onSelectDocument: { chain in onSelectDocument(collection, chain) },
|
||||
onSearchInCollection: onSearchInCollection,
|
||||
onCollectionsChanged: { await viewModel.load() }
|
||||
)
|
||||
// Plain `ScrollView`/`LazyVStack`, not `List`: on macOS, a
|
||||
// `List` row's context menu wins over any `.contextMenu`
|
||||
// nested deeper inside that row's content (right-clicking a
|
||||
// document row inside an expanded `CollectionTreeRow` showed
|
||||
// the collection's menu instead of the document's) — every
|
||||
// row here already does its own selection highlighting, so
|
||||
// `List` wasn't buying anything but that bug.
|
||||
ScrollView {
|
||||
LazyVStack(alignment: .leading, spacing: 2) {
|
||||
ForEach(viewModel.collections) { collection in
|
||||
CollectionTreeRow(
|
||||
apiClient: viewModel.apiClient,
|
||||
collection: collection,
|
||||
isExpanded: expandedCollectionIDs.contains(collection.id),
|
||||
isSelected: selectedCollection?.id == collection.id,
|
||||
selectedDocumentID: selectedDocumentID,
|
||||
onToggle: { toggle(collection) },
|
||||
onSelectDocument: { chain in onSelectDocument(collection, chain) },
|
||||
onSearchInCollection: onSearchInCollection,
|
||||
onCollectionsChanged: { await viewModel.load() }
|
||||
)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 4)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import OutlineKit
|
||||
/// Read-only revision list, backed by `revisions.list`. No diff/restore view —
|
||||
/// `revisions.list` omits body content for performance, and restoring is a
|
||||
/// bigger follow-up feature, not a one-off action.
|
||||
@MainActor
|
||||
struct DocumentHistorySheet: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import OutlineKit
|
||||
|
||||
/// Backed by `documents.insights` — server returns an error if insights
|
||||
/// aren't enabled on the document, surfaced like any other load failure.
|
||||
@MainActor
|
||||
struct DocumentInsightsSheet: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import OutlineKit
|
||||
/// Distraction-free reading view — no toolbar/sidebar chrome, larger type.
|
||||
/// Presentation is just a bigger render of the same markdown, not a real
|
||||
/// slide-by-slide deck (Outline's own "Present" isn't slide-based either).
|
||||
@MainActor
|
||||
struct DocumentPresentSheet: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import OutlineKit
|
||||
/// text instead of the styled reader view — the tradeoff is losing rich
|
||||
/// rendering in exchange for real match highlighting and scroll-to-match via
|
||||
/// `ScrollViewReader`, which wouldn't be possible against an opaque view.
|
||||
@MainActor
|
||||
struct DocumentSearchSheet: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import OutlineKit
|
||||
/// picking a destination deeper than one level (i.e. can't move under a
|
||||
/// grandchild) — kept intentionally shallow rather than mirroring the full
|
||||
/// sidebar tree in a picker.
|
||||
@MainActor
|
||||
struct MoveDocumentSheet: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
|
||||
Reference in New Issue
Block a user