New tool: Snippets - #10
Merged
Merged
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a new standalone menu-bar helper, Snippets, implemented in DMonteCore and wired into the Toolbox catalog/build/packaging/docs. Snippets provides a searchable, ordered library of reusable text with optional placeholder expansion and a paste-into-previous-app flow that reuses the existing ClipboardPaste/activation-tracking infrastructure.
Changes:
- Introduces the Snippets feature end-to-end: SwiftUI popover UI, controller/view-model, snippet model + filtering/placeholder expansion utilities, and a debounced JSON persistence store with flush-on-terminate.
- Adds a new helper app target (
DMonteSnippets) with its own app delegate, single-instance behavior, and distributed notification “reopen” support. - Refactors
ClipboardPasteto expose a sharedpasteText(_:sourceBundleID:into:)entry point and updates docs/build/packaging metadata accordingly.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| Tests/DMonteCoreTests/SnippetsKitTests.swift | Adds focused unit tests for filtering, placeholder expansion, row-label bounding, reordering, and persistence/flush behavior. |
| Sources/DMonteSnippetsApp/SnippetsAppDelegate.swift | New helper app delegate: panel host + status item wiring, activation tracking, key monitoring, and terminate-time flush/cleanup. |
| Sources/DMonteSnippetsApp/main.swift | New helper entry point with SingleInstanceGuard and --open distributed-notification behavior. |
| Sources/DMonteCore/ToolboxCatalog.swift | Registers the new “Snippets” tool in the toolbox catalog. |
| Sources/DMonteCore/SnippetsView.swift | Implements the Snippets popover UI (search, list, editor overlay, settings overlay, accessibility notice). |
| Sources/DMonteCore/SnippetsStore.swift | Adds debounced, generation-guarded JSON persistence for the snippets library with owner-only permissions and terminate-time flush. |
| Sources/DMonteCore/SnippetsSizing.swift | Provides panel sizing/scale logic consistent with other tools’ scaled panels. |
| Sources/DMonteCore/SnippetsKit.swift | Defines Snippet plus pure logic for filtering, placeholder expansion, reordering, and JSON encode/load helpers. |
| Sources/DMonteCore/SnippetsController.swift | Adds the Snippets view-model: search/selection, paste orchestration, editing sessions, and key handling. |
| Sources/DMonteCore/ClipboardPaste.swift | Adds pasteText helper and funnels both entry-paste and snippet-paste through the shared activation/⌘V synthesis path. |
| Scripts/package_app.sh | Includes the new Snippets helper in the packaging pipeline. |
| README.md | Updates tool count and documents Snippets + its Accessibility permission needs. |
| Packaging/SnippetsInfo.plist | Adds the helper app Info.plist for Snippets. |
| Package.swift | Adds the DMonteSnippets executable target/product. |
| CHANGELOG.md | Documents Snippets under Unreleased. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
New menu bar helper (DMonteSnippets, com.havokentity.mactools.snippets):
a searchable library of reusable text. Clicking a snippet — or pressing
Return on the top search hit — copies it and pastes it into whatever app
the user was working in before the panel appeared, which is the same
problem Clipboard History already solved, so the paste target comes from
ActivationTracker via the panel host's onWillShow hook and the ⌘V
synthesis is shared rather than reimplemented: ClipboardPaste grows a
pasteText(_:sourceBundleID:into:) alongside the existing entry paste, and
both now funnel through one permission check. Missing Accessibility is
reported honestly in the panel and in Settings — the text still reaches
the clipboard, so the tool degrades to copy-only instead of failing
silently.
Snippets are created, edited, reordered and deleted in the popover and
persist as JSON in Application Support with owner-only permissions,
since a snippet library holds exactly the private boilerplate
(addresses, account numbers, signatures) that should not be world
readable. Writes follow ClipboardStore's post-fix shape: debounced off
the main actor, serialized through a generation-checked writer so a
slow large snapshot cannot land after a newer small one and resurrect a
deleted snippet, and flushed at applicationWillTerminate so quitting
right after Save does not lose the snippet.
Placeholder expansion is deliberately a three-token substitution list
({date}, {time}, {datetime}) and not a templating language: anything
richer becomes a syntax the user must learn and the tool must version.
It is opt-out per snippet so text that legitimately contains braces can
be pasted verbatim, and the editor's help string is generated from
SnippetsKit.placeholders so the documentation cannot drift from what is
actually substituted.
Filtering, expansion, reordering and the JSON round-trip live in
SnippetsKit; SnippetsKitTests covers them plus the survives-a-quit
contract, driving a real store pointed at a temp directory.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ole documents
Reopening the panel with the editor still open stole focus back to the
search field. The editor deliberately survives a reopen so half-typed
snippet text is not discarded, but its overlay covers the search field,
so the unconditional refocus on showToken sent the user's next
keystrokes into a control they could not see — silently filtering the
list instead of typing the snippet. The panel is only ordered out, never
torn down, so simply not refocusing while an editor session exists
restores the editor field the window already held as first responder.
The list row's date badge was driven off body.contains("{"), which is a
different question from the one the badge answers: a snippet whose only
braces are {foo} expands to itself, so a pure code template was badged as
date-bearing while pasting byte-for-byte what was typed. Both the badge
and the expansion fast path now go through SnippetsKit.containsPlaceholder,
and Snippet.pastesADifferentValueEachTime names the property the badge
actually wants, so icon and pasteText(for:) cannot drift apart. A test
asserts the two agree for expanding, opted-out, unknown-token and
brace-free snippets.
displayTitle split the entire body into lines to read the first one and
previewText copied the whole body to flatten it, both on every row
render, for two lineLimit(1) labels. A snippet body is whatever the user
pasted into the editor, so that is unbounded work — and a megabyte-long
string handed to a single-line label is laid out in full. Both now read
through a bounded previewSource that skips leading blank space and stops
at 200 characters. Skipping leading blank space also fixes a snippet
whose text begins with an empty line rendering as "Untitled".
Also corrects the .sortedKeys comment, which justified itself with a
byte-identical-write check that neither this store nor ClipboardStore
has.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
havokentity
force-pushed
the
feat/snippets
branch
from
July 18, 2026 11:00
fb20172 to
6f0109c
Compare
Three separate defects, all reported from a real install.
**Return dismissed the panel and did nothing.** `paste(_:)` closed the panel
first — correct, since focus has to be back in the target app before ⌘V is
synthesized — then set `needsAccessibility` when the paste failed. That flag
drives a banner on a panel the user can no longer see, so a missing
Accessibility grant was indistinguishable from "Return just closes the
window". The permission is now checked *before* dismissing: without it the
panel stays open, the banner shows, and the system prompt appears.
**Reorder pasted instead of reordering.** The row carried
`.onTapGesture { onPaste() }` over its whole width, and the reorder and delete
buttons are nested inside that row, so tapping Move Up ran the row's paste
action — which closes the panel. The tap now covers only the icon, the labels
and the gap; the action buttons sit outside it.
**⌘C/⌘V/⌘A/⌘Z did nothing in the editor.** No helper in this suite installs a
main menu, on the reasonable-sounding theory that an `LSUIElement` app has no
menu bar to show one in. But AppKit resolves those shortcuts by matching the
event against the main menu's key equivalents before any view sees them, so
with no menu they are silently dead in every text field. `HelperMainMenu`
installs a never-drawn Edit menu whose items use nil targets, so each one
travels the responder chain to the focused text view.
That last one is shared infrastructure and this only wires it into Snippets.
Every other tool with a text field — Scratchpad, Dev Tools, QR, the Clipboard
search field — has the same dead shortcuts and needs the same one-line call.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…aste target
**⌘↑ / ⌘↓ moved the selection instead of the snippet.** The guard was
`flags == .command`, but arrow keys always report `.numericPad` and
`.function` alongside whatever the user held, so that equality is never true
for an arrow and both shortcuts fell through to the plain-arrow cases below.
Compare only the modifiers a shortcut cares about — command, shift, option,
control — and ignore the rest.
**The row buttons were an 18pt target with three neighbours.** Missing one
landed on the row behind, which pastes and dismisses the panel. Hit area is
now 26x24 with an explicit contentShape; the glyph itself only goes 11pt to
12pt, so the row does not get taller.
**⏎ looked like it did nothing.** It copies the snippet, dismisses, and
synthesizes ⌘V into the app that was frontmost beforehand — but if that app
has no focused text field, nothing visibly happens and the panel just closes.
The footer now names the destination ("⏎ Paste into Safari"), or says "Copy"
when no target was resolved, so the outcome is predictable before the key is
pressed rather than mysterious afterwards.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bare ⌘↑ / ⌘↓ collide with Mission Control and space-switching shortcuts on many setups, and a system-reserved shortcut is consumed before the app's local key monitor ever sees the event — so the reorder key went to Mission Control and the tool could not intercept it. Moved reorder to ⌥⌘↑ / ⌥⌘↓, the common move-item convention, which no default system shortcut claims. Footer hint and button tooltips updated to match. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
# Conflicts: # CHANGELOG.md # Package.swift # README.md # Scripts/package_app.sh # Sources/DMonteCore/ToolboxCatalog.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
New menu-bar tool: a library of reusable text snippets you can paste instantly.
ClipboardStoreshape — debounced off the main actor, write generation claimed inside the serial queue, flushed at terminate — so it does not reintroduce the unserialised-write bug that file was recently fixed for.ClipboardPaste+ActivationTrackervia the panel host'sonWillShowhook.ClipboardPastewas refactored so both entry-paste and snippet-paste funnel through one permission-check/activate/synthesize path. Missing Accessibility permission is surfaced honestly, as Clipboard History does.{date},{time}) — deliberately not a templating language.Wired through every registration point;
ToolRegistrationConsistencyTestspasses.Tests: 428 → 456, all passing. Persistence tests use a per-test temp directory, never the real Application Support.
Reviewer notes
The adversarial pass fixed several real issues, including two performance traps:
displayTitlesplit the entire body to read one line andpreviewTextcopied the whole body to flatten newlines — on every row render, for twolineLimit(1)labels. A megabyte snippet handed a megabyte string to a single-line label. Both now read through a bounded 200-character window.Three pre-existing quirks were deliberately not changed here because they are house-wide patterns shared with Clipboard/CleanDrive/FocusTimer/etc. (settings overlay state not reset on close;
handleKeyunaware of the overlay; first ↓ from a nil selection landing on the second row). Making Snippets the odd one out would be worse than one follow-up that fixes all the tools together.🤖 Generated with Claude Code