diff --git a/.cursorrules b/.cursorrules index 4a34852..66f7452 100644 --- a/.cursorrules +++ b/.cursorrules @@ -11,7 +11,7 @@ Keep these four files consistent when architecture or agent guidance changes. ## Project layout - Main app: `Source/Marker Data/Marker Data/` -- Workflow Extension: `Source/Marker Data/Workflow Extension/` (`WorkflowExtensionView.swift` — Extract + Roles tabs) +- Workflow Extension: `Source/Marker Data/Workflow Extension/` (`WorkflowExtensionView.swift` — Extract + Roles tabs; two Compile Sources rows including unused main-app membership — do not delete). `WorkflowExtensionViewController.swift` is appex-only. - 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` @@ -46,6 +46,8 @@ Keep these four files consistent when architecture or agent guidance changes. - **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 (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). +- **FCPXML UTTypes:** public `UTType.fcpxml` / `.fcpxmld` (`UTTypeExtension`; private `finalCutProType` = `UTType(identifier) ?? UTType(importedAs:identifier, conformingTo:)`). First Extract layout evaluates these types — never `!`. Keep `UTImportedTypeDeclarations` in `Marker-Data-Info.plist`. Do not split the Share Destination **Asset Description File** document type. +- **File menu:** `FileCommands` replaces `.newItem` (Open Pagemaker ⌘P, Install Share Destination, Show/Clean Cache ⌘K). Do not empty `.newItem` in `Marker_DataApp` and do not add a second Close. Extract **Choose File** stays on `ExtractView` (`FilePicker`, ⌘O). ## Pitfalls to avoid - Do not remove or rename persisted settings keys without a migration. @@ -61,10 +63,14 @@ Keep these four files consistent when architecture or agent guidance changes. - 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. +- Do not force-unwrap `UTType("com.apple.finalcutpro.xml")` / `.xmld` — resolve with `UTType(identifier) ?? UTType(importedAs:identifier, conformingTo:)` and keep `UTImportedTypeDeclarations` in `Marker-Data-Info.plist`. First Extract layout evaluates these types; FCP may be absent (App Preview). Do not split the Share Destination **Asset Description File** document type. +- File menu custom items replace `.newItem` so system Close stays last — do not empty `.newItem` and do not add a second Close. ## Definition of done for code changes - 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`). +- FCPXML `UTType`s still use `importedAs` fallback (no `!`); Info.plist still has `UTImportedTypeDeclarations`; Share Destination **Asset Description File** type is unchanged. +- File menu still replaces `.newItem` with app actions; system Close stays last (no second Close; do not empty `.newItem`). - 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 **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. diff --git a/AGENT.md b/AGENT.md index 9a00b68..76d762d 100644 --- a/AGENT.md +++ b/AGENT.md @@ -19,6 +19,7 @@ - [New export fields / overlays](#new-export-fields--overlays) - [New database platform](#new-database-platform) - [FCP integrations](#fcp-integrations) + - [File menu](#file-menu) - [Drop overlay / shared Workflow Extension UI](#drop-overlay--shared-workflow-extension-ui) - [App icon / dialogs](#app-icon--dialogs) - [Uninstaller cleanup paths](#uninstaller-cleanup-paths) @@ -58,7 +59,7 @@ For deeper module/data-flow detail, see **`ARCHITECTURE.md`**. For hard always/n | Concern | File | |---------|------| -| `@main` app | `Source/Marker Data/Marker Data/Marker_DataApp.swift` — constructs `SettingsContainer`, `DatabaseManager`, `ExtractionModel`, `QueueModel`; menu commands; Failed Tasks + Pagemaker windows | +| `@main` app | `Source/Marker Data/Marker Data/Marker_DataApp.swift` — constructs `SettingsContainer`, `DatabaseManager`, `ExtractionModel`, `QueueModel`; menu commands (`FileCommands` replaces `.newItem`); Failed Tasks + Pagemaker windows | | AppKit / Sparkle delegate | `Source/Marker Data/Marker Data/ApplicationDelegate.swift` | | Sidebar navigation | `Source/Marker Data/Marker Data/Views/Main/ContentView.swift` (`MainViews` enum) | | Extract UI | `Source/Marker Data/Marker Data/Views/Main/ExtractView.swift` | @@ -66,8 +67,11 @@ For deeper module/data-flow detail, see **`ARCHITECTURE.md`**. For hard always/n | External handoffs (open / Workflow Extension) | `.../ExtractionModel_EventHandlers.swift` | | FCPXML intake (files / pasteboard / textClipping) | `Utilities/Other/FCPXMLIntake.swift`, `TextClippingReader.swift` | | Extract drop modifier | `Views/Components/FCPXMLDropModifier.swift` (`.fcpxmlDropDestination`) | +| FCPXML UTTypes | `Utilities/Extensions/UTTypeExtension.swift` — public `UTType.fcpxml` / `.fcpxmld` via private `finalCutProType` (`UTType(identifier) ?? UTType(importedAs:identifier, conformingTo:)` — never `!`); `Source/Marker Data/Marker-Data-Info.plist` `UTImportedTypeDeclarations` + Asset Description File `LSItemContentTypes` | +| File menu | `Views/Menu Bar Commands/FileCommands.swift` — replaces `.newItem` (Open Pagemaker ⌘P, Install Share Destination, Show/Clean Cache ⌘K); system Close stays last | +| Info.plist | `Source/Marker Data/Marker-Data-Info.plist` — Sparkle, Share Destination document types (**Asset Description File** name is a contract), imported FCPXML UTIs | | Drop overlay (Extract / Roles / Queue + WE) | `Views/Components/DropTargetOverlay.swift` (also Workflow Extension Compile Sources) | -| Workflow Extension UI | `Source/Marker Data/Workflow Extension/WorkflowExtensionView.swift` | +| Workflow Extension UI | `Source/Marker Data/Workflow Extension/WorkflowExtensionView.swift` — Extract + Roles tabs; **two** Compile Sources rows (main app + appex — do not delete the main-app membership). `WorkflowExtensionViewController.swift` is appex-only. | | Color helpers (`markerAccent`, `heroGradient`) | `Utilities/Extensions/ColorExtension.swift` | | Queue UI | `Views/Detail Views/QueueView.swift` | | Queue scan/upload | `Models/Queue/QueueModel.swift` | @@ -225,14 +229,21 @@ Also: getter for `colorSwatchSettings` **forces `enableSwatch = false` when extr - Note: existing property spelling is `plaform` on `DatabaseProfileModel` — keep consistent when extending. ### FCP integrations -- Share Destination: `Resources/OSAScriptingDefinition.sdef` + Obj‑C under `FCP Share Destination/Objective-C Code/` + Swift `OpenEventHandler`. -- Workflow Extension: DistributedNotificationCenter names + fixed Movies-cache FCPXML path; roles via `preferences.json`. +- Share Destination: `Resources/OSAScriptingDefinition.sdef` + Obj‑C under `FCP Share Destination/Objective-C Code/` + Swift `OpenEventHandler`. Keep the **Asset Description File** document type name (`DocumentController` matches it). +- Workflow Extension: DistributedNotificationCenter names + fixed Movies-cache FCPXML path; roles via `preferences.json`. Appex `Info.plist` `ProExtensionAttributes` are 700×550 (FCP host min); SwiftUI frame is 600×400. `WorkflowExtensionView.swift` has two Compile Sources rows. - Extract panel intake: `FCPXMLIntake` / `FCPXMLDropModifier` — keep Dock Open With (`OpenEventHandler`) and Share Destination paths distinct. +- FCPXML UTTypes: public `UTType.fcpxml` / `.fcpxmld` (private `finalCutProType`, never `!`) + `Marker-Data-Info.plist` `UTImportedTypeDeclarations` / Asset Description File `LSItemContentTypes`. + +### File menu +- Custom File items live in `FileCommands` as `CommandGroup(replacing: .newItem)`. +- Do **not** also empty `.newItem` in `Marker_DataApp` (that puts system Close first). Emptying `.toolbar` in `Marker_DataApp` is unrelated (removes the default View-menu toolbar group). +- Do **not** add a Close item — system Close / Close All remain last. +- Current items (in order): **Open Pagemaker** (⌘P) → **Install FCP Share Destination…** → **Show Cache** / **Clean Cache** (⌘K). Extract **Choose File** still uses ⌘O on the Extract panel (`FilePicker`), not the File menu. Extract completion also shows **Open Pagemaker** when the profile is extract-only Notion or Airtable. ### 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. -- 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`. +- Shared types must stay in **both** targets’ Compile Sources (two `PBXBuildFile` rows, one `PBXFileReference`). Minimum shared UI: `DropTargetOverlay`, `ColorExtension`, `DialogIcon.swift`, `HelpButton`, `OverlayHelpButton`. `WorkflowExtensionView.swift` is also dual-membership (appex UI; do not delete the main-app Compile Sources row). `WorkflowExtensionViewController.swift` is appex-only. 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). @@ -304,11 +315,15 @@ Exact overlay strings and always/never rules: **`GUARDRAILS.md`**. - **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/`. +- **FCPXML `UTType` force-unwrap:** `UTType("com.apple.finalcutpro.xml")!` traps on first Extract layout when Final Cut Pro is not installed (App Preview / machines without FCP). Resolve with `UTType(identifier) ?? UTType(importedAs:identifier, conformingTo:)` and keep `UTImportedTypeDeclarations` in `Marker-Data-Info.plist`. Do not split the Share Destination **Asset Description File** document type. +- **File menu Close last:** custom File items replace `.newItem` (`FileCommands`). Do not empty `.newItem` (that puts system Close first) and do not add a second Close. ## Definition of done for most changes - 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`. +- FCPXML `UTType`s still use `importedAs` fallback (no `!`); Info.plist still has `UTImportedTypeDeclarations`; Share Destination **Asset Description File** type is unchanged. +- File menu still replaces `.newItem` with app actions; system Close stays last (no second Close; do not empty `.newItem`). - 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”**). diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 15aa54c..1b6b48c 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -30,6 +30,8 @@ - [Color swatch pipeline](#color-swatch-pipeline) - [Database profiles](#database-profiles) - [UI architecture](#ui-architecture) +- [File menu](#file-menu) +- [FCPXML UTTypes without Final Cut Pro](#fcpxml-uttypes-without-final-cut-pro) - [App icon and dialogs](#app-icon-and-dialogs) - [Notifications (full list)](#notifications-full-list) - [Entitlements / security model](#entitlements--security-model) @@ -53,7 +55,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; 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`**. +**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 for extraction workflows, but the **app must launch without FCP installed** (App Preview / review Macs) — FCPXML `UTType`s use `importedAs` fallback. 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`**. @@ -72,7 +74,8 @@ Primary responsibilities: - Persistence of configurations, DB profiles, logs - Queue scanning/uploading previously extracted Notion/Airtable jobs (local-first `manifestURL`) - Installing FCP Share Destination templates -- Hosting Pagemaker (WebView) for PDF generation +- Hosting Pagemaker (WebView) for PDF generation (File menu **Open Pagemaker**, plus Extract footer after Notion/Airtable extract-only) +- FCPXML UTTypes that resolve when Final Cut Pro is absent (`UTTypeExtension` + Info.plist imported types) - Embedding the Workflow Extension as `Contents/PlugIns/Workflow Extension.appex` ### Workflow Extension target @@ -85,6 +88,7 @@ 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 +- SwiftUI frame is **600×400**; FCP host minimum comes from the **appex** `Info.plist` `ProExtensionAttributes` (**700×550**). Do not edit `ProExtensionAttributes` on `Marker-Data-Info.plist` (main app; leftover 400×600) expecting to size the extension. - 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 “?” @@ -94,7 +98,8 @@ Responsibilities: | Kind | Files | |------|--------| -| Extension-only | `WorkflowExtensionView.swift`, `WorkflowExtensionViewController.swift` | +| Extension-only | `WorkflowExtensionViewController.swift` | +| Dual membership (appex UI; also in main-app Compile Sources — do not delete either row) | `WorkflowExtensionView.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` | @@ -317,18 +322,19 @@ Changing the JSON shape of `roles` / `RoleModel` (or any `SettingsStore` key) mu **UI:** `DropTargetOverlay` while targeted and not extracting; hero caption under title describes FCP timeline / file drop. Overlay copy: see **`GUARDRAILS.md`**. -Supported types: `UTType.fcpxml`, `UTType.fcpxmld` (`ExtractionModel.supportedContentTypes`). +Supported types: `UTType.fcpxml`, `UTType.fcpxmld` (`ExtractionModel.supportedContentTypes`) — defined in `UTTypeExtension` so first Extract layout does not trap if Final Cut Pro is not installed. ```mermaid flowchart LR Drop[Drop / Open / Pasteboard] + UT["UTType.fcpxml / .fcpxmld"] Intake[FCPXMLIntake] EM[ExtractionModel] ME[MarkersExtractor] Swatch["ColorPaletteRenderer.render -> Bool"] Up[DatabaseUploader] - Drop --> Intake --> EM --> ME + Drop --> UT --> Intake --> EM --> ME ME --> Swatch ME --> Up Swatch -->|true: markAllProcessesFinished| Up @@ -437,7 +443,7 @@ Roles tab: same `RolesSettingsView` + `DropTargetOverlay` as the main app (full - 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. +Info.plist (`Source/Marker Data/Marker-Data-Info.plist`) advertises Media Asset Protocol, Sparkle, and document types for asset media/description collections. Keep **Asset Description File** as the FCPXML/`fcpxmld` `CFBundleTypeName` (`DocumentController` matches that string). Also declares `UTImportedTypeDeclarations` for `com.apple.finalcutpro.xml` / `.xmld` so Extract layout does not trap when FCP is missing. **Note:** FCP **timeline** drops onto the Dock icon are often pasteboard-only (no file URL). Prefer Extract panel drop, file Open With, Workflow Extension, or Share Destination for media+XML. @@ -491,7 +497,7 @@ Notable modules under `Views/`: |------|--------| | Main | `ContentView`, `ExtractView` (drop overlay + `.fcpxmlDropDestination`) | | Detail | General (File/Roles/Notifications/Updates), Image, Label, Configurations, Databases, Queue (folder drop overlay), About | -| Menu commands | App / File / Edit / Sidebar / Configuration / Help | +| Menu commands | App / File (`FileCommands` replaces `.newItem` so system Close stays last) / Edit / SwiftUI `SidebarCommands` / Configuration / Help | | Onboarding | `@AppStorage("showOnboarding")` sheet | | 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`. | @@ -508,6 +514,7 @@ flowchart TB EX[ExtractView] RO[RolesSettingsView] QU[QueueView] + FILE[FileCommands replacing newItem] end subgraph weUI [Workflow Extension] @@ -523,7 +530,8 @@ flowchart TB WRO --> OV EX --> FM[FCPXMLDropModifier] - FM --> IN[FCPXMLIntake] + FM --> UT[UTType.fcpxml / .fcpxmld] + UT --> IN[FCPXMLIntake] IN --> EM[ExtractionModel] RO --> RMD[RolesManager DropDelegate] @@ -543,6 +551,8 @@ flowchart TB subgraph disk [One file on disk] DI[DialogIcon.swift] DTO[DropTargetOverlay.swift] + WEV[WorkflowExtensionView.swift] + UT[UTTypeExtension.swift] RM[RolesManager.swift] SS[SettingsStore.swift] end @@ -559,6 +569,10 @@ flowchart TB DI --> WSRC DTO --> MSRC DTO --> WSRC + WEV --> MSRC + WEV --> WSRC + UT --> MSRC + UT --> WSRC RM --> MSRC RM --> WSRC SS --> MSRC @@ -582,6 +596,59 @@ flowchart LR --- +## File menu + +`Marker_DataApp` registers `FileCommands()` and does **not** empty `.newItem`. `FileCommands` **replaces** `.newItem` so custom items sit at the top of File and system **Close / Close All** stay last. Do not add a Close button. (`Marker_DataApp` does empty `.toolbar` — that only removes the default View-menu toolbar group, not File → Close.) + +| Order | Item | Notes | +|-------|------|--------| +| 1 | Open Pagemaker | `openWindow(id: "pagemaker")`, ⌘P | +| 2 | Install FCP Share Destination… | `ShareDestinationInstaller.install()` | +| 3 | Show Cache / Clean Cache | Movies cache folder; Clean is ⌘K | +| last | Close / Close All | System — do not duplicate | + +Extract **Choose File** remains on `ExtractView` (`FilePicker`, ⌘O), not the File menu. SwiftUI `SidebarCommands` is the system sidebar group (no custom `SidebarCommands.swift`). A second **Open Pagemaker** control appears on the Extract completion footer when the profile is extract-only Notion or Airtable (`showPagemakerOpenButton`). + +```mermaid +flowchart TB + FileMenu[File menu] + Custom["FileCommands replacing .newItem"] + SysClose[System Close / Close All] + FileMenu --> Custom + FileMenu --> SysClose + Custom --> PM[Open Pagemaker] + Custom --> SD[Install FCP Share Destination] + Custom --> Cache[Show Cache / Clean Cache] +``` + +--- + +## FCPXML UTTypes without Final Cut Pro + +`com.apple.finalcutpro.xml` / `.xmld` are not in the UTI database unless Final Cut Pro is installed. `UTType("…")!` traps on first Extract layout (`FilePicker` / drop evaluate `.fcpxml` / `.fcpxmld`). + +**Public API:** `UTType.fcpxml` and `UTType.fcpxmld` (`UTTypeExtension.swift`). The helper `finalCutProType` is **private**: `UTType(identifier) ?? UTType(importedAs:identifier, conformingTo:)` with `.xml` for `.fcpxml` and `.package` for `.fcpxmld`. **Never** force-unwrap. Callers use `UTType.fcpxml` / `.fcpxmld` (e.g. `ExtractionModel.supportedContentTypes`, `FilePicker`, `FCPXMLDropModifier`, Workflow Extension `.onDrop`). + +**Declare** in `Source/Marker Data/Marker-Data-Info.plist`: `UTImportedTypeDeclarations` for both identifiers, plus `LSItemContentTypes` on the existing **Asset Description File** document type. Do **not** split or rename that type (Share Destination `DocumentController` matches `"Asset Description File"`). Do not add versioned pasteboard UTIs (`…xml.v1`–`v14`) or iPad `.fcpproj`. + +The Workflow Extension also compiles `UTTypeExtension.swift`; FCP is present in that host, but the same helper must stay `importedAs`-safe. + +```mermaid +flowchart LR + UI["Extract FilePicker / drop / WE"] + UT["UTType.fcpxml / .fcpxmld"] + Lookup["UTType identifier"] + Import["UTType importedAs conformingTo"] + Plist["Marker-Data-Info.plist UTImportedTypeDeclarations"] + + UI --> UT --> Lookup + Lookup -->|FCP installed| OK[Resolved] + Lookup -->|FCP absent nil| Import --> OK + Plist --> Lookup +``` + +--- + ## 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`. @@ -664,7 +731,22 @@ Changing automation, sandbox, or extension behavior usually requires entitlement | Binary refresh workflows | `update_airlift_binary.yml`, `update_csv2notion_neo_binary.yml`, `update_pagemaker.yml` | ### Notable SPM dependencies (main app) -MarkersExtractor, DominantColors, DockProgress, Sparkle, FilePicker, ColorWellKit, PasswordField, ButtonKit, WebViewKit, swift-collections, swift-log-oslog (and related logging). + +Minimum versions from `project.pbxproj` (`XCRemoteSwiftPackageReference`): + +| Package | Minimum | +|---------|---------| +| MarkersExtractor | **0.4.8** | +| DominantColors | 1.2.2 | +| DockProgress | 5.1.0 | +| Sparkle | 2.9.0 | +| FilePicker | 1.0.1 | +| ColorWellKit | 1.1.2 | +| PasswordField | 1.0.0 | +| ButtonKit | 0.7.1 | +| WebViewKit | 1.0.0 | +| swift-collections | 1.4.0 | +| swift-log-oslog | 0.2.2 | --- @@ -705,7 +787,7 @@ Source/Marker Data/Marker Data/ Views/ Main/ # ContentView, ExtractView Detail Views/ # General, Image, Label, Configurations, Databases, QueueView, About - Menu Bar Commands/ + Menu Bar Commands/ # FileCommands (replaces .newItem), AppCommands, EditCommands, ConfigurationCommands, HelpCommands Components/ # DropTargetOverlay, FCPXMLDropModifier, HelpButton, OverlayHelpButton, … Extensions/ # DialogIcon (@MainActor MarkerDataAppIcon + .appDialogIcon), ApplyPickerSizing Onboarding/, Other/ # FailedExtractionsView, ExportProfilePicker, … @@ -713,7 +795,7 @@ Source/Marker Data/Marker Data/ Install View/, Objective-C Code/, OpenEventHandler.swift Pagemaker/ # PagemakerView, PDF export handler, UIDelegate, WebViewStateManager Utilities/ - Extensions/ # URL, Color (markerAccent, heroGradient), UTType, NotificationName, … + Extensions/ # URL, Color (markerAccent, heroGradient), UTType (`UTType.fcpxml` / `.fcpxmld`, never `!`), NotificationName, … Shell/, Notifications/, Other/ # FCPXMLIntake, TextClippingReader, LibraryFolders, FileWatcher, … Resources/ @@ -722,10 +804,15 @@ Source/Marker Data/Marker Data/ Marker-Data.icon # Icon Composer (ASSETCATALOG_COMPILER_APPICON_NAME = Marker-Data) Assets.xcassets/ # empty AppIcon.appiconset placeholder; no AppIconSingle +Source/Marker Data/Marker-Data-Info.plist + # Sparkle, Media Asset Protocol, Share Destination document types (keep Asset Description File), + # UTImportedTypeDeclarations for com.apple.finalcutpro.xml / .xmld + Source/Marker Data/Workflow Extension/ - WorkflowExtensionView.swift # Extract overlay + handoff; MarkerDataAppIcon header 100×100; hosts RolesSettingsView; TabView .padding(.bottom, 40) - WorkflowExtensionViewController.swift - Assets.xcassets/, Info.plist, entitlements, bridging header + WorkflowExtensionView.swift # Extract overlay + handoff; MarkerDataAppIcon header 100×100; hosts RolesSettingsView; TabView .padding(.bottom, 40); SwiftUI frame 600×400; **two** Compile Sources rows (main app + appex) + WorkflowExtensionViewController.swift # Appex-only + Info.plist # NSExtension WorkflowExtension; ProExtensionAttributes 700×550 + Assets.xcassets/, 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) @@ -753,4 +840,6 @@ Source/Marker Data/Marker Data Uninstaller/ 13. FCPXML pasteboard/clipping temps live under `~/Movies/Marker Data Cache/` (not App Support). 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. +16. FCPXML UTTypes must resolve without Final Cut Pro installed: public `UTType.fcpxml` / `.fcpxmld` (private `finalCutProType`: `UTType(identifier)` then `importedAs:conformingTo:`) plus `UTImportedTypeDeclarations` in `Marker-Data-Info.plist`. Never `UTType("com.apple.finalcutpro.xml")!`. Keep the Share Destination **Asset Description File** document type (do not rename/split it). +17. File menu custom items replace `.newItem` (`FileCommands`). Do not empty `.newItem` in `Marker_DataApp` (puts system Close first) and do not add a second Close. +18. Definition of done: unsigned arm64 Debug+Release build; settings migrate; `.fcpxml`/`.fcpxmld` + pasteboard intake; FCPXML UTTypes use `importedAs` fallback; File menu Close last; 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 a2fb48f..94edb7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +### 2.1.1 (25) + +**🎉 Released:** +- 9th September 2026 + +**🐞 Bug Fix:** +- Launch no longer crashes on Macs that do not have Final Cut Pro installed. +- File menu now lists app actions first, with Close last. + +--- + ### 2.1.0 (24) **🎉 Released:** diff --git a/GUARDRAILS.md b/GUARDRAILS.md index b2cc450..1697786 100644 --- a/GUARDRAILS.md +++ b/GUARDRAILS.md @@ -31,6 +31,8 @@ Read with **`AGENT.md`** (how to change things), **`ARCHITECTURE.md`** (how it w | **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`). | +| **FCPXML UTTypes** | Resolve `.fcpxml` / `.fcpxmld` with `UTType(identifier) ?? UTType(importedAs:identifier, conformingTo:)` (`.xml` / `.package`). Keep `UTImportedTypeDeclarations` and Asset Description File `LSItemContentTypes` in `Marker-Data-Info.plist`. | +| **File menu** | Custom File items replace `.newItem` (`FileCommands`) so system Close / Close All stay last. | --- @@ -51,6 +53,8 @@ Read with **`AGENT.md`** (how to change things), **`ARCHITECTURE.md`** (how it w | **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). | +| **FCPXML `UTType` unwrap** | Never force-unwrap `com.apple.finalcutpro.xml` / `.xmld`. Never split or rename the Share Destination **Asset Description File** document type. | +| **File menu Close** | Never empty `.newItem` in `Marker_DataApp` (that puts system Close first). Never add a second Close item. | --- @@ -82,8 +86,10 @@ Shared component: `Views/Components/DropTargetOverlay.swift` (main app + Workflo 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. +14. **Shared Compile Sources look duplicated in `pbxproj`.** One `DialogIcon.swift` (and other shared files, including `WorkflowExtensionView.swift`) correctly appears twice as `PBXBuildFile` — main app + Workflow Extension. Deleting one membership breaks the extension (or, for `WorkflowExtensionView`, still violates the two-row contract — leave the unused main-app row). `WorkflowExtensionViewController.swift` is the only appex-only Swift file. 15. **Workflow Extension does not migrate settings.** `RolesManager` JSON-decodes `SettingsStore` with no `SettingsVersioningManager`. Open the main app after a schema bump. +16. **FCPXML `UTType` force-unwrap** — `UTType("com.apple.finalcutpro.xml")!` traps on Extract first layout when Final Cut Pro is absent (App Preview / review Macs). Use `importedAs` fallback and Info.plist imported types. Do not copy the old Marker Data force-unwrap. +17. **File menu Close last:** custom File items replace `.newItem`. Emptying `.newItem` makes system Close the first File item; do not duplicate Close. --- @@ -92,6 +98,8 @@ Shared component: `Views/Components/DropTargetOverlay.swift` (main app + Workflo - [ ] 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`. +- [ ] FCPXML `UTType`s still use `importedAs` fallback (no `!` on `com.apple.finalcutpro.xml` / `.xmld`); Info.plist still has `UTImportedTypeDeclarations`; Share Destination **Asset Description File** type is unchanged. +- [ ] File menu still replaces `.newItem` with app actions; system Close stays last (no second Close; do not empty `.newItem`). - [ ] 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. diff --git a/Source/Marker Data/Marker Data/Marker_DataApp.swift b/Source/Marker Data/Marker Data/Marker_DataApp.swift index 87f0a41..8f05d77 100644 --- a/Source/Marker Data/Marker Data/Marker_DataApp.swift +++ b/Source/Marker Data/Marker Data/Marker_DataApp.swift @@ -110,9 +110,6 @@ struct Marker_DataApp: App { .windowResizability(.contentSize) // Customise Menu Bar Commands .commands { - // Removes New Window Menu Item - CommandGroup(replacing: .newItem) {} - // Removes Toolbar Menu Items CommandGroup(replacing: .toolbar) {} diff --git a/Source/Marker Data/Marker Data/Utilities/Extensions/UTTypeExtension.swift b/Source/Marker Data/Marker Data/Utilities/Extensions/UTTypeExtension.swift index 5a86cd7..7593704 100644 --- a/Source/Marker Data/Marker Data/Utilities/Extensions/UTTypeExtension.swift +++ b/Source/Marker Data/Marker Data/Utilities/Extensions/UTTypeExtension.swift @@ -4,11 +4,27 @@ // // Created by Milán Várady on 07/10/2023. // +// +// FCPXML/FCPXMLD UTTypes that resolve without Final Cut Pro installed. +// import Foundation import UniformTypeIdentifiers extension UTType { - public static let fcpxml = UTType("com.apple.finalcutpro.xml")! - public static let fcpxmld = UTType("com.apple.finalcutpro.xmld")! + /// Final Cut Pro XML document (`.fcpxml`). + public static let fcpxml = Self.finalCutProType( + identifier: "com.apple.finalcutpro.xml", + conformingTo: .xml + ) + + /// Final Cut Pro XML bundle (`.fcpxmld`). + public static let fcpxmld = Self.finalCutProType( + identifier: "com.apple.finalcutpro.xmld", + conformingTo: .package + ) + + private static func finalCutProType(identifier: String, conformingTo supertype: UTType) -> UTType { + UTType(identifier) ?? UTType(importedAs: identifier, conformingTo: supertype) + } } diff --git a/Source/Marker Data/Marker Data/Views/Detail Views/Database/DatabaseSettingsView.swift b/Source/Marker Data/Marker Data/Views/Detail Views/Database/DatabaseSettingsView.swift index cc1fa8f..376e4f4 100644 --- a/Source/Marker Data/Marker Data/Views/Detail Views/Database/DatabaseSettingsView.swift +++ b/Source/Marker Data/Marker Data/Views/Detail Views/Database/DatabaseSettingsView.swift @@ -61,6 +61,7 @@ struct DatabaseSettingsView: View { Button("Cancel", role: .cancel) {} } + .appDialogIcon() } var tableView: some View { diff --git a/Source/Marker Data/Marker Data/Views/Menu Bar Commands/FileCommands.swift b/Source/Marker Data/Marker Data/Views/Menu Bar Commands/FileCommands.swift index a0af30d..946f89c 100644 --- a/Source/Marker Data/Marker Data/Views/Menu Bar Commands/FileCommands.swift +++ b/Source/Marker Data/Marker Data/Views/Menu Bar Commands/FileCommands.swift @@ -4,6 +4,10 @@ // // Created by Milán Várady on 20/01/2024. // +// +// File menu items replace the New group so system Close / Close All stay last. +// Do not duplicate Close here. +// import Foundation import SwiftUI @@ -12,7 +16,7 @@ struct FileCommands: Commands { @Environment(\.openWindow) var openWindow var body: some Commands { - CommandGroup(after: .importExport) { + CommandGroup(replacing: .newItem) { Button("Open Pagemaker") { openWindow(id: "pagemaker") } @@ -29,13 +33,13 @@ struct FileCommands: Commands { } } } - + Divider() - + Button("Show Cache") { NSWorkspace.shared.open(URL.FCPExportCacheFolder) } - + Button("Clean Cache") { LibraryFolders.deleteCache() } diff --git a/Source/Marker Data/Marker-Data-Info.plist b/Source/Marker Data/Marker-Data-Info.plist index 7094495..2b35ec0 100644 --- a/Source/Marker Data/Marker-Data-Info.plist +++ b/Source/Marker Data/Marker-Data-Info.plist @@ -71,10 +71,52 @@ Viewer LSHandlerRank Default + LSItemContentTypes + + com.apple.finalcutpro.xml + com.apple.finalcutpro.xmld + NSDocumentClass + UTImportedTypeDeclarations + + + UTTypeConformsTo + + public.xml + + UTTypeDescription + Final Cut Pro XML + UTTypeIdentifier + com.apple.finalcutpro.xml + UTTypeTagSpecification + + public.filename-extension + + fcpxml + + + + + UTTypeConformsTo + + com.apple.package + + UTTypeDescription + Final Cut Pro XML Bundle + UTTypeIdentifier + com.apple.finalcutpro.xmld + UTTypeTagSpecification + + public.filename-extension + + fcpxmld + + + + NSAppleScriptEnabled NSServices