File ▸ Share… stages a board as a zip and hands it to the system share sheet

Duplicate's own posture — a faithful copy, `.git` the sole exclusion, attachments/comments/trash
carried verbatim — staged to a temp directory (BoardShareStager, ditto-zipped via
DittoZipArchiver) and presented through NSSharingServicePicker (BoardSharePresentation), anchored
to the board window's toolbar or its center. Follows Duplicate/Save as Template's flush-then-
cancellable-copy sequence under the banner's in-progress row (ShareBoardCommand, AppCommands.swift),
menu-validated on focus alone rather than the read-only lock (a share is a read, Print's own
posture) with the one carve-out an open inline title editor still needs. WriteOperation gains
.shareBoard for the banner vocabulary; 11-command-nexus.md's File menu table gains the row.

Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
This commit is contained in:
2026-08-09 02:13:54 -04:00
parent 05bbf78926
commit da5d310673
10 changed files with 997 additions and 15 deletions
+6
View File
@@ -917,6 +917,12 @@ public final class BannerCenter {
// pressed and the board is still exactly as it was: nothing was saved *over*, and the
// failure is about the copy in the templates folder, not about this board's own files.
if let title { "Couldn't save '\(title)' as a template" } else { "Couldn't save the board as a template" }
case let .shareBoard(title):
// The command's own word (File Share), on `.saveAsTemplate`'s reasoning: the board
// itself is untouched by a share that fails partway nothing here was saved *over*
// so the sentence is about the staged copy that never reached the picker, not about
// this board's own files.
if let title { "Couldn't share '\(title)'" } else { "Couldn't share the board" }
case let .importAttachment(filename):
"Couldn't import '\(filename)'"
case .listAttachments: