Architecture & Internals
Architecture Overview & Boundaries
Target split between pure Core domain framework and OpenClip macOS platform integration.
On this page (10 sections)
OpenClip is engineered around a clean target split and single-responsibility architectural boundaries ("Architectural Subsystems"). This design guarantees strict separation between pure domain logic, platform-specific side-effects, dynamic action runtimes, and user interface components.
Target Split#
The codebase is split into two primary targets:
OpenClip Workspace
├── Core (Framework / Swift Package)
│ ├── Pure domain models (Action, ActionChrome, ActionResult, SelectionContext)
│ ├── Selection detection contracts (TextRetrieving, SelectionMonitoring)
│ ├── Central action catalog & ordering (ActionRegistry, ActionCoordinator)
│ ├── Action-search matcher & popup mode (ActionSearch)
│ ├── Strongly-typed settings engine (SettingsStore, SettingKey)
│ ├── Application policy rules (AppRule, RuleEngine)
│ └── Pure snippet & manifest parsing (OpenClipSnippetParser, ExtensionManifest)
│
└── OpenClip (macOS Application Target)
├── AppKit floating panels & SwiftUI UI (PopupPanel, PopupView, PreferencesView)
├── Action execution runtimes (JavaScriptAction, AppleScriptAction)
├── Platform side-effect handler (ActionResultHandler)
├── Action factory implementation (DefaultActionFactory)
├── macOS selection monitoring & retrieval (MacSelectionMonitor, SelectionRetrievalCoordinator + strategies)
└── App Composition Root (AppDelegate)Core Target Constraints#
- Zero AppKit / SwiftUI UI dependencies in
Sources/Core/Actions/orSources/Core/Settings/. - Pure Swift Types: Value types, protocols, and decoupled services.
- Dependency Injection: Core components requiring settings accept a
SettingsStoreinstance during initialization (defaulting toDefaultSettingsStore.shared). Builtin actions (SearchAction,CalendarAction,CalculateAction) take the store viaBuiltinRegistry.makeCoreBuiltins(settingsStore:);ActionCoordinatortakesregistry/ruleEngine/extensionManager/settingsStoreininit(default.shared). Display title/icon resolution goes through an injectedActionPresenting, never a hidden singleton inside theActionprotocol extension.
Core Architectural Subsystems#
OpenClip enforces a single entry point for each cross-cutting concern. Bypassing these single-responsibility interfaces is strictly prohibited.
Rendering diagram…
1. Settings Subsystem — SettingsStore#
- Responsibility: Centralized persistence and retrieval of application settings.
- Mechanism: Operates via strongly-typed
SettingKey<T>instances. - Strict Rule: Zero direct
UserDefaults.standardcalls anywhere inSources/. All access goes throughSettingsStorein Core via dependency injection orDefaultSettingsStore.sharedin the App target. (The only remaining raw access is the one-timeaiCloudAPIKeymigration inAIServiceManager— read-then-delete; the AI-config/theme@AppStoragesurface remains too. Migrating is ongoing — do not add new direct call sites.) Secrets (e.g. the cloud AI API key) live inSecretStore(~/.openclip/secrets.jsonwith POSIX 0600 permissions), never UserDefaults.
2. Action Presentation — ActionCustomizationManager#
- Responsibility: Resolves display titles and icons for specific UI surfaces (
.popupor.table). - Mechanism:
ActionCustomizationManager.presented(action, surface:)queries user customizations and falls back to action defaults;ActionPresentationModelcarries the resolved title/icon. - Strict Rule: UI code never computes display icons using type checks or string matches. Preferences table icons delegate to
ConfigurableAction.preferenceIconName.
3. Action Chrome Policy — ActionChrome#
- Responsibility: Exposes UI policy metadata for actions without logic coupling.
- Metadata:
badge:.none,.script,.url,.custom,.extensionPkg(String)rowStyle:.standard,.actionGrouppopupBehavior:.perform,.showSubActions,.provideCompletionssource:.builtin,.custom,.extensionPkg(packageID: String)
- Strict Rule: Views must inspect
action.chrome.badgeoraction.chrome.sourceinstead of checkingif action is ScriptActionor matchingaction.id.
4. Action Factory — DefaultActionFactory#
- Responsibility: Instantiating executable
Actionobjects from extension manifest JSON metadata or parsed script snippet headers. - Mechanism: Implements the
ActionFactoryprotocol in the App target where JavaScript, AppleScript, and process runtimes are available.
5. Action Result Handler — ActionResultHandler#
- Responsibility: Executing platform side-effects returned by actions (such as copying text, pasting into active apps, opening URLs, or showing system sharing services).
- Strict Rule:
PopupWindowControllermanages window presentation and event filtering, delegating all result execution side-effects toActionResultHandler.
6. Action Coordinator & Composition — ActionCoordinator#
- Responsibility: Orchestrates initial state loading, registers builtins, and connects disk extensions (including GUI-authored manifest packages) to the central
ActionRegistry. - Strict Rule:
ExtensionManagerdoes not couple directly to the registry; it reports changes throughonRegister/onUnregistercallbacks wired byActionCoordinator.loadInitialState(). - Search catalog:
ActionCoordinator.searchCatalog(→ActionRegistry.searchCatalog) exposes what the popup's action-search palette may offer: the registered catalog minus anything switched off in settings (per action, whole package, a disabled group's members, an AI preset toggled off) and minus anything that cannot run against the current context. It is the bar's visibility rules on a flat, unpaginated surface;ActionSearch.searchranks it.
Key Design Guidelines#
- Accept dependencies, don't create them: Core types accept
SettingsStoreininit(settingsStore:)with default fallback (see §1 above);ActionCoordinatorand the builtin builder take their collaborators ininit. - No
ActionRegistry.sharedinside domain managers: Domain managers report registration changes throughonRegister/onUnregistercallbacks wired byActionCoordinator. OnlyActionCoordinatortouches the registry directly. - No
switch action.idstring matching in UI: Display formatting relies onConfigurableAction.preferenceIconNameandActionChrome. - Pure Snippet Parsing:
OpenClipSnippetParseris a pure string parser with no UI or@MainActorties (nonisolated). - Pure Layout Math:
PopupPositioneris a pure static struct for computing panel coordinates and edge clamping. - Static previews don't share live state:
PopupPreviewhardcodes a canonical action set and passes its ownPopupHoverState()+isStatic: truetoPopupView, so it never reflects — or leaks hover into — the real popup. - Standard windows unless a chrome-less card is the goal: Preferences uses standard window chrome (
hiddenTitleBar/fullSizeContentView) with.glassSurfacecontent; onboarding is a.borderlesstransparent window that re-draws the card, border, and shadow manually.