diff --git a/.cursorrules b/.cursorrules index dc063fa6..4a348525 100644 --- a/.cursorrules +++ b/.cursorrules @@ -15,8 +15,8 @@ Keep these four files consistent when architecture or agent guidance changes. - Uninstaller: `Source/Marker Data/Marker Data Uninstaller/` (target **Uninstall Marker Data**; product `Uninstall Marker Data.app`; bundle ID `co.theacharya.MarkerData.Uninstaller`) - FCP Share Destination (Swift + Obj‑C): `Source/Marker Data/Marker Data/FCP Share Destination/` - Bundled binaries (do not modify internals): `Source/Marker Data/Marker Data/Resources/airlift`, `csv2notion_neo` -- Build: open `Source/Marker Data/Marker Data.xcodeproj`, scheme **Marker Data**, **Apple Silicon** (`arm64`) only -- CI: `macos-26` runners with **Xcode 26.6.0**; Workflow Extensions SDK from `SDK/Workflow_Extensions_1.0.3.dmg` +- Build: open `Source/Marker Data/Marker Data.xcodeproj`, scheme **Marker Data**, **Apple Silicon** (`arm64`) only. After code or agent-doc changes, unsigned Debug **and** Release: `CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO`, `EXCLUDED_ARCHS=x86_64`. +- CI: `macos-26` runners with **Xcode 26.6.0**; Workflow Extensions SDK from `SDK/Workflow_Extensions_1.0.3.dmg` (ignore unused `1.0.2` DMG in that folder) ## Swift / SwiftUI conventions - Format Swift with SwiftFormat (see `CONTRIBUTING.md`). @@ -30,7 +30,8 @@ Keep these four files consistent when architecture or agent guidance changes. - On launch, `SettingsVersioningManager.updateAll()` migrates JSON **before** decode — dict-based `upgradeVersion(...)`, one step per version increment. - UI binds via `@EnvironmentObject SettingsContainer` and `$settings.store.`. - Export pipeline reads settings through `SettingsStore.markersExtractorSettings(fcpxmlFileUrl:)` → `MarkersExtractor.Settings` — wire new export-related fields there. Roles are reloaded from disk inside that method. -- `RolesManager` reads/writes `preferences.json` directly (Workflow Extension sync via DistributedNotification `.rolesChanged`) — keep `roles` compatible. +- `RolesManager` reads/writes `preferences.json` directly (Workflow Extension sync via DistributedNotification `.rolesChanged`) — keep `roles` compatible. The appex compiles `SettingsStore.swift` and does **not** run `SettingsVersioningManager`. +- Do not rename persisted key `plaform` without a migration. - **Any new/changed/renamed persisted `SettingsStore` property:** 1. Bump `SettingsStore.version` 2. Add migration `case` for the previous version @@ -40,18 +41,18 @@ Keep these four files consistent when architecture or agent guidance changes. ## Key runtime flows (see ARCHITECTURE.md for detail) - **Extract intake:** `FCPXMLIntake` resolves `.fcpxml` / `.fcpxmld`, Finder `.textClipping`, and Final Cut Pro pasteboard → temp file under `~/Movies/Marker Data Cache/` → `ExtractionModel.receiveFiles` / `receiveItemProviders` -- **Extract:** `ExtractionModel` → MarkersExtractor → optional `ColorPaletteRenderer.render(...) -> Bool` (`await applyTaskAppearance` only after image checks inside renderer; `false` → `markProcessAsFinished` / “Extract done”; `true` → `markAllProcessesFinished`; never `reset()` before swatch) → optional `DatabaseUploader` +- **Extract:** `ExtractionModel` → MarkersExtractor → optional `ColorPaletteRenderer.render(...) -> Bool` (`await applyTaskAppearance` only after image checks inside renderer; `false` → `markProcessAsFinished` / “Extract done”; `true` → `markAllProcessesFinished` because `ImageRenderService` replaces extract URLs with image URLs; never `reset()` before swatch) → optional `DatabaseUploader` - **Drop overlays:** shared `DropTargetOverlay` on main-app Extract / Roles / Queue **and** Workflow Extension Extract + Roles — exact copy in **GUARDRAILS.md**; border uses `Color.markerAccent` (not host `accentColor`) - **Queue:** scans for `extract_info.json` (Notion/Airtable + JSON manifest only) → upload via `QueueInstance.manifestURL` (local JSON beside folder preferred) → parallel re-upload; folder drop clears queue and disables auto-scan -- **Workflow Extension:** Extract tab drops `.fcpxml` → Movies-cache handoff → open `/Applications/Marker Data.app` → DistributedNotification `.workflowExtensionFileReceived`; both Extract and Roles show `DropTargetOverlay` (`WorkflowExtensionView` + shared `RolesSettingsView`). Extension Compile Sources must include `DropTargetOverlay` + `ColorExtension` (+ shared Roles sources) -- **Share Destination:** `OSAScriptingDefinition.sdef` + Obj‑C `MakeCommand` → Movies cache → local `FCPShareStart` → Apple Event open → `.openFile` +- **Workflow Extension:** Extract tab drops `.fcpxml` → Movies-cache handoff (does **not** mkdir the cache folder) → open `/Applications/Marker Data.app` → DistributedNotification `.workflowExtensionFileReceived`; both Extract and Roles show `DropTargetOverlay`. Shared sources have **two** `pbxproj` Compile Sources rows (one file, two targets) — never “dedupe”. Membership list: **ARCHITECTURE.md → Workflow Extension target**. Header uses `MarkerDataAppIcon.image()` at 100×100 from the **containing** `Marker Data.app` — not the appex `AppIcon.appiconset`. TabView keeps `.padding(.bottom, 40)` so the drop zone stays above the overlaid help “?”. +- **Share Destination:** `OSAScriptingDefinition.sdef` + Obj‑C `MakeCommand` (cache `/` then location `…//`) → local `FCPShareStart` → Apple Event open → `.openFile`. `OpenEventHandler.setupHandler()` on init, `.FCPShareStart`, and `Marker_DataApp` `.task` (main queue). ## Pitfalls to avoid - Do not remove or rename persisted settings keys without a migration. - Do not assume behavior of `airlift` / `csv2notion_neo` beyond CLI usage in `DatabaseUploader` (and Dropbox setup in `DropboxSetupModel`). - Do not break Share Destination scripting (`Resources/OSAScriptingDefinition.sdef` + Obj‑C) or Workflow Extension disk/DNC contracts. - The app warns if not run from `/Applications`; do not remove that check (extension opens that path). -- SwiftUI `.alert` icons: Dock uses Icon Composer (`Marker-Data.icon`), which often yields a blank document glyph. After every `.alert`, chain `.appDialogIcon()` (`Views/Extensions/DialogIcon.swift` → PNG `AppIconSingle`). +- SwiftUI `.alert` / `.confirmationDialog` icons: chain `.appDialogIcon()` after every dialog. `MarkerDataAppIcon` is `@MainActor` (`DialogIcon.swift`). Main-app Dock / About / alerts use Icon Composer `Marker-Data.icon` (`ASSETCATALOG_COMPILER_APPICON_NAME` = Marker-Data). Workflow Extension **header** loads the icon from the containing `Marker Data.app` (walk `appex` → `PlugIns` → `Contents` → `.app`); the appex’s `applicationIconImage` is its own `AppIcon.appiconset` (or Final Cut Pro). Do not flatten the layer PNG into `AppIconSingle`. Do not replace the Workflow Extension bundle/plugin `AppIcon.appiconset`. Uninstaller uses `applicationIconImage`. - Database models use property name `plaform` — keep consistent unless intentionally migrating. - If adding new on-disk paths the app uses, update `MarkerDataUninstaller` cleanup paths too. - Do not upload Queue jobs using only absolute `ExtractInfo.jsonURL` when a same-named JSON exists in the dropped/scanned folder — use `manifestURL`. @@ -59,11 +60,12 @@ Keep these four files consistent when architecture or agent guidance changes. - `applyTaskAppearance` is `@MainActor` — always `await` it from `ColorPaletteRenderer`. - FCP timeline drops onto the Dock icon are often pasteboard-only; Extract panel drop is the supported timeline path. - Do not use `Color.accentColor` / `.accent` for shared chrome hosted in Final Cut Pro — use `Color.markerAccent`. +- Do not delete a second `DialogIcon.swift in Sources` (or other shared files) from `project.pbxproj` — that is two-target membership, not a duplicate file. ## Definition of done for code changes -- App builds (Debug and Release) for arm64 (Workflow Extension still embeds). +- App builds **unsigned** Debug and Release for arm64 (`CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO`; Workflow Extension still embeds). - Settings load and migrate; extraction works for `.fcpxml`/`.fcpxmld` (incl. FCP pasteboard / textClipping via `FCPXMLIntake`). - Drop overlays still work on main-app Extract / Roles / Queue **and** Workflow Extension Extract + Roles; queue still finds/uploads `extract_info.json` folders via `manifestURL`. - Skipped / no-media swatch finishes as “Extract done” (not “Analysing swatch done”). -- New alerts use `.appDialogIcon()`; settings changes include version + migration + wiring as required above. +- New alerts **and** confirmation dialogs use `.appDialogIcon()`; Workflow Extension header still shows the main-app Icon Composer icon (not the appex `AppIcon.appiconset`); settings changes include version + migration + wiring as required above. - Update `AGENT.md` / `ARCHITECTURE.md` / `GUARDRAILS.md` / `.cursorrules` when behavior changes. diff --git a/AGENT.md b/AGENT.md index 10fddadf..9a00b68a 100644 --- a/AGENT.md +++ b/AGENT.md @@ -20,6 +20,7 @@ - [New database platform](#new-database-platform) - [FCP integrations](#fcp-integrations) - [Drop overlay / shared Workflow Extension UI](#drop-overlay--shared-workflow-extension-ui) + - [App icon / dialogs](#app-icon--dialogs) - [Uninstaller cleanup paths](#uninstaller-cleanup-paths) - [Release metadata](#release-metadata) - [Agent documentation](#agent-documentation) @@ -80,14 +81,32 @@ For deeper module/data-flow detail, see **`ARCHITECTURE.md`**. For hard always/n | Settings migrations | `Source/Marker Data/Marker Data/Models/Settings/SettingsVersioningManager.swift` | | Canonical disk paths | `Source/Marker Data/Marker Data/Utilities/Extensions/URLExtension.swift` | | Notification names | `Source/Marker Data/Marker Data/Utilities/Extensions/NotificationNameExtension.swift` | -| Alert dialog icon helper | `Source/Marker Data/Marker Data/Views/Extensions/DialogIcon.swift` (`.appDialogIcon()`) | +| App / dialog icon | `Views/Extensions/DialogIcon.swift` — `@MainActor` `MarkerDataAppIcon` + `.appDialogIcon()`; Icon Composer `Marker-Data.icon` | +| About | `Views/Detail Views/AboutView.swift` — `MarkerDataAppIcon.image()` at 200×200 | +| Workflow Extension header icon | Same `MarkerDataAppIcon.image()` at 100×100; in the appex, resolve from containing `Marker Data.app` | +| Apple Event open | `FCP Share Destination/OpenEventHandler.swift` — `kAEOpen` → `.openFile`; re-register on `.FCPShareStart` (main queue) | +| Roles (app + WE) | `Models/Roles/RolesManager.swift` + `RolesManager+DropDelegate.swift` — reads/writes `preferences.json` (no `SettingsContainer` in the appex) | +| Uninstaller | `Marker Data Uninstaller/MarkerDataUninstaller.swift` — path list must match runtime paths | ## Build & run (local) 1. Open `Source/Marker Data/Marker Data.xcodeproj`. 2. Select the **Marker Data** scheme. 3. Build/run for **Apple Silicon** (`arm64`). Do not target Intel. -CI installs the Workflow Extensions SDK from `SDK/Workflow_Extensions_1.0.3.dmg` before `xcodebuild`. Locally you need the same SDK installed under `/Library/Developer/SDKs/WorkflowExtensionSDK.sdk` to build the extension target. +**Unsigned verification** (CI-equivalent; Debug **and** Release after code or agent-doc changes): + +```bash +xcodebuild -project "Source/Marker Data/Marker Data.xcodeproj" \ + -scheme "Marker Data" \ + -configuration Debug \ + -destination "platform=macOS,arch=arm64" \ + ONLY_ACTIVE_ARCH=NO EXCLUDED_ARCHS="x86_64" \ + CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO +``` + +Repeat with `-configuration Release`. A good result is an adhoc/linker-signed thin **arm64** `Marker Data.app` with `Contents/PlugIns/Workflow Extension.appex` and `Uninstall Marker Data.app` beside it. This machine may redirect `SYMROOT` outside `-derivedDataPath`; confirm the Products folder actually contains those apps. + +CI installs the Workflow Extensions SDK from `SDK/Workflow_Extensions_1.0.3.dmg` (the folder may also contain an unused older `1.0.2` DMG). Locally you need that SDK under `/Library/Developer/SDKs/WorkflowExtensionSDK.sdk` to build the extension target. **SwiftFormat** (courtesy, not CI-enforced): from repo root, `swiftformat .` — see `CONTRIBUTING.md`. @@ -122,7 +141,8 @@ When bumping a release: update `MARKETING_VERSION` and `CURRENT_PROJECT_VERSION` - Extraction/upload use `Task` / `TaskGroup`; cancellation via `Task.cancel()` and terminating child `Process`es. - **Persistence:** versioned JSON under Application Support (see Settings system). Database profiles are separate JSON files. - **External tools:** Notion/Airtable uploads spawn bundled executables; treat binaries as opaque. Progress UI depends on stdout lines containing `NN%`. -- **Colors / icons:** Dock icon is Icon Composer (`Marker-Data.icon`). SwiftUI `.alert` often shows a blank document glyph — always chain `.appDialogIcon()` after `.alert` / confirmation dialogs. Shared Workflow Extension chrome uses `Color.markerAccent` (not host `accentColor`). +- **Colors / icons:** `MarkerDataAppIcon` is `@MainActor` (`DialogIcon.swift`). Main-app Dock / About / alerts use Icon Composer `Marker-Data.icon`. Workflow Extension **header** loads that icon from the containing `Marker Data.app` (not the appex `AppIcon.appiconset`). Do **not** flatten the layer PNG into `AppIconSingle`. Shared Workflow Extension chrome uses `Color.markerAccent` (not host `accentColor`). +- **Shared Compile Sources:** one file on disk can appear **twice** in `project.pbxproj` (`PBXBuildFile` “in Sources”) — main app + Workflow Extension. That is target membership, not a duplicate file. Never delete one row to “dedupe”. Full extension membership list: **ARCHITECTURE.md → Workflow Extension target**. ## Settings system (read before changing preferences) @@ -147,7 +167,7 @@ Marker Data settings are **versioned JSON**. The active store is `preferences.js 2. **Runtime:** UI binds via `@EnvironmentObject SettingsContainer` and `$settings.store.`. 3. **Auto-save:** any change to `store` writes `preferences.json` immediately. 4. **Extraction:** `SettingsStore.markersExtractorSettings(fcpxmlFileUrl:)` maps store → `MarkersExtractor.Settings` (not automatic — wire new export-related fields here). Roles are **reloaded from disk** inside this method. -5. **Roles exception:** `RolesManager` reads/writes `preferences.json` and posts DistributedNotification `.rolesChanged`. Main app `SettingsContainer` observes that and reloads the store. +5. **Roles exception:** `RolesManager` reads/writes `preferences.json` and posts DistributedNotification `.rolesChanged`. Main app `SettingsContainer` observes that and reloads the store. The Workflow Extension uses this path only (no `SettingsContainer`, no `SettingsVersioningManager`). ### Migration ladder (each `case N` upgrades N → N+1) @@ -172,12 +192,14 @@ Current schema version is **8**. Adding a property requires a new `case 8:` (8 | 3 | Add `case :` in `SettingsVersioningManager.upgradeVersion(dict:version:)` | | 4 | Add UI binding under `Views/Detail Views/` | | 5 | If export-related: pass through `markersExtractorSettings(fcpxmlFileUrl:)` | -| 6 | If MarkersExtractor gained a new API: bump the SPM package in `project.pbxproj` (currently minimum **0.4.6**) | +| 6 | If MarkersExtractor gained a new API: bump the SPM package in `project.pbxproj` (currently minimum **0.4.8**) | | 7 | Confirm old `preferences.json` / `Configurations/*.json` migrate and the app still loads settings | **Always migrate** when adding/removing/renaming persisted keys. **Never** rely on `Codable` defaults alone for existing on-disk files. -**Reference implementation:** `allowUTF8InMIDIExport` (issue #148) — property, v7→v8 migration, `FileSettingsView` toggle, `isMIDIFileUTF8EncodingAllowed` in `markersExtractorSettings`, MarkersExtractor 0.4.6. +The Workflow Extension **compiles `SettingsStore.swift`** and decodes `preferences.json` via `RolesManager` **without** running `SettingsVersioningManager`. After a schema bump, the main app must launch once so migrations write before the extension can decode. + +**Reference implementation:** `allowUTF8InMIDIExport` (issue #148) — property, v7→v8 migration, `FileSettingsView` toggle, `isMIDIFileUTF8EncodingAllowed` in `markersExtractorSettings`. MarkersExtractor SPM minimum in `project.pbxproj` is **0.4.8**. ### Known settings gaps (do not “fix” casually without product intent) These fields exist in UI / `SettingsStore` but are **not** currently passed into `MarkersExtractor.Settings`: @@ -210,7 +232,19 @@ Also: getter for `colorSwatchSettings` **forces `enableSwatch = false` when extr ### Drop overlay / shared Workflow Extension UI - Update copy in **`GUARDRAILS.md`** (Drop overlay copy table) when changing messages. - Workflow Extension **Extract** (`WorkflowExtensionView`) and **Roles** (shared `RolesSettingsView`) both show overlays. -- If overlay / color helpers change, ensure Workflow Extension Compile Sources still include `DropTargetOverlay` and `ColorExtension`. Shared chrome uses `Color.markerAccent`. +- Shared types must stay in **both** targets’ Compile Sources (two `PBXBuildFile` rows, one `PBXFileReference`). Minimum shared UI: `DropTargetOverlay`, `ColorExtension`, `DialogIcon.swift`, `HelpButton`, `OverlayHelpButton`. Roles/settings files listed in **ARCHITECTURE.md**. Shared chrome uses `Color.markerAccent`. + +### App icon / dialogs +- Icon Composer **`Marker-Data.icon`** (`ASSETCATALOG_COMPILER_APPICON_NAME` = Marker-Data) lives beside the catalog — **not** inside `Assets.xcassets`. Main-app `AppIcon.appiconset` is an empty placeholder. Never flatten the layer PNG into `AppIconSingle` (imageset removed). +- `MarkerDataAppIcon` is **`@MainActor`** (Marker Data does **not** set `SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor`; a nonisolated `NSApplication.shared.applicationIconImage` read warns under Swift 6). +- **Resolution (`displayIcon`):** + 1. If running as `.appex`: load from the containing `Marker Data.app` (`appex` → `PlugIns` → `Contents` → `.app`): `Bundle.image(forResource: "Marker-Data")`, else `NSWorkspace.icon(forFile:)`. Do **not** use the appex’s `applicationIconImage` (that is `AppIcon.appiconset`, or Final Cut Pro). + 2. Else: `NSImage(named: "Marker-Data")`, then `NSApplication.shared.applicationIconImage`. +- **Surfaces:** About `MarkerDataAppIcon.image()` **200×200**; Workflow Extension header **100×100**; alerts **and** confirmation dialogs `.appDialogIcon()`. +- **Main-app `.alert` / `.confirmationDialog` (chain `.appDialogIcon()`):** `Marker_DataApp` (library folders); `ContentView` (install location); `ExtractView` (extract + upload, ×2); `ConfigurationSettingsView` (unsaved-switch, unsaved-add, and delete confirmations + alert); `DatabaseSettingsView` (remove + duplicate alerts, ×2, plus delete-profile confirmation); `CreateDBProfileSheet`; `DropboxSetupView`; `InstallShareDestinationView`. +- Uninstaller is a **separate target**: `UninstallerView` uses `NSApplication.shared.applicationIconImage`, not `MarkerDataAppIcon`. +- Do **not** change the Workflow Extension’s bundle/plugin icon (`ASSETCATALOG_COMPILER_APPICON_NAME` = AppIcon, existing `AppIcon.appiconset`). +- Workflow Extension Compile Sources must include `DialogIcon.swift` (header UI). Keep Extract/Roles `TabView` `.padding(.bottom, 40)` so `overlayHelpButton` does not sit on the drop zone. ### Uninstaller cleanup paths - If the app/extension gains new Application Support, cache, container, or preferences paths, update `MarkerDataUninstaller.run()` path list to match. @@ -232,11 +266,11 @@ Also: getter for `colorSwatchSettings` **forces `enableSwatch = false` when extr **Queue manifest resolution:** `QueueInstance.manifestURL` prefers `{folder}/{json basename}` when that file exists (moved/copied exports), else falls back to absolute `ExtractInfo.jsonURL`. Uploads and `filterMissing()` must use `manifestURL`, not the sidecar path alone. -**Progress across extract → swatch:** `ColorPaletteRenderer.render(...) -> Bool`. Inside renderer, after image checks pass, `await progress.applyTaskAppearance("Analysing swatch", ...)` (MainActor). Returns `false` on skip (no images / unsupported GIF) → ExtractionModel uses `markProcessAsFinished` (**“Extract done”**). Returns `true` → `markAllProcessesFinished`. Never `reset()` when entering swatch. Ignore late KVO after a process is finished. +**Progress across extract → swatch:** `ColorPaletteRenderer.render(...) -> Bool`. Inside renderer, after image checks pass, `await progress.applyTaskAppearance("Analysing swatch", ...)` (MainActor). Returns `false` on skip (no images / unsupported GIF) → ExtractionModel uses `markProcessAsFinished` (**“Extract done”**). Returns `true` → `markAllProcessesFinished` (required because `ImageRenderService.export` calls `setProcesses` with **image** URLs, replacing the extract FCPXML rows). Never `reset()` when entering swatch. Ignore late KVO after a process is finished. -**Temporary pasteboard FCPXML:** `FCPXMLIntake.writeTemporaryFCPXML` → `~/Movies/Marker Data Cache/`. +**Temporary pasteboard FCPXML:** `FCPXMLIntake.writeTemporaryFCPXML` → `~/Movies/Marker Data Cache/` (creates the cache folder if needed). -**Workflow Extension:** Extract + Roles tabs both use `DropTargetOverlay` (`WorkflowExtensionView` / shared `RolesSettingsView`); also shares `RolesManager` / `ColorExtension`. Add shared UI types to the extension target’s Compile Sources. Shared chrome uses `Color.markerAccent` (not host `accentColor`). +**Workflow Extension:** Extract + Roles tabs both use `DropTargetOverlay` (`WorkflowExtensionView` / shared `RolesSettingsView`); also shares `RolesManager` / `ColorExtension` / `DialogIcon` (`MarkerDataAppIcon`) / `HelpButton` / `OverlayHelpButton` plus the settings/roles files in **ARCHITECTURE.md**. Shared chrome uses `Color.markerAccent` (not host `accentColor`). Header icon: containing `Marker Data.app`, not appex `AppIcon.appiconset`. Extract/Roles `TabView` uses `.padding(.bottom, 40)` so the drop zone stays above the help “?”. The extension **writes** `WorkflowExtensionExport.fcpxml` but does **not** create `~/Movies/Marker Data Cache/` — the main app’s `LibraryFolders.checkAndCreateMissing()` does. Exact overlay strings and always/never rules: **`GUARDRAILS.md`**. @@ -251,7 +285,8 @@ Exact overlay strings and always/never rules: **`GUARDRAILS.md`**. | `updateAvailable` | Local | Sparkle delegate → sidebar badge | **Workflow Extension file:** `~/Movies/Marker Data Cache/WorkflowExtensionExport.fcpxml` (`URL.workflowExtensionExportFCPXML`). -**App open path hard-coded by extension:** `/Applications/Marker Data.app`. +**App open path hard-coded by extension:** `/Applications/Marker Data.app`. +**`OpenEventHandler.setupHandler()`** is invoked from `init`, from `.FCPShareStart`, and again from `Marker_DataApp` `.task` — keep the `DispatchQueue.main.async` registration (see comments in that file). ## Common pitfalls - **Don’t break migrations:** settings JSONs in the wild must upgrade step-by-step. @@ -263,17 +298,21 @@ Exact overlay strings and always/never rules: **`GUARDRAILS.md`**. - **Queue relocated folders:** never upload only `ExtractInfo.jsonURL` — use `manifestURL` (see Signs in `GUARDRAILS.md`). - **Progress `reset()` / wrong swatch label:** never `reset()` before swatch; never retitle to “Analysing swatch” until render will run. Skipped swatch must finish as **“Extract done”**, not **“Analysing swatch done”**. `await applyTaskAppearance` from `ColorPaletteRenderer` (MainActor). - **FCP timeline → Dock:** often pasteboard-only; Extract panel drop is the supported UI path for timelines. -- **Alert icons:** chain `.appDialogIcon()` after every `.alert`. +- **Alert / confirmation icons:** chain `.appDialogIcon()` after every `.alert` and `.confirmationDialog`. `MarkerDataAppIcon` is `@MainActor`; source is compiled `Marker-Data.icon`. Do not flatten the layer PNG into `AppIconSingle`. In the Workflow Extension header, load from the containing `Marker Data.app` — not the appex `AppIcon.appiconset`. - **Dual Sparkle controllers:** both `Marker_DataApp` and `ApplicationDelegate` construct `SPUStandardUpdaterController`; update-available UI relies on `ApplicationDelegate.bestValidUpdate(...)` posting `.updateAvailable`. Don’t “simplify” without understanding that path. - **`OpenEventHandler`:** must re-register on `.FCPShareStart`; registration should stay on the main queue (see comments in that file). +- **Shared `pbxproj` rows:** do not remove a second `DialogIcon.swift in Sources` (or other shared files) — the file is compiled into two targets. +- **Persisted typos:** do not rename JSON key `plaform` without a migration. +- **WE cache folder:** the extension does not `mkdir` Movies cache; a first-time drop can fail if the main app has never created `~/Movies/Marker Data Cache/`. ## Definition of done for most changes -- App builds (Debug + Release) for **arm64** (Workflow Extension still embeds). +- App builds **unsigned** Debug **and** Release for **arm64** (`CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO`; Workflow Extension still embeds). - Settings still load and migrate cleanly (`preferences.json` + `Configurations/*.json`). - Extraction works for `.fcpxml` and `.fcpxmld`, including FCP pasteboard / textClipping intake via `FCPXMLIntake`. - Drop overlays still work on main-app Extract / Roles / Queue **and** Workflow Extension Extract + Roles. - Queue scan/upload still works for folders containing `extract_info.json`, including **moved/copied** folders (`manifestURL`). - Skipped / no-media swatch finishes as **“Extract done”** (not **“Analysing swatch done”**). -- Any new `.alert` uses `.appDialogIcon()`. +- Any new `.alert` or `.confirmationDialog` uses `.appDialogIcon()` (`MarkerDataAppIcon` from compiled `Marker-Data.icon`). +- Workflow Extension header still shows the main-app Icon Composer icon (containing `Marker Data.app`); bundle/plugin `AppIcon.appiconset` unchanged. - If settings changed: version bumped + migration case + UI wired + export bridge if needed. - If behavior/architecture changed: update `AGENT.md`, `ARCHITECTURE.md`, `GUARDRAILS.md`, and `.cursorrules`. diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 5c43d551..15aa54c4 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -30,6 +30,7 @@ - [Color swatch pipeline](#color-swatch-pipeline) - [Database profiles](#database-profiles) - [UI architecture](#ui-architecture) +- [App icon and dialogs](#app-icon-and-dialogs) - [Notifications (full list)](#notifications-full-list) - [Entitlements / security model](#entitlements--security-model) - [Build, packaging, updates](#build-packaging-updates) @@ -43,7 +44,7 @@ ## Overview **Marker Data** is a macOS SwiftUI application that extracts Final Cut Pro marker metadata and generates export artifacts (CSV/TSV/XLSX/MIDI/Markdown/SRT/YouTube/Compressor, plus Notion/Airtable JSON). It can optionally: -- Render stills / GIFs for markers (via **`MarkersExtractor`**, SPM ≥ **0.4.6**) +- Render stills / GIFs for markers (via **`MarkersExtractor`**, SPM ≥ **0.4.8**) - Compute and render dominant color swatches/palettes into images (`DominantColors` + app-side merge) - Upload extracted JSON manifests to Notion/Airtable using bundled CLI tools (`csv2notion_neo`, `airlift`) @@ -52,7 +53,7 @@ It also ships two Final Cut Pro integrations: - **Share Destination** — Media Asset Protocol / AppleScript so FCP exports media + FCPXML and opens them into Marker Data - **Workflow Extension** — ProExtensions-hosted UI: Extract tab handoff (overlay + Movies-cache FCPXML → open app) and Roles management (shared prefs + overlay) -**Runtime requirements (product):** Apple silicon; macOS Sequoia 15.7+ (from 2.0.0); Final Cut Pro 12+ recommended. The app is expected to run from **`/Applications/Marker Data.app`**. +**Runtime requirements (product):** Apple silicon; macOS Sequoia **15.7+** (from 2.0.0; see README / CHANGELOG). Xcode `MACOSX_DEPLOYMENT_TARGET` is **15.0** — the 15.7 floor is a support policy, not the build setting. Final Cut Pro 12+ recommended. The app is expected to run from **`/Applications/Marker Data.app`**. For agent-oriented change checklists, see **`AGENT.md`**. For hard always/never constraints and learned pitfalls, see **`GUARDRAILS.md`**. For short Cursor enforcement, see **`.cursorrules`**. @@ -84,7 +85,19 @@ Primary responsibilities: Responsibilities: - SwiftUI UI inside `WorkflowExtensionViewController` (`NSHostingView`) - Extract tab: drag/drop `.fcpxml` + `DropTargetOverlay` (“Drop to Open Marker Data”) → write Movies-cache handoff file → open main app → DistributedNotification +- Header: `MarkerDataAppIcon.image()` at 100×100 from the containing `Marker Data.app` (`DialogIcon.swift`); bundle/plugin icon remains `Assets.xcassets/AppIcon.appiconset` - Roles tab sharing `RolesSettingsView` / `RolesManager` / `DropTargetOverlay` / `ColorExtension` with the main app (same prefs file) +- Extract/Roles `TabView` uses `.padding(.bottom, 40)` so the drop zone stays above the help “?” +- The appex **does not** create `~/Movies/Marker Data Cache/`; `LibraryFolders.checkAndCreateMissing()` in the main app does + +**Compile Sources (extension target)** — each shared `.swift` file is one disk file and **two** `PBXBuildFile` “in Sources” rows (main app + appex). Do not delete one row. + +| Kind | Files | +|------|--------| +| Extension-only | `WorkflowExtensionView.swift`, `WorkflowExtensionViewController.swift` | +| Shared UI | `DropTargetOverlay.swift`, `ColorExtension.swift`, `DialogIcon.swift`, `HelpButton.swift`, `OverlayHelpButton.swift`, `RolesSettingsView.swift` | +| Shared roles / settings (no `SettingsContainer` in the appex) | `RolesManager.swift`, `RolesManager+DropDelegate.swift`, `RoleModel.swift`, `SettingsStore.swift`, `SettingsModels.swift`, `ColorSwatchSettingsModel.swift`, `UnifiedExportProfile.swift`, `NotificationFrequency.swift`, `ExtractError.swift` | +| Shared helpers | `URLExtension.swift`, `UTTypeExtension.swift`, `RoleExtension.swift`, `NotificationNameExtension.swift`, `MarkersExtractorModelExtensions.swift`, `ExportProfileFormatExtrension.swift`, `DeltaEFormulaExtension.swift` | ### Uninstaller target (`Uninstall Marker Data`) **Location:** `Source/Marker Data/Marker Data Uninstaller/` @@ -92,7 +105,7 @@ Responsibilities: **Display name:** Marker Data Uninstaller **Bundle ID:** `co.theacharya.MarkerData.Uninstaller` -`MarkerDataUninstaller.run()` terminates the main app, deletes the `co.theacharya.MarkerData` defaults domain, trashes app/cache/prefs/container paths, and writes `~/Desktop/Marker-Data_Uninstall_Log.txt`. +`MarkerDataUninstaller.run()` terminates the main app, deletes the `co.theacharya.MarkerData` defaults domain, trashes app/cache/prefs/container paths, and writes `~/Desktop/Marker-Data_Uninstall_Log.txt`. UI (`UninstallerView`) uses `NSApplication.shared.applicationIconImage` (Uninstaller Icon Composer `Marker-Data-Uninstaller.icon`), not main-app `MarkerDataAppIcon`. Does not trash `Uninstall Marker Data.app` itself. Built as part of the **Marker Data** scheme; CI copies it next to the main app in the DMG. @@ -268,8 +281,9 @@ FCP role enable/disable lives in `preferences.json`, but **`RolesManager` reads/ - Save → DistributedNotification `.rolesChanged` - Main app observes and replaces `store` from disk - Extraction always reloads roles from disk inside `markersExtractorSettings` +- The appex **compiles `SettingsStore.swift`** and decodes prefs **without** `SettingsVersioningManager.updateAll()`. After a schema upgrade, the main app must launch once so migrations land before the extension can decode. -Changing the JSON shape of `roles` / `RoleModel` must remain compatible with both targets. +Changing the JSON shape of `roles` / `RoleModel` (or any `SettingsStore` key) must remain compatible with both targets. #### Checklist: adding or changing a persisted setting @@ -329,7 +343,7 @@ Pipeline (`performExtraction`): - Build settings via `markersExtractorSettings` - `MarkersExtractor.extract()`; KVO `progress.fractionCompleted` → progress UI / DockProgress - Optionally write `extract_info.json` when `ExtractInfo(exportResult:)` succeeds (Notion/Airtable + JSON path) - - If swatches enabled: `didRender = await ColorPaletteRenderer.render(...)` — renderer `await`s `applyTaskAppearance` only after images pass checks; `true` → `markAllProcessesFinished`; `false` → `markProcessAsFinished(url)` (keeps **“Extract done”**) + - If swatches enabled: `didRender = await ColorPaletteRenderer.render(...)` — renderer `await`s `applyTaskAppearance` only after images pass checks; `true` → `markAllProcessesFinished` (`ImageRenderService` replaced extract URLs with image URLs); `false` → `markProcessAsFinished(url)` (keeps **“Extract done”**) - If swatches disabled: `markProcessAsFinished(url)` - If DB profile selected: `DatabaseUploader.uploadToDatabase(jsonManifestPath, profile)` 4. Aggregate `ExportExitStatus` + `ExtractionFailure[]`; notifications; optional Finder/Pagemaker open @@ -390,20 +404,20 @@ Implementation details: - Non-zero exit → platform-specific `DatabaseUploadError` - Cancel → `Process.terminate()` -Dropbox auth for Airtable: `DropboxSetupModel` writes a temp `.command` that runs `airlift --dropbox-refresh-token` and opens it in Terminal (avoids fragile AppleScript paths on newer macOS). +Dropbox auth for Airtable: `DropboxSetupModel` writes a temp `.command` that runs `airlift --dropbox-refresh-token` and opens it in Terminal (avoids fragile AppleScript paths on newer macOS). Watches the token file with `FileWatcher`. ### 4) Workflow Extension handoff **Entry:** drop `.fcpxml` on extension Extract tab (`WorkflowExtensionView`). 1. Show `DropTargetOverlay` (**“Drop to Open Marker Data”**) while targeted -2. Write `~/Movies/Marker Data Cache/WorkflowExtensionExport.fcpxml` +2. Write `~/Movies/Marker Data Cache/WorkflowExtensionExport.fcpxml` (does **not** create the parent folder) 3. `NSWorkspace.openApplication` → `/Applications/Marker Data.app` 4. DistributedNotification `.workflowExtensionFileReceived` (no URL payload) App: `ExtractionModel_EventHandlers.handleWorkflowExtensionEvent` reads the fixed path; starts extraction or sets external-file gate. `SidebarSelectionSwitcher` selects Extract. -Roles tab: same `RolesSettingsView` + `DropTargetOverlay` as the main app (extension Compile Sources must include overlay + `ColorExtension`). Overlay borders use `Color.markerAccent` so FCP’s host accent does not turn them blue. +Roles tab: same `RolesSettingsView` + `DropTargetOverlay` as the main app (full Compile Sources table under **Workflow Extension target**). Overlay borders use `Color.markerAccent` so FCP’s host accent does not turn them blue. Header icon comes from the containing `Marker Data.app`, not the appex `AppIcon.appiconset`. ### 5) Share Destination handoff @@ -414,12 +428,13 @@ Roles tab: same `RolesSettingsView` + `DropTargetOverlay` as the main app (exten - Asset location record keys: `folder`, `basename`, `hasMedia`, `hasDescription` **Obj‑C:** -- `MakeCommand` posts `FCPShareStart`, creates `~/Movies/Marker Data Cache//`, returns asset location expecting media + `.fcpxml` +- `MakeCommand` posts `FCPShareStart`, creates `~/Movies/Marker Data Cache//`, then sets the asset location folder to `…//` with empty basename, `hasMedia` + `hasDescription` → expect `.mov` + `.fcpxml` - `DocumentController` / `Asset` attach opened media/description URLs to the document **Swift:** - `OpenEventHandler` registers `kAEOpen`; on each URL posts `.openFile` with `userInfo["url"]` -- Re-registers when `.FCPShareStart` arrives +- Re-registers when `.FCPShareStart` arrives (`DispatchQueue.main.async`) +- Also invoked from `OpenEventHandler.init` and `Marker_DataApp` `.task` - `ExtractionModel.handleOpenDocument` validates type and starts extract / gate Info.plist advertises Media Asset Protocol and document types for asset media/description collections. @@ -444,6 +459,7 @@ After successful extract, if `colorSwatchSettings.enableSwatch`: - GIF + non-JSON: skip palette (`false`) - Forced off for XLSX extract profile (settings getter) - No images / Skip Image Generation: `false` → ExtractionModel `markProcessAsFinished` → **“Extract done”** (never **“Analysing swatch done”**). Do not `reset()` when entering the swatch phase. +- On `true`, `ImageRenderService.export` calls `progress.setProcesses` with **image URLs**, replacing extract FCPXML rows — caller must `markAllProcessesFinished`, not finish the original FCPXML URL. Settings model: `ColorSwatchSettingsModel` (nested under SettingsStore, Codable). @@ -477,8 +493,8 @@ Notable modules under `Views/`: | Detail | General (File/Roles/Notifications/Updates), Image, Label, Configurations, Databases, Queue (folder drop overlay), About | | Menu commands | App / File / Edit / Sidebar / Configuration / Help | | Onboarding | `@AppStorage("showOnboarding")` sheet | -| Components | `DropTargetOverlay` (main app + WE), `FCPXMLDropModifier`, shared controls | -| Extensions | **`DialogIcon.appDialogIcon()`** for alerts | +| Components | `DropTargetOverlay` (main app + WE), `FCPXMLDropModifier`, `HelpButton` / `OverlayHelpButton`, shared controls | +| Extensions | **`MarkerDataAppIcon` / `.appDialogIcon()`** (`DialogIcon.swift`, `@MainActor`) — About, Workflow Extension header, alerts and confirmation dialogs; see **App icon and dialogs**. Also `ApplyPickerSizing`, `OptionalKeyboardShortcut`. | | Other | `FailedExtractionsView` (truncate + `.help()` tooltips; min ~640×240) | | Pagemaker | `PagemakerView` WebView + `PagemakerPDFExportHandler` (JS → Swift PDF via `NSSavePanel`) | @@ -494,7 +510,7 @@ flowchart TB QU[QueueView] end - subgraph we [Workflow Extension] + subgraph weUI [Workflow Extension] WEX[WorkflowExtensionView Extract] WRO[RolesSettingsView] end @@ -510,12 +526,47 @@ flowchart TB FM --> IN[FCPXMLIntake] IN --> EM[ExtractionModel] - RO --> RM[RolesManager DropDelegate] - WRO --> RM + RO --> RMD[RolesManager DropDelegate] + WRO --> RMD QU --> QM[QueueModel.performDrop] WEX --> HO[Movies cache + open app + DNC] + ICON[MarkerDataAppIcon] + WEX --> ICON + AV[AboutView] + AV --> ICON ``` +Shared Swift files are compiled into **both** targets (one disk file, two Compile Sources memberships): + +```mermaid +flowchart TB + subgraph disk [One file on disk] + DI[DialogIcon.swift] + DTO[DropTargetOverlay.swift] + RM[RolesManager.swift] + SS[SettingsStore.swift] + end + + subgraph mainT [Marker Data target] + MSRC[Compile Sources] + end + + subgraph weT [Workflow Extension target] + WSRC[Compile Sources] + end + + DI --> MSRC + DI --> WSRC + DTO --> MSRC + DTO --> WSRC + RM --> MSRC + RM --> WSRC + SS --> MSRC + SS --> WSRC +``` + +`pbxproj` stores this as **one** `PBXFileReference` and **two** `PBXBuildFile` entries. Seeing `DialogIcon.swift in Sources` twice is correct. + ```mermaid flowchart LR Drop[FCPXML drop on WE Extract] @@ -531,6 +582,48 @@ flowchart LR --- +## App icon and dialogs + +Shared resolution in `Views/Extensions/DialogIcon.swift` (`@MainActor` `MarkerDataAppIcon` + `.appDialogIcon()`). Marker Data does **not** enable `SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor` (unlike Production Data), so the enum and `.appDialogIcon()` must be isolated or Swift 6 warns on `NSApplication.shared.applicationIconImage`. + +**No SF Symbol fallback. Do not flatten the Icon Composer layer PNG into `AppIconSingle`.** + +```mermaid +flowchart TB + DOC["Marker-Data.icon
Icon Composer"] + CAT["Main-app empty AppIcon.appiconset
catalog placeholder"] + APPEX["WE AppIcon.appiconset
bundle / plugin icon only"] + MD["MarkerDataAppIcon.displayIcon"] + ABOUT["AboutView 200×200"] + WEH["WE header 100×100"] + ALERTS[".appDialogIcon"] + + DOC --> MD + CAT -.->|not artwork source| MD + APPEX -.->|do not use for header| MD + MD --> ABOUT + MD --> WEH + MD --> ALERTS +``` + +**Resolution:** + +| Context | Source | +|---------|--------| +| Main app | `NSImage(named: "Marker-Data")`, else `NSApplication.shared.applicationIconImage` | +| Workflow Extension (`.appex`) | Containing `Marker Data.app`: walk `appex` → `PlugIns` → `Contents` → `.app`, then `Bundle.image(forResource: "Marker-Data")` or `NSWorkspace.icon(forFile:)`. Never the appex `applicationIconImage`. | + +| Surface | Wiring | +|---------|--------| +| Dock | `ASSETCATALOG_COMPILER_APPICON_NAME` = Marker-Data (`Marker-Data.icon`, not inside `Assets.xcassets`) | +| About | `MarkerDataAppIcon.image()` — size **200×200** only in `AboutView` | +| Workflow Extension header | `MarkerDataAppIcon.image()` — **100×100** in `WorkflowExtensionView` | +| `.alert` / `.confirmationDialog` | `.appDialogIcon()` after every dialog. Call sites: `Marker_DataApp`, `ContentView`, `ExtractView` (×2), `ConfigurationSettingsView` (3 confirmations + alert), `DatabaseSettingsView` (×2 alerts + delete confirmation), `CreateDBProfileSheet`, `DropboxSetupView`, `InstallShareDestinationView`. Uninstaller uses `applicationIconImage`, not this helper. | + +`Marker-Data.icon` lives in `Source/Marker Data/Marker Data/` beside the catalog. Workflow Extension `ASSETCATALOG_COMPILER_APPICON_NAME` remains **AppIcon** (existing PNG `AppIcon.appiconset`) until that plugin icon is updated separately. + +--- + ## Notifications (full list) Defined in `NotificationNameExtension.swift`: @@ -565,7 +658,7 @@ Changing automation, sandbox, or extension behavior usually requires entitlement |------|--------| | Project | `Source/Marker Data/Marker Data.xcodeproj` | | CI runner / Xcode | `macos-26` / **Xcode 26.6.0** | -| Workflow Extension SDK | `SDK/Workflow_Extensions_1.0.3.dmg` | +| Workflow Extension SDK | `SDK/Workflow_Extensions_1.0.3.dmg` (CI). An older `1.0.2` DMG may sit beside it; do not use it. | | DMG | `appdmg` + `Distribution/dmg-builds/build-marker-data-dmg.json` | | Sparkle feed | `appcast.xml`; generator `Distribution/dmg-builds/sparkle/generate_appcast_script.py` | | Binary refresh workflows | `update_airlift_binary.yml`, `update_csv2notion_neo_binary.yml`, `update_pagemaker.yml` | @@ -597,35 +690,48 @@ Source/Marker Data/Marker Data/ Marker_DataApp.swift ApplicationDelegate.swift Models/ - Extract/ # ExtractionModel, ProgressViewModel, DatabaseUploader, results - Queue/ # QueueModel, QueueInstance (manifestURL), ExtractInfo - Settings/ # Store, Container, Versioning, models + Extract/ + Extraction Model/ # ExtractionModel, ExtractionModel_EventHandlers + ProgressViewModel, DatabaseUploader, ExportProcess, ExportExitStatus + ExtractionResult.swift # ExtractionFailure + ExportFailPhase + Queue/ # QueueModel, QueueInstance (manifestURL), ExtractInfo, QueueStatus + Settings/ # Store (v8), Container, Versioning, SettingsModels, MarkersExtractorModelExtensions Database/ # Manager + Notion/Airtable/Dropbox profiles - Roles/ # RolesManager (+ DropDelegate isDropTargeted), RoleModel - Color Swatch/ # ColorPaletteRenderer (render -> Bool), image merge, color extraction + Roles/ # RolesManager, RolesManager+DropDelegate, RoleModel + Color Swatch/ # ColorPaletteRenderer (render -> Bool), ImageRenderService, ColorsExtractorService Configurations/ # ConfigurationsViewModel Errors/ - Other/ # MainViews, WindowSize, UnifiedExportProfile + Other/ # MainViews, WindowSize (920×520, sidebar 208), UnifiedExportProfile Views/ - Main/, Detail Views/, Menu Bar Commands/, - Components/ # DropTargetOverlay, FCPXMLDropModifier, … - Extensions/ # DialogIcon - Onboarding/, Other/ # FailedExtractionsView, … + Main/ # ContentView, ExtractView + Detail Views/ # General, Image, Label, Configurations, Databases, QueueView, About + Menu Bar Commands/ + Components/ # DropTargetOverlay, FCPXMLDropModifier, HelpButton, OverlayHelpButton, … + Extensions/ # DialogIcon (@MainActor MarkerDataAppIcon + .appDialogIcon), ApplyPickerSizing + Onboarding/, Other/ # FailedExtractionsView, ExportProfilePicker, … FCP Share Destination/ - Install View/, Objective-C Code/, OpenEventHandler (Swift) - Pagemaker/ + Install View/, Objective-C Code/, OpenEventHandler.swift + Pagemaker/ # PagemakerView, PDF export handler, UIDelegate, WebViewStateManager Utilities/ Extensions/ # URL, Color (markerAccent, heroGradient), UTType, NotificationName, … Shell/, Notifications/, - Other/ # FCPXMLIntake, TextClippingReader, LibraryFolders, … + Other/ # FCPXMLIntake, TextClippingReader, LibraryFolders, FileWatcher, … Resources/ airlift, csv2notion_neo, OSAScriptingDefinition.sdef, - *.fcpxdest, Pagemaker.html, entitlements, DefaultConfiguration.json + *.fcpxdest, Pagemaker.html, entitlements, DefaultConfiguration.json (legacy, not live prefs) + Marker-Data.icon # Icon Composer (ASSETCATALOG_COMPILER_APPICON_NAME = Marker-Data) + Assets.xcassets/ # empty AppIcon.appiconset placeholder; no AppIconSingle Source/Marker Data/Workflow Extension/ - WorkflowExtensionView.swift # Extract overlay + handoff; hosts RolesSettingsView + WorkflowExtensionView.swift # Extract overlay + handoff; MarkerDataAppIcon header 100×100; hosts RolesSettingsView; TabView .padding(.bottom, 40) WorkflowExtensionViewController.swift Assets.xcassets/, Info.plist, entitlements, bridging header + # Bundle/plugin icon remains AppIcon.appiconset (do not replace with Marker-Data.icon) + # Header UI loads the containing Marker Data.app icon via DialogIcon.swift + # Compile Sources: see Workflow Extension target table above (shared files = two pbxproj memberships) + +Source/Marker Data/Marker Data Uninstaller/ + UninstallerApp.swift, UninstallerView.swift, MarkerDataUninstaller.swift ``` --- @@ -639,10 +745,12 @@ Source/Marker Data/Workflow Extension/ 5. Shared WE chrome must use `Color.markerAccent` — `accentColor` resolves to Final Cut Pro’s blue inside the appex. 6. Queue is upload-oriented around `extract_info.json` (Notion/Airtable), not a universal browser of all exports. 7. Queue uploads must use `QueueInstance.manifestURL` so moved/copied folders work despite absolute sidecar paths. -8. Extract → swatch: never `reset()`. `ColorPaletteRenderer.render -> Bool`; retitle only via `await applyTaskAppearance` after images exist; skip → `markProcessAsFinished` (“Extract done”); success → `markAllProcessesFinished`. +8. Extract → swatch: never `reset()`. `ColorPaletteRenderer.render -> Bool`; retitle only via `await applyTaskAppearance` after images exist; skip → `markProcessAsFinished` (“Extract done”); success → `markAllProcessesFinished` because `ImageRenderService` replaces process URLs with image URLs. 9. CLI progress and success depend on binary stdout contracts (`NN%`, exit codes). -10. Share Destination and Workflow Extension both assume `/Applications` install. -11. Alert UI must use PNG `AppIconSingle` via `.appDialogIcon()` (Icon Composer dock asset is unreliable in dialogs). +10. Share Destination and Workflow Extension both assume `/Applications` install. The extension does not create Movies cache. +11. Alert / About / Workflow Extension **header** UI must use `@MainActor` `MarkerDataAppIcon` / `.appDialogIcon()` from compiled Icon Composer `Marker-Data.icon`. Do not flatten the layer PNG into `AppIconSingle`. Main-app `AppIcon.appiconset` is a catalog placeholder only. Do **not** replace the Workflow Extension’s bundle/plugin `AppIcon.appiconset`. In the extension header, load the icon from the containing `Marker Data.app` (`appex` → `PlugIns` → `Contents` → `.app`); appex `applicationIconImage` is the extension catalog or Final Cut Pro. 12. Preserve `plaform` spelling when touching database models unless intentionally migrating. 13. FCPXML pasteboard/clipping temps live under `~/Movies/Marker Data Cache/` (not App Support). -14. Definition of done: arm64 Debug+Release build; settings migrate; `.fcpxml`/`.fcpxmld` + pasteboard intake; WE Extract + Roles overlays; queue finds/uploads via `manifestURL`; agent docs (`AGENT.md` / `ARCHITECTURE.md` / `GUARDRAILS.md` / `.cursorrules`) stay aligned. +14. Shared Swift sources appear twice in `project.pbxproj` Compile Sources (one file, two targets). Never “dedupe” those rows. +15. Workflow Extension decodes `SettingsStore` without running migrations — main app must migrate on launch first. +16. Definition of done: unsigned arm64 Debug+Release build; settings migrate; `.fcpxml`/`.fcpxmld` + pasteboard intake; WE Extract + Roles overlays; queue finds/uploads via `manifestURL`; agent docs (`AGENT.md` / `ARCHITECTURE.md` / `GUARDRAILS.md` / `.cursorrules`) stay aligned. diff --git a/CHANGELOG.md b/CHANGELOG.md index 45c087ad..5cb393bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +### 2.1.0 (24) + +**🎉 Released:** +- TBA + +**Marker Data** update requires a fresh install, so please use the included Uninstaller to remove the previous version before installing, as configurations will need to be recreated afterwards. + +**🔨 Improvements:** +- Updated Marker Data with a new application icon +- Updated Marker Data's Workflow Extension with a new application icon +- Updated Marker Data's Uninstaller with a new application icon +- Updated Marker Data's DMG icon +- Updated core dependencies + +--- + ### 2.0.6 (23) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 421d8ad7..90c2ef35 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -91,7 +91,7 @@ We distribute the Release build `.app` directly from **Xcode's Derived Data** ra Yes — Archive strips debug info, resulting in smaller binaries. However: -- Our final DMG is already **under 50MB**. +- Our final DMG is already **under 100MB**. - The slight size gain isn’t worth sacrificing debug convenience. - We prioritise **debuggability and reproducibility** over minimal file size. diff --git a/Distribution/dmg-builds/dmg-background.png b/Distribution/dmg-builds/dmg-background.png old mode 100755 new mode 100644 index d60f5bb7..55cc3a31 Binary files a/Distribution/dmg-builds/dmg-background.png and b/Distribution/dmg-builds/dmg-background.png differ diff --git a/Distribution/dmg-builds/dmg-background@2x.png b/Distribution/dmg-builds/dmg-background@2x.png old mode 100755 new mode 100644 index 7079820d..199f1528 Binary files a/Distribution/dmg-builds/dmg-background@2x.png and b/Distribution/dmg-builds/dmg-background@2x.png differ diff --git a/Distribution/dmg-builds/marker-data-dmg-icon.icns b/Distribution/dmg-builds/marker-data-dmg-icon.icns index eed2f4c4..1591ff26 100644 Binary files a/Distribution/dmg-builds/marker-data-dmg-icon.icns and b/Distribution/dmg-builds/marker-data-dmg-icon.icns differ diff --git a/GUARDRAILS.md b/GUARDRAILS.md index c7135b3d..b2cc4503 100644 --- a/GUARDRAILS.md +++ b/GUARDRAILS.md @@ -17,19 +17,20 @@ Read with **`AGENT.md`** (how to change things), **`ARCHITECTURE.md`** (how it w | Area | Rule | |------|------| -| **Settings** | Bump `SettingsStore.version`, add a dict migration `case`, wire UI, and pass export-related fields through `markersExtractorSettings(fcpxmlFileUrl:)`. | +| **Settings** | Bump `SettingsStore.version`, add a dict migration `case`, wire UI, and pass export-related fields through `markersExtractorSettings(fcpxmlFileUrl:)`. The Workflow Extension compiles `SettingsStore` and decodes prefs **without** running migrations — main app must launch after a schema bump. | | **Config names** | Throw `ConfigurationSaveError.nameAlreadyExists` on add / rename / duplicate collisions — never overwrite `{name}.json` silently. | -| **Alerts** | Chain `.appDialogIcon()` after every `.alert` / confirmation dialog (`DialogIcon.swift` → PNG `AppIconSingle`). | +| **Alerts** | Chain `.appDialogIcon()` after every `.alert` / `.confirmationDialog` (`DialogIcon.swift` → `@MainActor` `MarkerDataAppIcon` from compiled `Marker-Data.icon`). Uninstaller is a separate target and uses `applicationIconImage`. | | **Extract intake** | Route Finder files, text clippings, and Final Cut Pro pasteboard drops through `FCPXMLIntake` → `ExtractionModel.receiveFiles` / `receiveItemProviders`. | -| **Drop overlays** | Use shared `DropTargetOverlay` on main-app Extract / Roles / Queue **and** Workflow Extension Extract + Roles; keep extension Compile Sources including `DropTargetOverlay` + `ColorExtension` (+ shared Roles sources). | +| **Drop overlays** | Use shared `DropTargetOverlay` on main-app Extract / Roles / Queue **and** Workflow Extension Extract + Roles. Shared files have **two** Compile Sources memberships in `project.pbxproj` (one file, two targets) — never delete one row. Extension membership includes overlay + `ColorExtension` + `DialogIcon.swift` + `HelpButton` / `OverlayHelpButton` plus Roles/settings files listed in **ARCHITECTURE.md**. | | **Shared WE chrome color** | Use `Color.markerAccent` (system indigo) for overlay borders and other shared chrome hosted in FCP — not `Color.accentColor`. | | **Queue uploads** | Upload via `QueueInstance.manifestURL` (prefer JSON beside the scanned/dropped folder; fall back to sidecar `jsonURL`). | -| **Progress phases** | Do **not** `ProgressViewModel.reset()` when entering swatch. Call `await applyTaskAppearance` **only inside** `ColorPaletteRenderer` after image checks pass (MainActor). On skip: `markProcessAsFinished` → **“Extract done”**. On render: `markAllProcessesFinished`. | +| **Progress phases** | Do **not** `ProgressViewModel.reset()` when entering swatch. Call `await applyTaskAppearance` **only inside** `ColorPaletteRenderer` after image checks pass (MainActor). On skip: `markProcessAsFinished` → **“Extract done”**. On render: `markAllProcessesFinished` (`ImageRenderService` replaces extract URLs with image URLs). | | **No-media / empty swatch** | `ColorPaletteRenderer.render` returns `false` without retitling; finish the extract URL with `markProcessAsFinished` so the bar never shows **“Analysing swatch done”**. | | **Install location** | Keep the `/Applications` warning; Workflow Extension opens `/Applications/Marker Data.app`. | | **Uninstaller** | If the app gains new on-disk paths, update `MarkerDataUninstaller.run()` cleanup list. | | **Architecture** | Prefer Apple Silicon (`arm64`) only; CI uses `macos-26` + **Xcode 26.6.0**. | | **Opaque CLIs** | Treat `airlift` / `csv2notion_neo` as black boxes; only the args in `DatabaseUploader` / `DropboxSetupModel` are the contract. | +| **App icon** | Main app: Icon Composer **`Marker-Data.icon`** (name matches `ASSETCATALOG_COMPILER_APPICON_NAME`). Keep the empty main-app `AppIcon.appiconset` placeholder. Do not flatten the layer PNG into `AppIconSingle`. Workflow Extension **header** loads the icon from the containing `Marker Data.app`. Do **not** replace the Workflow Extension bundle/plugin icon (`AppIcon.appiconset`). | --- @@ -45,8 +46,11 @@ Read with **`AGENT.md`** (how to change things), **`ARCHITECTURE.md`** (how it w | **Absolute Queue paths** | Never upload solely from `ExtractInfo.jsonURL` when a same-named JSON exists in the queue folder (moved/copied exports). | | **Host accent in WE** | Never rely on `Color.accentColor` / `.accent` for shared UI inside the Workflow Extension (inherits Final Cut Pro’s blue). | | **`plaform`** | Never “fix” the spelling on database models unless intentionally migrating. | +| **Shared pbxproj membership** | Never remove a second `Foo.swift in Sources` row for a file that both the main app and Workflow Extension compile. | | **Intel** | Never add `x86_64` as a supported architecture. | | **Silent config overwrite** | Never replace an existing configuration file because the name already exists. | +| **AppIconSingle flatten** | Never copy the Icon Composer layer PNG into `AppIconSingle` (skips glass / fill). | +| **WE header icon** | Never use the appex `AppIcon.appiconset` / `applicationIconImage` for the Workflow Extension window header — that is the bundle/plugin icon (update separately). | --- @@ -55,8 +59,8 @@ Read with **`AGENT.md`** (how to change things), **`ARCHITECTURE.md`** (how it w | Surface | Message | Icon | Notes | |---------|---------|------|-------| | **Extract** (main app) | Default: **“Drop to Extract Marker Metadata”**; subtitle **“Use Marker Data's Share Destination for Image Extraction”**. Hero caption under title: **“Drop a timeline from Final Cut Pro, or an .fcpxml / .fcpxmld file”** | `arrow.down.doc.fill` | Hide while `extractionInProgress`. Border: `Color.markerAccent`; arrow: `Color.heroGradient`. | -| **Extract** (Workflow Extension) | **“Drop to Open Marker Data”** | default | `WorkflowExtensionView` + `isExtractDropTargeted`; `subtitle: nil`; handoff writes Movies-cache FCPXML then opens the app. Idle hint: *“Drag & Drop Final Cut Pro Project to Open Marker Data”*. | -| **Roles** (app + Workflow Extension) | **“Drop to Retrieve Roles Metadata”** | default | `subtitle: nil`; hide while `loadingInProgress`. | +| **Extract** (Workflow Extension) | **“Drop to Open Marker Data”** | default | `WorkflowExtensionView` + `isExtractDropTargeted`; `subtitle: nil`; handoff writes Movies-cache FCPXML then opens the app. Idle hint: *“Drag & Drop Final Cut Pro Project to Open Marker Data”*. Header: `MarkerDataAppIcon.image()` 100×100 from containing `Marker Data.app`. `TabView` `.padding(.bottom, 40)` so the drop zone stays above `overlayHelpButton`. | +| **Roles** (app + Workflow Extension) | **“Drop to Retrieve Roles Metadata”** | default | `subtitle: nil`; hide while `loadingInProgress`. Idle empty-state text is separate from the overlay. | | **Queue** | **“Drop Extract Folders (Notion or Airtable) into Queue”** | `folder.fill` | `subtitle: nil`; hide while `uploadInProgress`; drop blocked during upload. | Shared component: `Views/Components/DropTargetOverlay.swift` (main app + Workflow Extension Compile Sources). @@ -66,29 +70,33 @@ Shared component: `Views/Components/DropTargetOverlay.swift` (main app + Workflo ## Signs (learned pitfalls) 1. **`extract_info.json` stores absolute `jsonURL`.** After the user moves/copies an export folder, uploading the sidecar path fails (`FileNotFoundError`). Always resolve with `manifestURL`. -2. **Progress `reset()` before swatch** clears processes and can leave 0% — never reset when entering swatch. Retitle with `await applyTaskAppearance` only after `ColorPaletteRenderer` confirms images (returns `true`); then `markAllProcessesFinished`. If render returns `false`, keep **“Extract done”** via `markProcessAsFinished` (avoids **“Analysing swatch done”** with no stills). +2. **Progress `reset()` before swatch** clears processes and can leave 0% — never reset when entering swatch. Retitle with `await applyTaskAppearance` only after `ColorPaletteRenderer` confirms images (returns `true`); then `markAllProcessesFinished` because `ImageRenderService.setProcesses` replaces FCPXML URLs with image URLs. If render returns `false`, keep **“Extract done”** via `markProcessAsFinished` (avoids **“Analysing swatch done”** with no stills). 3. **`applyTaskAppearance` is `@MainActor`.** From `ColorPaletteRenderer` (nonisolated), call `await progress.applyTaskAppearance(...)` — a bare call fails isolation. 4. **Ignore late KVO** after a process is marked finished (`ProgressViewModel.updateProgress`) or the bar can bounce backward. 5. **FCP timeline → Dock** often cannot deliver a file URL (pasteboard-only). Supported paths: drop on Extract panel, open `.fcpxml`/`.fcpxmld` via Dock/Finder Open With, Workflow Extension / Share Destination handoffs. -6. **Temporary FCP pasteboard XML** is written under `~/Movies/Marker Data Cache/` (`FCPXMLIntake.writeTemporaryFCPXML`) — Marker Data convention, not App Support Cache. -7. **Workflow Extension accent:** `Color.accentColor` / `.accent` inside the appex often resolves to Final Cut Pro’s blue. Shared chrome (e.g. `DropTargetOverlay` border) must use `Color.markerAccent` (system indigo, matching Assets `AccentColor`). Extension Compile Sources must still include `DropTargetOverlay` + `ColorExtension`. +6. **Temporary FCP pasteboard XML** is written under `~/Movies/Marker Data Cache/` (`FCPXMLIntake.writeTemporaryFCPXML`) — Marker Data convention, not App Support Cache. Intake creates that folder; the Workflow Extension **write** of `WorkflowExtensionExport.fcpxml` does **not**. +7. **Workflow Extension accent:** `Color.accentColor` / `.accent` inside the appex often resolves to Final Cut Pro’s blue. Shared chrome (e.g. `DropTargetOverlay` border) must use `Color.markerAccent` (system indigo, matching Assets `AccentColor`). 8. **Workflow Extension Extract vs Roles:** Extract handoff lives in `WorkflowExtensionView` (`.onDrop([.fcpxml])`); Roles uses shared `RolesSettingsView` / `RolesManager` `DropDelegate`. Both show overlays — do not assume only Roles has one. -9. **Icon Composer** Dock asset (`Marker-Data.icon`) often yields a blank `.alert` glyph — PNG via `.appDialogIcon()` is mandatory. +9. **Icon Composer dialogs / WE header:** SwiftUI `.alert` and `.confirmationDialog` still need `.appDialogIcon()` (`@MainActor` `MarkerDataAppIcon` from compiled `Marker-Data.icon`). Do not flatten the layer PNG into `AppIconSingle` (skips glass / fill). Workflow Extension **header** loads the icon from the containing `Marker Data.app` (`appex` → `PlugIns` → `Contents` → `.app`) — the appex’s `applicationIconImage` is its `AppIcon.appiconset` (or Final Cut Pro). Leave the extension’s bundle/plugin `AppIcon.appiconset` unchanged. 10. **Dual Sparkle controllers** (`Marker_DataApp` + `ApplicationDelegate`) — don’t “simplify” without understanding `.updateAvailable` / `bestValidUpdate`. -11. **`OpenEventHandler`** must re-register on `.FCPShareStart`; keep registration on the main queue. +11. **`OpenEventHandler`** must re-register on `.FCPShareStart`; keep registration on the main queue. `setupHandler()` also runs from `init` and `Marker_DataApp` `.task`. 12. **Failed Tasks table** (`FailedExtractionsView`): one-line truncate + `.help()` tooltips; min frame ~640×240. +13. **Workflow Extension help vs drop:** `overlayHelpButton` is bottom-trailing. Keep Extract/Roles `TabView` `.padding(.bottom, 40)` so the drop overlay does not crowd the “?”. +14. **Shared Compile Sources look duplicated in `pbxproj`.** One `DialogIcon.swift` (and other shared files) correctly appears twice as `PBXBuildFile` — main app + Workflow Extension. Deleting one membership breaks the extension. +15. **Workflow Extension does not migrate settings.** `RolesManager` JSON-decodes `SettingsStore` with no `SettingsVersioningManager`. Open the main app after a schema bump. --- ## Definition of done (guardrail checklist) -- [ ] arm64 Debug + Release build succeeds (main app embeds Workflow Extension). +- [ ] Unsigned arm64 Debug + Release build succeeds (`CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO`; main app embeds Workflow Extension). - [ ] Settings load/migrate (`preferences.json` + `Configurations/*.json`). - [ ] Extract accepts `.fcpxml` / `.fcpxmld`, FCP pasteboard drop, and text clippings via `FCPXMLIntake`. - [ ] Drop overlays appear on main-app Extract / Roles / Queue **and** Workflow Extension Extract + Roles with the copy above. - [ ] Queue finds `extract_info.json` folders and uploads via `manifestURL` (relocated folders work). - [ ] No-media / skipped swatch finishes as **“Extract done”** (not **“Analysing swatch done”**); `ColorPaletteRenderer.render` → `Bool` drives finish path. -- [ ] New alerts use `.appDialogIcon()`. +- [ ] New alerts **and** confirmation dialogs use `.appDialogIcon()` (`MarkerDataAppIcon` from compiled `Marker-Data.icon`). +- [ ] App icon still Icon Composer `Marker-Data.icon` for the **main app** (not inside `Assets.xcassets`; do not flatten layer PNG into `AppIconSingle`). Workflow Extension **header** uses the containing `Marker Data.app` icon; bundle/plugin icon stays `AppIcon.appiconset`. - [ ] Settings changes include version + migration + UI + export bridge when required. - [ ] New on-disk paths are reflected in the Uninstaller. - [ ] `AGENT.md` / `ARCHITECTURE.md` / `GUARDRAILS.md` / `.cursorrules` updated when behavior changes. diff --git a/Press Kit/Marker Data - Press Release.pdf b/Press Kit/Marker Data - Press Release.pdf deleted file mode 100644 index 7448d69d..00000000 Binary files a/Press Kit/Marker Data - Press Release.pdf and /dev/null differ diff --git a/Press Kit/press-kit.zip b/Press Kit/press-kit.zip deleted file mode 100644 index 761b143f..00000000 Binary files a/Press Kit/press-kit.zip and /dev/null differ diff --git a/Source/Marker Data/Marker Data Uninstaller/Marker-Data-Uninstaller.icon/Assets/Marker-Data-Liquid-Glass-L01-Dark.png b/Source/Marker Data/Marker Data Uninstaller/Marker-Data-Uninstaller.icon/Assets/Marker-Data-Liquid-Glass-L01-Dark.png deleted file mode 100644 index 34cc05b1..00000000 Binary files a/Source/Marker Data/Marker Data Uninstaller/Marker-Data-Uninstaller.icon/Assets/Marker-Data-Liquid-Glass-L01-Dark.png and /dev/null differ diff --git a/Source/Marker Data/Marker Data Uninstaller/Marker-Data-Uninstaller.icon/Assets/Marker-Data-Liquid-Glass-L02=Dark.png b/Source/Marker Data/Marker Data Uninstaller/Marker-Data-Uninstaller.icon/Assets/Marker-Data-Liquid-Glass-L02=Dark.png deleted file mode 100644 index 6c88dcf6..00000000 Binary files a/Source/Marker Data/Marker Data Uninstaller/Marker-Data-Uninstaller.icon/Assets/Marker-Data-Liquid-Glass-L02=Dark.png and /dev/null differ diff --git a/Source/Marker Data/Marker Data Uninstaller/Marker-Data-Uninstaller.icon/Assets/Marker-Data-Liquid-Glass-L03.png b/Source/Marker Data/Marker Data Uninstaller/Marker-Data-Uninstaller.icon/Assets/Marker-Data-Liquid-Glass-L03.png deleted file mode 100644 index 9f337182..00000000 Binary files a/Source/Marker Data/Marker Data Uninstaller/Marker-Data-Uninstaller.icon/Assets/Marker-Data-Liquid-Glass-L03.png and /dev/null differ diff --git a/Source/Marker Data/Marker Data Uninstaller/Marker-Data-Uninstaller.icon/Assets/marker-data-uninstall-app-icon.png b/Source/Marker Data/Marker Data Uninstaller/Marker-Data-Uninstaller.icon/Assets/marker-data-uninstall-app-icon.png new file mode 100644 index 00000000..bd3c2cc2 Binary files /dev/null and b/Source/Marker Data/Marker Data Uninstaller/Marker-Data-Uninstaller.icon/Assets/marker-data-uninstall-app-icon.png differ diff --git a/Source/Marker Data/Marker Data Uninstaller/Marker-Data-Uninstaller.icon/icon.json b/Source/Marker Data/Marker Data Uninstaller/Marker-Data-Uninstaller.icon/icon.json index 16b97aa9..a3752a04 100644 --- a/Source/Marker Data/Marker Data Uninstaller/Marker-Data-Uninstaller.icon/icon.json +++ b/Source/Marker Data/Marker Data Uninstaller/Marker-Data-Uninstaller.icon/icon.json @@ -4,95 +4,28 @@ }, "groups" : [ { - "layers" : [ + "blend-mode" : "normal", + "blur-material" : 0.5, + "hidden-specializations" : [ { - "fill" : "none", - "glass" : false, - "image-name" : "Marker-Data-Liquid-Glass-L03.png", - "name" : "Red X", - "position-specializations" : [ - { - "idiom" : "macOS", - "value" : { - "scale" : 0.8, - "translation-in-points" : [ - 0, - 0 - ] - } - } - ] + "idiom" : "macOS", + "value" : false } ], - "name" : "Red X", - "shadow" : { - "kind" : "neutral", - "opacity" : 0.5 - }, - "translucency" : { - "enabled" : true, - "value" : 0.5 - } - }, - { - "layers" : [ - { - "fill" : "none", - "glass" : false, - "image-name" : "Marker-Data-Liquid-Glass-L01-Dark.png", - "name" : "Marker Data Icon", - "position-specializations" : [ - { - "idiom" : "macOS", - "value" : { - "scale" : 0.45, - "translation-in-points" : [ - -12, - -60 - ] - } - } - ] - } - ], - "name" : "Marker Data Icon", - "shadow" : { - "kind" : "neutral", - "opacity" : 0.5 - }, - "translucency" : { - "enabled" : true, - "value" : 0.5 - } - }, - { "layers" : [ { - "fill" : "none", - "glass" : false, - "image-name" : "Marker-Data-Liquid-Glass-L02=Dark.png", - "name" : "Background", - "position-specializations" : [ - { - "idiom" : "macOS", - "value" : { - "scale" : 0.38, - "translation-in-points" : [ - 0, - 18 - ] - } - } - ] + "image-name" : "marker-data-uninstall-app-icon.png", + "name" : "marker-data-uninstall-app-icon" } ], - "name" : "Background", + "lighting" : "combined", "shadow" : { "kind" : "neutral", "opacity" : 0.5 }, + "specular" : true, "translucency" : { - "enabled" : true, + "enabled" : false, "value" : 0.5 } } @@ -102,4 +35,4 @@ "macOS" ] } -} \ No newline at end of file +} diff --git a/Source/Marker Data/Marker Data.xcodeproj/project.pbxproj b/Source/Marker Data/Marker Data.xcodeproj/project.pbxproj index b34e1b9c..8f4fc0f0 100644 --- a/Source/Marker Data/Marker Data.xcodeproj/project.pbxproj +++ b/Source/Marker Data/Marker Data.xcodeproj/project.pbxproj @@ -97,6 +97,7 @@ 4176E7E42F608D51001547D7 /* ApplyPickerSizing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4176E7E32F608D51001547D7 /* ApplyPickerSizing.swift */; }; 4176E7E72F609243001547D7 /* OptionalKeyboardShortcut.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4176E7E62F609243001547D7 /* OptionalKeyboardShortcut.swift */; }; 4176E7F02F7A0001001547D7 /* DialogIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4176E7F12F7A0001001547D7 /* DialogIcon.swift */; }; + 4176E7F22F7A0002001547D7 /* DialogIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4176E7F12F7A0001001547D7 /* DialogIcon.swift */; }; 4176E7E92F61772A001547D7 /* FixedPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4176E7E82F61772A001547D7 /* FixedPicker.swift */; }; 4176E7EA2F632E7F001547D7 /* OverlayHelpButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47411FDB2A3EA140005890FE /* OverlayHelpButton.swift */; }; 4176E7EC2F632FA9001547D7 /* HelpButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4176E7EB2F632FA9001547D7 /* HelpButton.swift */; }; @@ -1264,6 +1265,7 @@ 41A055702B8B7685006AC0E4 /* NotificationFrequency.swift in Sources */, 4155B7172B62E131008D3505 /* RolesSettingsView.swift in Sources */, 41A700012F7A10010001A00B /* DropTargetOverlay.swift in Sources */, + 4176E7F22F7A0002001547D7 /* DialogIcon.swift in Sources */, 41A055752B8B776F006AC0E4 /* RolesManager.swift in Sources */, 41A055762B8B8442006AC0E4 /* URLExtension.swift in Sources */, 4155B7232B653D35008D3505 /* RoleModel.swift in Sources */, @@ -1907,7 +1909,7 @@ repositoryURL = "https://github.com/TheAcharya/MarkersExtractor"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 0.4.7; + minimumVersion = 0.4.8; }; }; 4155B72A2B668C0D008D3505 /* XCRemoteSwiftPackageReference "swift-log-oslog" */ = { diff --git a/Source/Marker Data/Marker Data/Assets.xcassets/AppIcon.appiconset/Contents.json b/Source/Marker Data/Marker Data/Assets.xcassets/AppIcon.appiconset/Contents.json index 483b7037..3f00db43 100644 --- a/Source/Marker Data/Marker Data/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/Source/Marker Data/Marker Data/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,61 +1,51 @@ { "images" : [ { - "filename" : "Icon-16@1x.png", "idiom" : "mac", "scale" : "1x", "size" : "16x16" }, { - "filename" : "Icon-16@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "16x16" }, { - "filename" : "Icon-32@1x.png", "idiom" : "mac", "scale" : "1x", "size" : "32x32" }, { - "filename" : "Icon-32@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "32x32" }, { - "filename" : "Icon-128@1x.png", "idiom" : "mac", "scale" : "1x", "size" : "128x128" }, { - "filename" : "Icon-128@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "128x128" }, { - "filename" : "Icon-256@1x.png", "idiom" : "mac", "scale" : "1x", "size" : "256x256" }, { - "filename" : "Icon-256@2x 1.png", "idiom" : "mac", "scale" : "2x", "size" : "256x256" }, { - "filename" : "Icon-256@2x.png", "idiom" : "mac", "scale" : "1x", "size" : "512x512" }, { - "filename" : "Icon-1024@1x.png", "idiom" : "mac", "scale" : "2x", "size" : "512x512" diff --git a/Source/Marker Data/Marker Data/Assets.xcassets/AppIcon.appiconset/Icon-1024@1x.png b/Source/Marker Data/Marker Data/Assets.xcassets/AppIcon.appiconset/Icon-1024@1x.png deleted file mode 100644 index a5ff929f..00000000 Binary files a/Source/Marker Data/Marker Data/Assets.xcassets/AppIcon.appiconset/Icon-1024@1x.png and /dev/null differ diff --git a/Source/Marker Data/Marker Data/Assets.xcassets/AppIcon.appiconset/Icon-128@1x.png b/Source/Marker Data/Marker Data/Assets.xcassets/AppIcon.appiconset/Icon-128@1x.png deleted file mode 100644 index 720f4ee9..00000000 Binary files a/Source/Marker Data/Marker Data/Assets.xcassets/AppIcon.appiconset/Icon-128@1x.png and /dev/null differ diff --git a/Source/Marker Data/Marker Data/Assets.xcassets/AppIcon.appiconset/Icon-128@2x.png b/Source/Marker Data/Marker Data/Assets.xcassets/AppIcon.appiconset/Icon-128@2x.png deleted file mode 100644 index 3e37ad00..00000000 Binary files a/Source/Marker Data/Marker Data/Assets.xcassets/AppIcon.appiconset/Icon-128@2x.png and /dev/null differ diff --git a/Source/Marker Data/Marker Data/Assets.xcassets/AppIcon.appiconset/Icon-16@1x.png b/Source/Marker Data/Marker Data/Assets.xcassets/AppIcon.appiconset/Icon-16@1x.png deleted file mode 100644 index 4f162f39..00000000 Binary files a/Source/Marker Data/Marker Data/Assets.xcassets/AppIcon.appiconset/Icon-16@1x.png and /dev/null differ diff --git a/Source/Marker Data/Marker Data/Assets.xcassets/AppIcon.appiconset/Icon-16@2x.png b/Source/Marker Data/Marker Data/Assets.xcassets/AppIcon.appiconset/Icon-16@2x.png deleted file mode 100644 index 3340b982..00000000 Binary files a/Source/Marker Data/Marker Data/Assets.xcassets/AppIcon.appiconset/Icon-16@2x.png and /dev/null differ diff --git a/Source/Marker Data/Marker Data/Assets.xcassets/AppIcon.appiconset/Icon-256@1x.png b/Source/Marker Data/Marker Data/Assets.xcassets/AppIcon.appiconset/Icon-256@1x.png deleted file mode 100644 index 158ca1aa..00000000 Binary files a/Source/Marker Data/Marker Data/Assets.xcassets/AppIcon.appiconset/Icon-256@1x.png and /dev/null differ diff --git a/Source/Marker Data/Marker Data/Assets.xcassets/AppIcon.appiconset/Icon-256@2x 1.png b/Source/Marker Data/Marker Data/Assets.xcassets/AppIcon.appiconset/Icon-256@2x 1.png deleted file mode 100644 index e57deaa1..00000000 Binary files a/Source/Marker Data/Marker Data/Assets.xcassets/AppIcon.appiconset/Icon-256@2x 1.png and /dev/null differ diff --git a/Source/Marker Data/Marker Data/Assets.xcassets/AppIcon.appiconset/Icon-256@2x.png b/Source/Marker Data/Marker Data/Assets.xcassets/AppIcon.appiconset/Icon-256@2x.png deleted file mode 100644 index e57deaa1..00000000 Binary files a/Source/Marker Data/Marker Data/Assets.xcassets/AppIcon.appiconset/Icon-256@2x.png and /dev/null differ diff --git a/Source/Marker Data/Marker Data/Assets.xcassets/AppIcon.appiconset/Icon-32@1x.png b/Source/Marker Data/Marker Data/Assets.xcassets/AppIcon.appiconset/Icon-32@1x.png deleted file mode 100644 index d0a2dd12..00000000 Binary files a/Source/Marker Data/Marker Data/Assets.xcassets/AppIcon.appiconset/Icon-32@1x.png and /dev/null differ diff --git a/Source/Marker Data/Marker Data/Assets.xcassets/AppIcon.appiconset/Icon-32@2x.png b/Source/Marker Data/Marker Data/Assets.xcassets/AppIcon.appiconset/Icon-32@2x.png deleted file mode 100644 index 63ed3cf1..00000000 Binary files a/Source/Marker Data/Marker Data/Assets.xcassets/AppIcon.appiconset/Icon-32@2x.png and /dev/null differ diff --git a/Source/Marker Data/Marker Data/Assets.xcassets/AppIconSingle.imageset/Contents.json b/Source/Marker Data/Marker Data/Assets.xcassets/AppIconSingle.imageset/Contents.json deleted file mode 100644 index f9efbad4..00000000 --- a/Source/Marker Data/Marker Data/Assets.xcassets/AppIconSingle.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "Icon-1024@1x.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Source/Marker Data/Marker Data/Assets.xcassets/AppIconSingle.imageset/Icon-1024@1x.png b/Source/Marker Data/Marker Data/Assets.xcassets/AppIconSingle.imageset/Icon-1024@1x.png deleted file mode 100644 index a5ff929f..00000000 Binary files a/Source/Marker Data/Marker Data/Assets.xcassets/AppIconSingle.imageset/Icon-1024@1x.png and /dev/null differ diff --git a/Source/Marker Data/Marker Data/Marker-Data.icon/Assets/Marker-Data-Liquid-Glass-L01.png b/Source/Marker Data/Marker Data/Marker-Data.icon/Assets/Marker-Data-Liquid-Glass-L01.png deleted file mode 100644 index e4c2374d..00000000 Binary files a/Source/Marker Data/Marker Data/Marker-Data.icon/Assets/Marker-Data-Liquid-Glass-L01.png and /dev/null differ diff --git a/Source/Marker Data/Marker Data/Marker-Data.icon/Assets/Marker-Data-Liquid-Glass-L02.png b/Source/Marker Data/Marker Data/Marker-Data.icon/Assets/Marker-Data-Liquid-Glass-L02.png deleted file mode 100644 index 7e0c712a..00000000 Binary files a/Source/Marker Data/Marker Data/Marker-Data.icon/Assets/Marker-Data-Liquid-Glass-L02.png and /dev/null differ diff --git a/Source/Marker Data/Marker Data/Marker-Data.icon/Assets/marker-data-app-icon.png b/Source/Marker Data/Marker Data/Marker-Data.icon/Assets/marker-data-app-icon.png new file mode 100644 index 00000000..2329da24 Binary files /dev/null and b/Source/Marker Data/Marker Data/Marker-Data.icon/Assets/marker-data-app-icon.png differ diff --git a/Source/Marker Data/Marker Data/Marker-Data.icon/icon.json b/Source/Marker Data/Marker Data/Marker-Data.icon/icon.json index e59024ec..418430da 100644 --- a/Source/Marker Data/Marker Data/Marker-Data.icon/icon.json +++ b/Source/Marker Data/Marker Data/Marker-Data.icon/icon.json @@ -1,107 +1,31 @@ { - "fill" : "automatic", + "fill" : { + "automatic-gradient" : "extended-srgb:0.00000,0.53333,1.00000,1.00000" + }, "groups" : [ { - "layers" : [ + "blend-mode" : "normal", + "blur-material" : 0.5, + "hidden-specializations" : [ { - "blend-mode-specializations" : [ - { - "appearance" : "tinted", - "value" : "normal" - } - ], - "fill-specializations" : [ - { - "value" : "none" - }, - { - "appearance" : "tinted", - "value" : "none" - } - ], - "glass" : false, - "hidden" : false, - "image-name" : "Marker-Data-Liquid-Glass-L01.png", - "name" : "Marker Data Icon", - "opacity-specializations" : [ - { - "value" : 1 - }, - { - "appearance" : "tinted", - "value" : 1 - } - ], - "position" : { - "scale" : 0.45, - "translation-in-points" : [ - -12, - -60 - ] - } + "idiom" : "macOS", + "value" : false } ], - "name" : "Marker Data Icon", - "shadow" : { - "kind" : "neutral", - "opacity" : 0.5 - }, - "translucency" : { - "enabled" : true, - "value" : 0.5 - } - }, - { "layers" : [ { - "blend-mode-specializations" : [ - { - "value" : "normal" - }, - { - "appearance" : "tinted", - "value" : "normal" - } - ], - "fill-specializations" : [ - { - "value" : "none" - }, - { - "appearance" : "dark", - "value" : "none" - }, - { - "appearance" : "tinted", - "value" : "none" - } - ], - "glass" : false, - "hidden" : false, - "image-name" : "Marker-Data-Liquid-Glass-L02.png", - "name" : "Background", - "opacity-specializations" : [ - { - "appearance" : "tinted", - "value" : 1 - } - ], - "position" : { - "scale" : 0.38, - "translation-in-points" : [ - 0, - 18 - ] - } + "image-name" : "marker-data-app-icon.png", + "name" : "marker-data-app-icon" } ], - "name" : "Background", + "lighting" : "combined", "shadow" : { "kind" : "neutral", "opacity" : 0.5 }, + "specular" : true, "translucency" : { - "enabled" : true, + "enabled" : false, "value" : 0.5 } } diff --git a/Source/Marker Data/Marker Data/Models/Settings/SettingsStore.swift b/Source/Marker Data/Marker Data/Models/Settings/SettingsStore.swift index 5f013bb0..454db125 100644 --- a/Source/Marker Data/Marker Data/Models/Settings/SettingsStore.swift +++ b/Source/Marker Data/Marker Data/Models/Settings/SettingsStore.swift @@ -86,7 +86,7 @@ struct SettingsStore: Sendable, Codable, Hashable, Equatable, Identifiable { } } - var horizonalAlignment: MarkerLabelProperties.AlignHorizontal + var horizontalAlignment: MarkerLabelProperties.AlignHorizontal var verticalAlignment: MarkerLabelProperties.AlignVertical var copyrightText: String @@ -157,7 +157,7 @@ struct SettingsStore: Sendable, Codable, Hashable, Equatable, Identifiable { fontColorOpacity: 100, strokeColor: .black, overlays: [], - horizonalAlignment: .left, + horizontalAlignment: .left, verticalAlignment: .top, copyrightText: "", hideLabelNames: false, @@ -235,7 +235,7 @@ struct SettingsStore: Sendable, Codable, Hashable, Equatable, Identifiable { imageLabelFontColor: self.fontColor.hex, imageLabelFontStrokeColor: self.strokeColor.hex, imageLabelFontStrokeWidth: strokeSize, - imageLabelAlignHorizontal: self.horizonalAlignment, + imageLabelAlignHorizontal: self.horizontalAlignment, imageLabelAlignVertical: self.verticalAlignment, imageLabelHideNames: self.hideLabelNames, exportFolderFormat: self.folderFormat diff --git a/Source/Marker Data/Marker Data/Views/Detail Views/AboutView.swift b/Source/Marker Data/Marker Data/Views/Detail Views/AboutView.swift index 0d86078f..42402438 100644 --- a/Source/Marker Data/Marker Data/Views/Detail Views/AboutView.swift +++ b/Source/Marker Data/Marker Data/Views/Detail Views/AboutView.swift @@ -10,8 +10,7 @@ import SwiftUI struct AboutView: View { var body: some View { VStack(spacing: 20) { - Image("AppIconSingle") - .resizable() + MarkerDataAppIcon.image() .frame(width: 200, height: 200) Text("Marker Data") diff --git a/Source/Marker Data/Marker Data/Views/Detail Views/Label/GeneralLabelSettingsView.swift b/Source/Marker Data/Marker Data/Views/Detail Views/Label/GeneralLabelSettingsView.swift index 6dbba613..1a3a6823 100644 --- a/Source/Marker Data/Marker Data/Views/Detail Views/Label/GeneralLabelSettingsView.swift +++ b/Source/Marker Data/Marker Data/Views/Detail Views/Label/GeneralLabelSettingsView.swift @@ -110,7 +110,7 @@ struct GeneralLabelSettingsView: View { var body: some View { Section(header: SectionHeader("Alignment")) { - FixedPicker("Horizontal", selection: $settings.store.horizonalAlignment) { + FixedPicker("Horizontal", selection: $settings.store.horizontalAlignment) { ForEach(MarkerLabelProperties.AlignHorizontal.allCases) { item in Text(item.displayName).tag(item) } diff --git a/Source/Marker Data/Marker Data/Views/Extensions/DialogIcon.swift b/Source/Marker Data/Marker Data/Views/Extensions/DialogIcon.swift index 6231deea..9e6e4176 100644 --- a/Source/Marker Data/Marker Data/Views/Extensions/DialogIcon.swift +++ b/Source/Marker Data/Marker Data/Views/Extensions/DialogIcon.swift @@ -2,18 +2,71 @@ // DialogIcon.swift // Marker Data // -// Created by Vigneswaran Rajkumar +// Marker Data • https://github.com/TheAcharya/MarkerData +// Licensed under MIT License // -// -// Forces SwiftUI alerts/confirmations to show the PNG app icon (Icon Composer -// assets often leave dialogs with the generic blank document glyph). +// Shared app icon for About, Workflow Extension, and SwiftUI alerts; +// Icon Composer Marker-Data.icon. Do not flatten the layer PNG into AppIconSingle. // +import AppKit import SwiftUI +@MainActor +enum MarkerDataAppIcon { + /// Compiled Icon Composer `Marker-Data.icon` (`ASSETCATALOG_COMPILER_APPICON_NAME` = Marker-Data). + private static var displayIcon: NSImage { + if isWorkflowExtension, let hostIcon = iconFromContainingMarkerDataApp { + return hostIcon + } + + if let named = NSImage(named: "Marker-Data") { + return named + } + + return NSApplication.shared.applicationIconImage + } + + /// Workflow Extension.appex lives in `Marker Data.app/Contents/PlugIns/`. + private static var isWorkflowExtension: Bool { + Bundle.main.bundleURL.pathExtension == "appex" + } + + /// Main app Icon Composer icon. Do not use this appex’s `AppIcon.appiconset` + /// (`applicationIconImage` in the extension is that catalog, or Final Cut Pro). + private static var iconFromContainingMarkerDataApp: NSImage? { + let hostApp = Bundle.main.bundleURL + .deletingLastPathComponent() + .deletingLastPathComponent() + .deletingLastPathComponent() + + guard hostApp.pathExtension == "app" else { return nil } + + if let hostBundle = Bundle(url: hostApp), + let named = hostBundle.image(forResource: "Marker-Data") { + return named + } + + return NSWorkspace.shared.icon(forFile: hostApp.path) + } + + /// Image for SwiftUI `.dialogIcon` (alerts / confirmation dialogs). + static var dialogImage: Image { + Image(nsImage: displayIcon) + } + + /// Unsized icon content. Callers apply layout (About 200×200, Workflow Extension 100×100). + static func image() -> some View { + Image(nsImage: displayIcon) + .resizable() + .scaledToFit() + } +} + extension View { - /// Applies the PNG `AppIconSingle` as the dialog icon for alerts and confirmation dialogs. + /// Applies the Marker Data app icon to alerts and confirmation dialogs. + @MainActor func appDialogIcon() -> some View { - self.dialogIcon(Image("AppIconSingle")) + self.dialogIcon(MarkerDataAppIcon.dialogImage) } } diff --git a/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIcon.appiconset/Icon-1024@1x.png b/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIcon.appiconset/Icon-1024@1x.png index 8090ff0f..43b42176 100644 Binary files a/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIcon.appiconset/Icon-1024@1x.png and b/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIcon.appiconset/Icon-1024@1x.png differ diff --git a/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIcon.appiconset/Icon-128@1x.png b/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIcon.appiconset/Icon-128@1x.png index dde7cdb4..b4477e6d 100644 Binary files a/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIcon.appiconset/Icon-128@1x.png and b/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIcon.appiconset/Icon-128@1x.png differ diff --git a/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIcon.appiconset/Icon-128@2x.png b/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIcon.appiconset/Icon-128@2x.png index beb2b997..bf75d7bb 100644 Binary files a/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIcon.appiconset/Icon-128@2x.png and b/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIcon.appiconset/Icon-128@2x.png differ diff --git a/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIcon.appiconset/Icon-16@1x.png b/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIcon.appiconset/Icon-16@1x.png index 3fc849f2..4b88065e 100644 Binary files a/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIcon.appiconset/Icon-16@1x.png and b/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIcon.appiconset/Icon-16@1x.png differ diff --git a/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIcon.appiconset/Icon-16@2x.png b/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIcon.appiconset/Icon-16@2x.png index 54cbc84c..c7479649 100644 Binary files a/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIcon.appiconset/Icon-16@2x.png and b/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIcon.appiconset/Icon-16@2x.png differ diff --git a/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIcon.appiconset/Icon-256@1x.png b/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIcon.appiconset/Icon-256@1x.png index beb2b997..bf75d7bb 100644 Binary files a/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIcon.appiconset/Icon-256@1x.png and b/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIcon.appiconset/Icon-256@1x.png differ diff --git a/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIcon.appiconset/Icon-256@2x 1.png b/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIcon.appiconset/Icon-256@2x 1.png index 735772a7..6f367345 100644 Binary files a/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIcon.appiconset/Icon-256@2x 1.png and b/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIcon.appiconset/Icon-256@2x 1.png differ diff --git a/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIcon.appiconset/Icon-256@2x.png b/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIcon.appiconset/Icon-256@2x.png index 735772a7..6f367345 100644 Binary files a/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIcon.appiconset/Icon-256@2x.png and b/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIcon.appiconset/Icon-256@2x.png differ diff --git a/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIcon.appiconset/Icon-32@1x.png b/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIcon.appiconset/Icon-32@1x.png index 6c1d1045..c7479649 100644 Binary files a/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIcon.appiconset/Icon-32@1x.png and b/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIcon.appiconset/Icon-32@1x.png differ diff --git a/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIcon.appiconset/Icon-32@2x.png b/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIcon.appiconset/Icon-32@2x.png index 4b0c0790..802b7e66 100644 Binary files a/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIcon.appiconset/Icon-32@2x.png and b/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIcon.appiconset/Icon-32@2x.png differ diff --git a/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIconSingle.imageset/Contents.json b/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIconSingle.imageset/Contents.json deleted file mode 100644 index f9efbad4..00000000 --- a/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIconSingle.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "Icon-1024@1x.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIconSingle.imageset/Icon-1024@1x.png b/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIconSingle.imageset/Icon-1024@1x.png deleted file mode 100644 index a5ff929f..00000000 Binary files a/Source/Marker Data/Workflow Extension/Assets.xcassets/AppIconSingle.imageset/Icon-1024@1x.png and /dev/null differ diff --git a/Source/Marker Data/Workflow Extension/WorkflowExtensionView.swift b/Source/Marker Data/Workflow Extension/WorkflowExtensionView.swift index 846aedd8..591d9b20 100644 --- a/Source/Marker Data/Workflow Extension/WorkflowExtensionView.swift +++ b/Source/Marker Data/Workflow Extension/WorkflowExtensionView.swift @@ -30,9 +30,8 @@ struct WorkflowExtensionView: View { .padding(8) } } - .padding(.bottom) - - Spacer() + // Keep the drop zone above the overlaid help control + .padding(.bottom, 40) } .frame(width: 600, height: 400) .padding() @@ -41,8 +40,7 @@ struct WorkflowExtensionView: View { private var titleHeaderView: some View { HStack { - Image("AppIconSingle") - .resizable() + MarkerDataAppIcon.image() .frame(width: 100, height: 100) .padding(.trailing, 8) diff --git a/assets/marker_data_app_icon.png b/assets/marker_data_app_icon.png index b1593569..27287ab1 100644 Binary files a/assets/marker_data_app_icon.png and b/assets/marker_data_app_icon.png differ