Add Workspaces feature for clipboard isolation#1417
Open
naman-ajmera wants to merge 1 commit into
Open
Conversation
naman-ajmera
marked this pull request as ready for review
June 7, 2026 17:25
2 tasks
Author
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.
Summary
Adds a Workspaces feature to Maccy that lets users organize clipboard history into separate, isolated workspaces. This prevents cross-project clipboard pollution — items copied in one workspace never appear in another.
What it does
clearOnQuitonly clears the active workspace, preserving other workspaces' dataWhat it doesn't do (current limitations)
Reviewer Guide
New files (4)
Maccy/Models/Workspace.swift@Model— id, name, createdAt, sortOrder, items relationshipMaccy/Observables/WorkspaceManager.swiftMaccy/Views/WorkspaceTabsView.swiftMaccy/Settings/WorkspaceSettingsPane.swiftKey changes to existing files
Maccy/Models/HistoryItem.swiftworkspace: Workspace?relationship withinverse: \Workspace.itemsMaccy/Storage.swiftWorkspace.selfin ModelContainer; addedensureDefaultWorkspace()Maccy/Observables/History.swiftload(),findSimilarItem(),clear(),clearAll()scoped to active workspace; newclearEverything()Maccy/Observables/AppState.swiftworkspaceManager,isPopoverEditing; registered Workspaces settings paneMaccy/Observables/Popup.swiftworkspaceTabsHeight; updatedpreferredHeight()andresize()Maccy/Views/KeyHandlingView.swiftselectCurrentItemreturns.ignoredwhenisPopoverEditingMaccy/Views/ContentView.swiftWorkspaceTabsView()between header and history listMaccy/AppDelegate.swiftensureDefaultWorkspace()+WorkspaceManager.shared.load()at launch;clearOnQuitusesclearEverything()Design decisions worth understanding
Workspace identity via stable UUID — not
persistentModelID.hashValue. The active workspace ID is persisted inDefaultsas a UUID string and restored across launches.SwiftData
inverse:on one side only —HistoryItem.workspacedeclaresinverse: \Workspace.items;Workspace.itemshas@Relationship(deleteRule: .nullify)without inverse. This follows SwiftData documentation to avoid conflicts.#Predicateand static properties —#Predicatemacros cannot reference static properties directly.Storage.ensureDefaultWorkspace()capturesWorkspace.defaultNameinto a local variable before use in the predicate.clearEverything()fetches from DB directly — At quit time,History.allmay not be loaded. The method fetches items matching the active workspace ID from storage rather than relying on in-memory state.findSimilarItemis workspace-scoped — Prevents a duplicate in Workspace A from removing an identical item in Workspace B. This is critical for workspace isolation.Per-method
@MainActor—WorkspaceManageruses@MainActoron individual methods (not the class) to match the codebase pattern seen in other observables.Rename popover coordination — A single
.onChange(of: showRenamePopover)drivesappState.isPopoverEditing, whichKeyHandlingViewchecks to avoid swallowing Enter during rename.Localization pattern — Popup views use bare keys in
Localizable.strings; settings panes usetableName:-scoped.stringsfiles (WorkspaceSettings.strings). This matches the existing codebase convention.clear()/clearAll()refactored — Replacedcontext.delete(model:)bulk deletes with per-item deletion loops. The bulk API deleted across all workspaces; per-item deletion respects workspace scope sinceHistory.allonly contains active workspace items.