# How to Add New Features to Palmier Pro: A Complete Swift 6.2 Guide

> Discover how to add new features to Palmier Pro using Swift 6.2. Extend the macOS video editor with UI components, ViewModel methods, and persistence.

- Repository: [Palmier/palmier-pro](https://github.com/palmier-io/palmier-pro)
- Tags: how-to-guide
- Published: 2026-06-23

---

**To add new features to Palmier Pro, you extend the macOS video editor by creating UI components using the `AppTheme` design system, exposing functionality through `EditorViewModel` methods, persisting state in the `VideoProject` model, and wiring actions into [`MainMenu.swift`](https://github.com/palmier-io/palmier-pro/blob/main/MainMenu.swift) or [`ToolbarView.swift`](https://github.com/palmier-io/palmier-pro/blob/main/ToolbarView.swift).**

Adding capabilities to **Palmier Pro**—a macOS-only video editor built with Swift 6.2, SwiftUI, AppKit, and AVFoundation—requires following a strict architectural pattern across five distinct layers. Understanding how these layers interact ensures that new features integrate cleanly with the existing timeline engine and autosave system.

## Understanding the Architecture

Palmier Pro separates concerns into five architectural layers. Feature development typically flows from UI down to infrastructure, with the `VideoProject` acting as the single source of truth for all persistent state.

| Layer | Responsibility | Key Files |
|-------|----------------|-----------|
| **UI (SwiftUI / AppKit)** | Visual components, layout, and user interaction | `Sources/PalmierPro/UI/*.swift`, `Sources/PalmierPro/Toolbar/*.swift` |
| **View‑Model** | Glue between UI and domain model, observable state | [`Editor/ViewModel/EditorViewModel.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Editor/ViewModel/EditorViewModel.swift) |
| **Domain Model** | Persistent project data (timeline, clips, media assets) | `Models/*.swift`, [`VideoProject.swift`](https://github.com/palmier-io/palmier-pro/blob/main/VideoProject.swift) (via `AppState`) |
| **Infrastructure** | Services such as MCP, generation, agents, file I/O | `App/*`, [`Editor/OverwriteEngine.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Editor/OverwriteEngine.swift) |
| **Testing** | Unit and UI tests | `Tests/PalmierProTests/**/*.swift` |

All UI changes must flow through `EditorViewModel`, which updates the model and triggers a rebuild of the playback engine.

## Creating the User Interface

### Follow the Design System

All visual constants must come from `AppTheme`. When adding buttons, text, or backgrounds, reference centralized spacing, fonts, and colors defined in [`Sources/PalmierPro/UI/AppTheme.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Sources/PalmierPro/UI/AppTheme.swift).

```swift
// Example: A "Smart Trim" button in the timeline toolbar
Button {
    viewModel.performSmartTrim()
} label: {
    Image(systemName: "scissors")
        .foregroundStyle(AppTheme.Text.primaryColor)
        .frame(width: AppTheme.IconSize.sm, height: AppTheme.IconSize.sm)
}
.buttonStyle(.plain)
.padding(.horizontal, AppTheme.Spacing.smMd)
.background(
    RoundedRectangle(cornerRadius: AppTheme.Radius.sm)
        .fill(AppTheme.Background.baseColor)
)

```

### Handle Drag-and-Drop Correctly

Palmier Pro uses a **single parent `.onDrop`** on the timeline view because nested SwiftUI drop targets get silenced on macOS 26. If your component requires its own drop area inside a drop-enabled parent, implement the target with native AppKit (`NSDraggingDestination`) as demonstrated in [`Sources/PalmierPro/MediaPanel/MediaPanelDropArea.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Sources/PalmierPro/MediaPanel/MediaPanelDropArea.swift).

### Implement the View

Create new SwiftUI views under `Sources/PalmierPro/Features/`. Bind observable state to `EditorViewModel` and import `AppTheme` for styling.

```swift
struct SmartTrimView: View {
    @ObservedObject var vm: EditorViewModel

    var body: some View {
        VStack(spacing: AppTheme.Spacing.sm) {
            Text("Smart Trim")
                .font(.system(size: AppTheme.FontSize.md))
                .foregroundStyle(AppTheme.Text.primaryColor)

            Button("Trim to Scene") { vm.smartTrimCurrentClip() }
                .buttonStyle(CapsuleButton(style: .filled))
        }
        .padding(AppTheme.Spacing.md)
        .background(AppTheme.Background.surface)
    }
}

```

## Connecting Logic with EditorViewModel

### Exposing Public Methods

All UI actions should call methods on `EditorViewModel`. Add a **public function** that performs core logic, updates observable state, and queues a timeline rebuild.

```swift
extension EditorViewModel {
    /// Removes silence from the currently selected clip using the AI service.
    func performSmartTrim() {
        guard let clipId = selectedClipIds.first,
              let clip = clipFor(id: clipId) else { return }

        Task {
            let trimmed = await generationService.trimSilence(in: clip, using: self)
            replaceClip(clipId, with: trimmed)
            notifyTimelineChangedDebounced()
        }
    }
}

```

*Source*: [`EditorViewModel.swift`](https://github.com/palmier-io/palmier-pro/blob/main/EditorViewModel.swift) contains existing patterns like `placeClip` and `createClips` for reference.

### Persisting State Changes

If the feature modifies the project permanently, ensure changes flow through `VideoProject` via `AppState.shared.activeProject`. The project automatically writes state on autosave or app termination.

```swift
activeProject?.editorViewModel.timeline = updatedTimeline
activeProject?.autosave(withImplicitCancellability: false) { _ in }

```

*Source*: See [`Sources/PalmierPro/App/AppState.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Sources/PalmierPro/App/AppState.swift) for the autosave flow implementation.

## Persisting New Data Models

When introducing **new model fields**—such as a per-clip "smart-trim" flag—add them to the relevant struct in `Sources/PalmierPro/Models/`.

```swift
struct Clip: Codable, Identifiable {
    var id: String = UUID().uuidString
    var mediaRef: String
    var startFrame: Int
    var durationFrames: Int
    // NEW: Feature-specific persistence
    var smartTrimApplied: Bool = false
}

```

Since the model uses `Codable`, Swift automatically handles serialization. Update the `MediaResolver` if custom decoding logic is required.

## Integrating with Menus and Toolbar

Wire global actions into [`Sources/PalmierPro/App/MainMenu.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Sources/PalmierPro/App/MainMenu.swift). Access the active project's view-model through `AppState.shared.activeProject`.

```swift
Menu("Edit") {
    Button("Smart Trim Selected Clip") {
        if let editor = AppState.shared.activeProject?.editorViewModel {
            editor.performSmartTrim()
        }
    }
    .keyboardShortcut("T", modifiers: [.command, .option])
}

```

For toolbar-specific commands, modify [`Sources/PalmierPro/Toolbar/ToolbarView.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Sources/PalmierPro/Toolbar/ToolbarView.swift) using the same button patterns as existing controls.

## Testing Your Implementation

Add unit tests under `Tests/PalmierProTests/` that exercise the new view-model method. Test files should verify both happy paths and edge cases.

```swift
func testSmartTrimAppliesToSelectedClip() async {
    let vm = EditorViewModel()
    let clipId = vm.placeClip(asset: mockAudioAsset,
                               trackIndex: 0,
                               startFrame: 0,
                               durationFrames: 200)[0]
    vm.selectedClipIds = [clipId]

    vm.generationService = MockGenerationService { _ in
        var trimmed = Clip(...)
        trimmed.durationFrames = 150
        return trimmed
    }

    await vm.performSmartTrim()
    XCTAssertEqual(vm.timeline.tracks[0].clips.first?.durationFrames, 150)
}

```

Run the full suite with `swift test` to ensure no regressions in the timeline engine or media pipeline.

## Build and Verification

Compile and launch the application to verify integration:

```bash
swift build            # Compile the project

swift run PalmierPro   # Launch the app (requires macOS 26)

```

After adding UI code, rebuilds automatically pick up new views. Use Xcode for iterative UI development if preferred over command-line builds.

## Summary

- **Identify the architectural layer** before coding—UI components belong in `Sources/PalmierPro/UI/`, while business logic belongs in [`EditorViewModel.swift`](https://github.com/palmier-io/palmier-pro/blob/main/EditorViewModel.swift).
- **Use `AppTheme`** for all visual constants to maintain design consistency across the macOS interface.
- **Expose functionality** through public methods on `EditorViewModel`, which coordinates between SwiftUI and the domain model.
- **Persist state** by modifying `VideoProject` and calling `autosave()` via `AppState.shared.activeProject`.
- **Wire menus** in [`MainMenu.swift`](https://github.com/palmier-io/palmier-pro/blob/main/MainMenu.swift) and toolbar buttons in [`ToolbarView.swift`](https://github.com/palmier-io/palmier-pro/blob/main/ToolbarView.swift) using the existing action patterns.
- **Test thoroughly** under `Tests/PalmierProTests/` and run `swift test` before committing changes.

## Frequently Asked Questions

### How do I add a new button to the Palmier Pro toolbar?

Add your button to [`Sources/PalmierPro/Toolbar/ToolbarView.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Sources/PalmierPro/Toolbar/ToolbarView.swift) using the `AppTheme` design system for sizing and colors. Ensure the button calls a method on `EditorViewModel` rather than manipulating state directly, and verify the action is accessible via `AppState.shared.activeProject?.editorViewModel`.

### Where should I store new project data that needs to survive app restarts?

Add new fields to the relevant model struct in `Sources/PalmierPro/Models/` (such as [`Clip.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Clip.swift)), ensuring it conforms to `Codable`. The `VideoProject` class automatically persists these changes when you call `autosave()` through `AppState.shared.activeProject`.

### Why does my custom drop target not receive events?

Palmier Pro uses a single parent `.onDrop` on the timeline because nested SwiftUI drop targets get silenced on macOS 26. If you need drop functionality inside an existing drop area, implement `NSDraggingDestination` using AppKit directly, following the pattern in [`Sources/PalmierPro/MediaPanel/MediaPanelDropArea.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Sources/PalmierPro/MediaPanel/MediaPanelDropArea.swift).

### How do I test a new feature that uses the AI generation service?

Create a `MockGenerationService` that conforms to the generation protocol and inject it into your `EditorViewModel` instance during testing. Place unit tests under `Tests/PalmierProTests/` and verify both the view-model state changes and any resulting timeline modifications using `XCTAssert` assertions.