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 inEditorViewModel.swift. - Use
AppThemefor 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
VideoProjectand callingautosave()viaAppState.shared.activeProject. - Wire menus in
MainMenu.swiftand toolbar buttons inToolbarView.swiftusing the existing action patterns. - Test thoroughly under
Tests/PalmierProTests/and runswift testbefore 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →