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

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 or 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
Domain Model Persistent project data (timeline, clips, media assets) Models/*.swift, VideoProject.swift (via AppState)
Infrastructure Services such as MCP, generation, agents, file I/O App/*, 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.

// 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.

Implement the View

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

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.

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 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.

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

Source: See 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/.

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. Access the active project's view-model through AppState.shared.activeProject.

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 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.

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:

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.
  • 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 and toolbar buttons in 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 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), 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.

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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →