Skip to content

feat: Add Tag Feature#1356

Open
batuyilmazer wants to merge 7 commits into
p0deje:masterfrom
batuyilmazer:master
Open

feat: Add Tag Feature#1356
batuyilmazer wants to merge 7 commits into
p0deje:masterfrom
batuyilmazer:master

Conversation

@batuyilmazer
Copy link
Copy Markdown

This PR is a complete rewrite of the tagging feature originally proposed in #1350, implementing all feedback from @weisJ.

Implemented: all three requested features

1. Colored tag dots in the history list

Each history item displays up to 3 Finder-style colored circles (8 px, macOS system colors)
next to its title. A +N overflow indicator appears for items with more than 3 tags.
Colors are assigned per tag name in Settings → Tags.

2. Tag button in the preview pane toolbar

A tag button (default shortcut ⌥T, configurable in Settings → General) has been added
to the toolbar alongside the existing pin and delete buttons. It opens a TagInputView
overlay with live autocomplete of existing tag names, keyboard navigation (↑↓), Enter to
confirm, and Escape to cancel.

3. Tag search with #tag syntax

Typing #tagname in the search bar filters items to those with that tag. Typing # alone
shows all tagged items. Tag filtering composes with all existing search modes
(exact, fuzzy, regex, mixed).


On NSTokenField vs #tag

@weisJ suggested using NSTokenField for tokenised searching. After investigation, this
was not adopted:

  • Maccy's search bar is a SwiftUI TextField. Wrapping NSTokenField via
    NSViewRepresentable would introduce an AppKit seam into an otherwise fully SwiftUI
    view hierarchy, adding significant coordinator complexity.
  • #tag follows a convention already familiar to most users (GitHub, Slack, Notion,
    Bear, etc.) and is more memorable than a key–colon prefix.
  • tag: syntax would be parsed as plain text until the colon is typed, causing
    spurious search results mid-input. # is unambiguous from the first keystroke.
  • Search.ParsedQuery is intentionally architected to support additional token types
    (e.g. type:image, date:today) in the future — the same extensibility the
    NSTokenField approach was meant to enable, without the AppKit dependency.
  • A dedicated TagInputView overlay handles tag assignment separately from searching,
    which avoids overloading the search field with two responsibilities.

Open to revisiting if there is a strong preference for the native AppKit component.


Settings

Pane What's new
Settings → Tags Create/rename/delete tags, assign a system color. Rename/delete cascade to all history items.
Settings → Pins Tags column — edit tags directly on pinned items.
Settings → General Configurable keyboard shortcut for the tag action (default ⌥T).

Data model & migration

HistoryItem gains a tags: [String] field. Existing items migrate automatically with an empty tag list. Tag colours are stored in UserDefaults.


Tests

  • TagColorTests — all 8 color cases, Codable, Identifiable, dictionary serialization.
  • SearchTests#tag filter, # (any-tag filter), tag + text composition.

Related issues

Closes / addresses: #254, #176, #581, #156, #40

Screenshots

screenshot-18 12 19 screenshot-18 13 55 screenshot-18 14 19 screenshot-18 14 25 screenshot-18 13 21 screenshot-18 16 35

@batuyilmazer
Copy link
Copy Markdown
Author

Improved UI for tag autocomplete pane, now it overlaps (but still readable) with history items.
(Before that, It was pushing history items down)
image

@mithunsridharan
Copy link
Copy Markdown

mithunsridharan commented Mar 22, 2026

Nice and cool feature. The tags work beautifully. However, in my local tests, the pin shortcuts seem to be broken i.e. the pin shortcuts don't work anymore when selected, e.g. CMD+K or so. The line:

if !searchFocused, let item = appState.history.pressedShortcutItem

seems to be the origin of this issue. Rolling back to the original version retains the expected behavior:

if let item = appState.history.pressedShortcutItem

Also, the previous database entries are deleted (I understand why this may be necessary). If there's a way to maintain compatibility with the existing structure, that would be great! Happy to test and share feedback on this cool feature.

@batuyilmazer
Copy link
Copy Markdown
Author

Nice and cool feature. The tags work beautifully. However, in my local tests, the pin shortcuts seem to be broken i.e. the pin shortcuts don't work anymore when selected, e.g. CMD+K or so. The line:

if !searchFocused, let item = appState.history.pressedShortcutItem

seems to be the origin of this issue. Rolling back to the original version retains the expected behavior:

if let item = appState.history.pressedShortcutItem

Also, the previous database entries are deleted (I understand why this may be necessary). If there's a way to maintain compatibility with the existing structure, that would be great! Happy to test and share feedback on this cool feature.

TYSM for your feedback! I committed your fix on fix: Pin shortcuts not working.

I am open to further feedbacks & recommendations

@mithunsridharan
Copy link
Copy Markdown

mithunsridharan commented Apr 2, 2026

The fix works and I can select the pins. One trivial issue, though:

This code in HistoryItem in Maccy/Models folder is unnecessary, I feel.
if let tagKey = KeyChord.tagKey, let character = Sauce.shared.character(for: Int(tagKey.QWERTYKeyCode), cocoaModifiers: []) { keys.remove(character) }
The tagging feature is linked to the Option key. The above code removes the possibility to assign the shortcut: "t" to a pin and subsequently, select with CMD+t.

I've commented out these lines in my local build and this feature works dandy with the CMD+t assignment.

@mithunsridharan
Copy link
Copy Markdown

Close, but no cigar. When this PR is merged and the app used for a while, the popup panel size shrinks until only a single top-most pin is visible. I've rolled back this feature in my local compilation, but would be happy to share some screenshots the next time I try replaying the PRs in my local instance.

@batuyilmazer
Copy link
Copy Markdown
Author

Thanks for the feedback! I've pushed a fix for the shortcut key conflict (the unnecessary keys.remove(character) block) in my latest commit. I'm also actively working on backward compatibility for existing database entries — will keep you posted once that's ready.

As for the panel shrinking issue, I'm currently trying to reproduce it on my end.

@batuyilmazer
Copy link
Copy Markdown
Author

I've fixed the database migration issue. As symptom of migration fails, history items were being cleaned. The previous destructive behaviour (drop & recreate on failure) was a shortcut I used during development to speed up iteration — not intended as a permanent solution.

This update brings a proper fix:

  • Legacy schema identifiers now match the actual pre-tag store layout, so SwiftData can recognize and migrate existing databases correctly.

  • The destructive recovery branch is gone — migration failures now preserve the user's data instead of silently wiping it.

  • Added a dedicated migration regression test suite that creates a temp legacy store, migrates it, and asserts all fields + contents are preserved with tags == [].

Existing clipboard history should be preserved on upgrade now.

@weisJ
Copy link
Copy Markdown
Collaborator

weisJ commented Apr 15, 2026

Open to revisiting if there is a strong preference for the native AppKit component.

I would really like this to use an NSTokenField implementation using a NSViewRepresentable to make it feel as native as possible. Some other remarks.

  • To save space the tags could overlap a little like in Finder:
image - I don't think a shortcut for the tag is necessary. The tags setting can sit inside the new slideout sidebar.

@weisJ weisJ self-assigned this Apr 15, 2026
@mithunsridharan
Copy link
Copy Markdown

Hi @batuyilmazer ,

Did some tests today, but nothing positive to report!
The appearance is still garbled and overtime, unusable!

Perhaps, the inputs @weisJ share might remediate these inconsistencies!
Also, consolidating tags as suggested is more "modern". Please explore!

Not a QA professional, but happy to share feedback to see this feature through to the main branch!
This opens up several possibilities, IMO! Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants