Add editable untitled Markdown previews - #244
Conversation
* Design untitled Markdown preview workflow * Plan untitled Markdown preview implementation * Ignore local implementation worktrees * Add editable untitled Markdown previews * Keep folder opens out of the untitled placeholder editor * Stop folder windows from becoming untitled editors * Save untitled Markdown documents * Preserve edits across untitled saves Co-authored-by: Cursor <cursoragent@cursor.com> * Open untitled Markdown drafts by default Co-authored-by: Cursor <cursoragent@cursor.com> * Preserve explicit Open during launch Co-authored-by: Cursor <cursoragent@cursor.com> * Allow creating untitled Markdown documents * Harden untitled Markdown workflows * remove(docs): delete outdated untitled markdown preview plans and specs - Removed `2026-07-27-untitled-markdown-preview.md` due to completion and integration - Removed `2026-07-27-untitled-markdown-preview-design.md` as it's no longer needed - Ensured all related tasks and goals are reflected in current documentation --------- Co-authored-by: Beckett_quik <beckett@quikturn.io> Co-authored-by: Cursor <cursoragent@cursor.com>
|
Think it's almost there- a good starting point |
There was a problem hiding this comment.
Pull request overview
Adds an “untitled, editable” document lifecycle so Markdown Preview can launch without a file and still provide a source editor + rendered preview workflow, including saving drafts to disk, while ensuring folder-navigation windows don’t accidentally become editors.
Changes:
- Create and manage URL-less (“Untitled”) documents that can enter edit mode, preserve draft contents across mode switches, and later adopt a chosen save location.
- Harden edit-mode transitions and window/menu behaviors (focus editor on reveal, prevent stacked save panels, and handle “folder-only” navigator windows distinctly).
- Update app/document metadata and misc housekeeping (document role, gitignore entries).
Reviewed changes
Copilot reviewed 6 out of 7 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| md-preview/SidebarViewController.swift | Avoids unmounting an explicitly opened folder when displaying an untitled (URL-less) document. |
| md-preview/MarkdownDocument.swift | Allows replacing markdown without requiring a file URL (supports untitled drafts). |
| md-preview/MainSplitViewController.swift | Improves edit overlay lifecycle (focus on reveal; guard for early exit before fade-in completes). |
| md-preview/DocumentWindowController.swift | Core untitled draft + Save/Save As workflow, save-panel gating, and folder-window vs draft-window behavior. |
| md-preview/AppDelegate.swift | Schedules creation of a new untitled document on launch/reopen instead of prompting for a file. |
| Info.plist | Marks the document type role as Editor to reflect new edit capability. |
| .gitignore | Ignores local design/tooling directories. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Thanks man, this is a good start |
…for markdown files - Modified `allowedContentTypes` to include additional markdown file extensions from `Self.markdownFileExtensions` - Ensures flexibility in handling various markdown file types - Maintains consistency with existing functionality while enhancing extensibility
| func windowWillReturnUndoManager(_ window: NSWindow) -> UndoManager? { | ||
| isEditing ? nil : tableUndoManager | ||
| tableUndoManager | ||
| } |
Edit ▸ Undo and Redo were dead while editing: the nib items resolved through the responder chain to the window's undo manager, which knows nothing about CodeMirror's history, so ⌘Z did nothing (or, worse, reached the preview's table undo stack). The editor now reports its history depth across the WebKit bridge, and the menu items are retargeted to route to whichever undo client actually owns the keystroke: - `undoWindow` resolves the owning window as `keyWindow ?? mainWindow`, then prefers an `attachedSheet`, so Undo can never reach past a sheet into the document blocked behind it. - `focusedEditorForUndo` claims Undo for CodeMirror while editing unless a field editor (`NSTextView`) is first responder, which keeps the toolbar search field's own undo stack intact. - Otherwise the window's own responder chain answers, exactly as AppKit would have — that path also supplies the per-action menu titles. Asking the responder chain directly means there is no focus state to mirror between JavaScript and Swift and no notification to keep in sync. Known tradeoff: the retargeted items no longer forward `undo:` into the out-of-process save panel, so ⌘Z does not undo typing in its filename field. That is system UI with no document risk, and the item correctly reports itself disabled there. Verified on an instrumented build: editor focused routes to CodeMirror (one step per invocation); ⌘F with or without typing routes to the field editor and leaves the document untouched; a Save As sheet routes to the panel, not the document. 94 Swift helper tests and 67 editor-bundle checks pass.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 11 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (3)
md-preview/DocumentWindowController.swift:1661
EditedMarkdownSaveResultwas changed tocase saved(rerendered: Bool), but this switch still matchescase .saved:which will not compile. Update the pattern to ignore/bind the associated value.
This issue also appears in the following locations of the same file:
- line 1715
- line 1821
guard let self else { return }
switch result {
case .saved:
self.currentMarkdown = updated
md-preview/DocumentWindowController.swift:1719
- This switch still uses
case .saved:even thoughEditedMarkdownSaveResult.savednow has an associated value (rerendered). This is a compile-time error; match.saved(rerendered: _)(or bind the bool if you need it).
guard let self else { return }
switch result {
case .saved:
self.currentMarkdown = updated
if let url = self.currentFileURL {
md-preview/DocumentWindowController.swift:1824
- This
switch resultstill includes acase .saved:branch earlier in the same switch, butEditedMarkdownSaveResult.savednow has an associated value (rerendered). That branch needs to match.saved(rerendered: _)(or bind the bool), otherwise the file won’t compile.
fileURL: fileURL
)
self.renderCurrentDocument(text: externalMarkdown, fileURL: fileURL)
case let .failed(error):
Summary
Adds workflow for previewing raw Markdown without creating a file first.
Now opens an editable untitled document when launched without a file. Users can paste Markdown directly from clipboard, switch to the existing full rendered preview, return to the source editor without losing content, and optionally save the draft as a Markdown file.
Reuses app’s existing editor and renderer. Does not add a second parser, rendering surface, temporary-file layer, or separate Quick Look implementation.
Motivation
Previously, the renderer was effectively gated behind a local file URL. This was inconvenient when Markdown already existed in the clipboard ie content copied from GPT, Claude, mail, messages, notes, docs, or terminal.
Reviewing that Markdown required manually creating a file, saving it, and reopening it in Markdown Preview.
The renderer itself was already capable of rendering an in-memory
String; the missing piece was an untitled document lifecycle around it.Verification
Automated
Result:
swift test --package-path tests/swift-testsResult: