EditorViewModel in Palmier Pro: The Central Hub for Managing Editor State

The EditorViewModel is the central, observable data hub that keeps the entire editor UI in sync with the underlying project model, acting as the single source of truth for both persisted timeline data and transient interface state.

The EditorViewModel class in the Palmier Pro video editor serves as the backbone of the application's state management layer. Built with SwiftUI's @Observable macro and running on the @MainActor, this view model ensures instantaneous UI updates while coordinating between the timeline, media library, and generation services. Understanding how EditorViewModel manages editor state is essential for developers extending Palmier Pro's functionality or integrating with its project model.

Core Responsibilities of EditorViewModel

Persisted Project Data

At the heart of EditorViewModel.swift (lines 27-32), the view model stores the three critical components of a VideoProject: the timeline, media manifest, and generation log. These properties ensure that every edit, media reference, and AI generation history persists across app sessions:

var timeline = Timeline()
var mediaManifest = MediaManifest()
var generationLog = GenerationLog()

Panel Focus and Layout State

The view model tracks interface layout through focusedPanel, maximizedPanel, and layoutPreset (lines 35-48, 99-107). These values synchronize with UserDefaults to restore the editor's exact configuration between launches, maintaining user preferences for panel visibility and workspace arrangement.

Transient UI State

Between lines 55-96, EditorViewModel maintains ephemeral editing state including currentFrame, selectedClipIds, zoomScale, toolMode, and previewTabs. Because the class is marked @Observable, mutations to these properties automatically trigger SwiftUI view updates without manual publisher management.

Media Library Cache

Lines 108-131 implement an in-memory asset management system comprising mediaAssets, mediaVisualCache, and a searchIndex. The mediaResolver property maintains a connection to the current project URL, enabling efficient thumbnail generation and media validation without blocking the main thread.

Service Coordination

The view model owns high-level service dependencies including generationService and agentService (lines 32-38), exposing AI-driven capabilities to SwiftUI views through the agentPanelVisible toggle. This centralized ownership prevents service lifecycle issues and ensures consistent state across generation workflows.

Playback Bridge

Acting as intermediary between the UI and VideoEngine, the view model implements playback controls in lines 27-35 and 58-70. Methods like togglePlayback(), play(), pause(), and seekToFrame(_:) update both the underlying engine and observable UI state (isPlaying, playheadState), keeping visual playheads synchronized with audio output.

Undo Support and Drag Handling

To support complex editing operations, lines 94-100 store pre-operation snapshots in dragBefore and preDragTimeline. The notifyTimelineChangedDebounced method (lines 161-169) prevents excessive rebuilds during rapid drag interactions while maintaining undo history integrity.

Project Telemetry

The telemetrySnapshot() and updateTelemetryContext() methods (lines 89-112) aggregate statistics about tracks, clips, and generation logs, feeding anonymized usage data to the telemetry pipeline for performance optimization.

Working with EditorViewModel: Code Examples

The following examples demonstrate practical interactions with EditorViewModel from SwiftUI views.

Toggling Playback

This button implementation reads the isPlaying property (line 59) and calls togglePlayback() (lines 29-35):

Button(viewModel.isPlaying ? "Pause" : "Play") {
    viewModel.togglePlayback()
}

Adding Clips to the Timeline

The placeClip method (lines 30-77) creates Clip instances, handles audio linking, and returns generated identifiers:

let newClipIds = viewModel.placeClip(
    asset: selectedMediaAsset,
    trackIndex: 0,
    startFrame: viewModel.currentFrame,
    durationFrames: 120
)

Managing Multi-Selection

Selection state drives both the timeline visualization and inspector panels through the observable set:

viewModel.selectedClipIds = Set([clipA.id, clipB.id])

Tab switching updates both current state and navigation history (lines 92-95):

viewModel.activePreviewTabId = PreviewTab.media.id
viewModel.previewTabHistory.append(viewModel.activePreviewTabId)

Triggering AI Edits

Extension methods in EditorViewModel+AIEdit.swift coordinate with the generation service:

await viewModel.performAIEdit(on: clip.id, prompt: "Make it brighter")

Modular Architecture via Extensions

Rather than monolithic implementation, Palmier Pro distributes editor functionality across focused extension files. This architecture keeps the core EditorViewModel.swift manageable while providing namespaced APIs for specific features:

  • EditorViewModel+Tracks.swift – Track-level mutations including add, delete, and reorder operations
  • EditorViewModel+TimelineRange.swift – Range selection, trimming, and ripple-mode calculations
  • EditorViewModel+AIEdit.swift – AI-driven editing workflows
  • EditorViewModel+Ripple.swift – Ripple editing for batch timeline adjustments
  • EditorViewModel+Linking.swift – Video/audio clip linking management
  • EditorViewModel+Clipboard.swift – Cut, copy, and paste operations
  • EditorViewModel+GeneratedClips.swift – Handling of AI-generated clip replacements
  • EditorViewModel+PreviewTabs.swift – Tab navigation and history management
  • EditorViewModel+MediaLibrary.swift – Media search index construction and caching
  • EditorViewModel+Cost.swift – AI generation cost analysis utilities
  • EditorViewModel+SaveAsMedia.swift – Export functionality for individual clips

Summary

  • EditorViewModel acts as the single source of truth for both persisted VideoProject data and transient UI state in Palmier Pro.
  • The @Observable macro and @MainActor conformance ensure thread-safe, automatic SwiftUI updates.
  • Core project data (timeline, media manifest, generation log) lives in the main file at lines 27-32.
  • Playback control bridges the gap between UI gestures and the low-level VideoEngine.
  • Extension files in Sources/PalmierPro/Editor/ViewModel/ provide modular, feature-specific APIs while maintaining a thin core view model.
  • Built-in telemetry, undo support, and debounced change notifications support professional editing workflows.

Frequently Asked Questions

How does EditorViewModel synchronize with SwiftUI views?

The class utilizes the @Observable macro introduced in Swift 5.9, which automatically tracks property access within views and triggers updates when values change. Combined with @MainActor isolation, this ensures all UI updates occur on the main thread without manual dispatch queue management.

Where is the playback state actually managed?

While EditorViewModel exposes isPlaying and playheadState properties, it delegates actual media decoding and audio output to the VideoEngine instance. Methods like togglePlayback() and seekToFrame(_:) in lines 27-35 and 58-70 forward commands to the engine while updating observable UI state to reflect playback progress.

Can I extend EditorViewModel for custom editor features?

Yes. The codebase encourages extension-based additions. Create a new file following the EditorViewModel+Feature.swift naming convention in Sources/PalmierPro/Editor/ViewModel/. Extensions can access all public and internal properties of the view model, allowing you to add methods that manipulate the timeline, selectedClipIds, or coordinate with generationService.

How does EditorViewModel handle project persistence?

The view model stores references to Timeline, MediaManifest, and GenerationLog objects which conform to Codable. When the user saves a project, these structures serialize to the project bundle. Panel layouts and preferences synchronize automatically with UserDefaults via the focusedPanel and layoutPreset properties.

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 →