3180e565e8c50f888abe847389057a529b4b64ed
2
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
9e6f4567df |
Print: entitle the sandbox, and stop the ⌘P chord from ever falling through
Root cause of the owner's repro (board window frontmost, File ▸ Print… enabled, chosen from the menu, alert appears anyway): Kanban.entitlements carried no com.apple.security.print key. The app is sandboxed, and a sandboxed NSPrintOperation is denied by the sandbox with exactly this wording — "This application does not support printing. Please contact the application's developer." — regardless of which code path invokes it. Fix: add the entitlement. Alongside it, hardening for a separate, narrower failure mode that happens to produce the identical alert text by a different mechanism: PrintCommand used to disable itself over a window that published neither a board nor a printable card (welcome, the template chooser, Settings, the restore-bootstrap window, a card window whose board hasn't joined). A disabled SwiftUI Button still owns its .keyboardShortcut, so the unclaimed ⌘P chord fell through to AppKit's own nil-target printDocument: action, whose stock failure is the same system alert. The row now claims ⌘P unconditionally in every window; scope resolves at the moment of the action instead (board, then card, then a polite "Nothing to Print" / "Open a board or a card to print it." refusal in the app's own voice). The boolean isEnabled(hasBoard: hasPrintableCard:) becomes a three-way PrintCommand.resolveScope(...) -> Scope pure function. Also implements AppDelegate's application(_:printFiles:withSettings: showPrintPanels:) — Finder's own File ▸ Print… / drag-to-printer / print-and-open path was previously unhandled, its own separate route to the same stock alert. PrintCoordinator.printFiles loads each path headless through BoardLoader (no store, no window) and either prints it or gives the same one-sentence refusal; the operation-building code shared with the in-app path is factored out of run(_:) into makeOperation(for:showsPrintPanel:) and runOperation(_:session:). Docs: 11-command-nexus.md's Print row, PrintCommand's and PrintCoordinator's doc comments, KanbanApp.swift's CommandGroup comment, and project.yml's entitlements comment all narrate the entitlement as the actual fix and the scope work as hardening beside it. Tests: PrintCommandValidationTests now exercises resolveScope's three arms in place of the old boolean. A new PrintFinderResolutionTests suite covers PrintCoordinator.resolveFinderPrint(atPath:) — the one piece of the Finder half a test can drive without handing AppKit a real print job — against a real board, an empty non-board folder, a plain file, and an unsupported schema. Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy |
||
|
|
7651e40318 |
Print boards and cards with configurable components and named print profiles
⌘P had no story: KanbanApp removed the platform's Print row outright on 11-command-nexus.md's "No Print story in v1 (⌘P unused)" line. That line retires. File ▸ Print… now prints the board in front — lanes left to right, each lane's cards top to bottom, as a linear document rather than a picture of the strip — or, from a card window, that card. The trash is unreachable by construction: it is a sibling container of `lanes`, not a lane. The rules live in a pure layer nothing AppKit can reach. `PrintOptions` is one Codable value carrying the printing card's five bullets — which components (title, icon+labels line, rendered body, comments off by default with either reading order), page breaks, one base face and size every other size derives from, and a toggleable running head and foot. `PrintSource` is what is being printed, frozen at ⌘P so the panel's repeated relayouts and a board reloading underneath cannot disagree. `PrintDocumentBuilder` turns the pair into a block list, which is where every decision a rendered page hides becomes something a test can hold: component order, comment ordering, and page-break markers that are markers rather than whitespace. Empty is empty all the way up — a card with nothing to print consumes no page break, and a lane whose cards all dropped out takes its heading with it. A page break is a pagination fact, not a spacing one. TextKit has no page-break character, so `PrintDocumentView` splits the document into sections at its breaks and flows each into as many page-sized text containers as it needs: a container boundary *is* a sheet boundary, at any paper size with any margins. Bodies come from the app's one Markdown pass — `BodyMarkup.parse` into `BodyMarkupRenderer` — re-faced run by run so the chosen family reaches the text and fixed-pitch code keeps its own, and drawn under a forced light appearance so the card window's dynamic label colours do not print white. Options ride in the print panel's own accessory rather than a pre-flight sheet of ours, which buys the system's live preview of the real paginated document; the preview refreshes through one KVO revision counter rather than thirteen mirrored properties. Profiles persist app-side in UserDefaults, never in board files — a print profile is how this user likes to read, not what a board is (`BoardZoomStore`'s argument). A name is a profile's identity, folded case-insensitively; "Last Used" is reserved in every spelling, kept out of the stored list, and captured when an operation actually ran, so a cancelled print rewrites nothing. Both decoders are total: one unrecognized key must not cost a user every profile they saved. DESIGN/11-command-nexus.md gains the Print row and loses the sentence saying it would never have one. Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy |