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 {
|
private struct DocumentNodeRow: View {
|
||||||
let apiClient: OutlineAPIClient
|
let apiClient: OutlineAPIClient
|
||||||
let node: DocumentNode
|
let node: DocumentNode
|
||||||
|
|||||||
@@ -65,7 +65,15 @@ struct CollectionsTreeView: View {
|
|||||||
description: Text("Collections you have access to will appear here.")
|
description: Text("Collections you have access to will appear here.")
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
List {
|
// 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
|
ForEach(viewModel.collections) { collection in
|
||||||
CollectionTreeRow(
|
CollectionTreeRow(
|
||||||
apiClient: viewModel.apiClient,
|
apiClient: viewModel.apiClient,
|
||||||
@@ -80,6 +88,9 @@ struct CollectionsTreeView: View {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.padding(.horizontal, 8)
|
||||||
|
.padding(.vertical, 4)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.task {
|
.task {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import OutlineKit
|
|||||||
/// Read-only revision list, backed by `revisions.list`. No diff/restore view —
|
/// Read-only revision list, backed by `revisions.list`. No diff/restore view —
|
||||||
/// `revisions.list` omits body content for performance, and restoring is a
|
/// `revisions.list` omits body content for performance, and restoring is a
|
||||||
/// bigger follow-up feature, not a one-off action.
|
/// bigger follow-up feature, not a one-off action.
|
||||||
|
@MainActor
|
||||||
struct DocumentHistorySheet: View {
|
struct DocumentHistorySheet: View {
|
||||||
@Environment(\.dismiss) private var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import OutlineKit
|
|||||||
|
|
||||||
/// Backed by `documents.insights` — server returns an error if insights
|
/// Backed by `documents.insights` — server returns an error if insights
|
||||||
/// aren't enabled on the document, surfaced like any other load failure.
|
/// aren't enabled on the document, surfaced like any other load failure.
|
||||||
|
@MainActor
|
||||||
struct DocumentInsightsSheet: View {
|
struct DocumentInsightsSheet: View {
|
||||||
@Environment(\.dismiss) private var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import OutlineKit
|
|||||||
/// Distraction-free reading view — no toolbar/sidebar chrome, larger type.
|
/// Distraction-free reading view — no toolbar/sidebar chrome, larger type.
|
||||||
/// Presentation is just a bigger render of the same markdown, not a real
|
/// 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).
|
/// slide-by-slide deck (Outline's own "Present" isn't slide-based either).
|
||||||
|
@MainActor
|
||||||
struct DocumentPresentSheet: View {
|
struct DocumentPresentSheet: View {
|
||||||
@Environment(\.dismiss) private var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import OutlineKit
|
|||||||
/// text instead of the styled reader view — the tradeoff is losing rich
|
/// text instead of the styled reader view — the tradeoff is losing rich
|
||||||
/// rendering in exchange for real match highlighting and scroll-to-match via
|
/// rendering in exchange for real match highlighting and scroll-to-match via
|
||||||
/// `ScrollViewReader`, which wouldn't be possible against an opaque view.
|
/// `ScrollViewReader`, which wouldn't be possible against an opaque view.
|
||||||
|
@MainActor
|
||||||
struct DocumentSearchSheet: View {
|
struct DocumentSearchSheet: View {
|
||||||
@Environment(\.dismiss) private var dismiss
|
@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
|
/// picking a destination deeper than one level (i.e. can't move under a
|
||||||
/// grandchild) — kept intentionally shallow rather than mirroring the full
|
/// grandchild) — kept intentionally shallow rather than mirroring the full
|
||||||
/// sidebar tree in a picker.
|
/// sidebar tree in a picker.
|
||||||
|
@MainActor
|
||||||
struct MoveDocumentSheet: View {
|
struct MoveDocumentSheet: View {
|
||||||
@Environment(\.dismiss) private var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user