How Media Folder Organization and Indexing Work in Palmier Pro: Architecture, API, and Performance

Palmier Pro organizes media assets in a hierarchical tree structure using the MediaFolder model, indexes relationships via the MediaFolderIndex struct for O(1) lookups and O(N) traversals, and exposes all operations through the EditorViewModel extension with built-in cycle detection and undo support.

The palmier-io/palmier-pro repository implements a client-side media library that treats folders as a directed acyclic graph stored in the MediaManifest. Understanding how the MediaFolderIndex builds lookup tables from the flat folders array—and how the EditorViewModel leverages these indexes—reveals the performance and safety guarantees behind drag-and-drop operations, breadcrumb navigation, and cascading deletes.

The Core Data Model (MediaFolder and MediaManifest)

MediaFolder Structure

Each folder is defined in MediaFolder.swift as a Codable struct:

struct MediaFolder: Codable, Sendable, Equatable, Identifiable {
    let id: String                // Stable UUID
    var name: String              // User-visible label
    var parentFolderId: String?   // nil represents the root
}

The parentFolderId field establishes the parent-child relationship. Because the struct is immutable with respect to id, the hierarchy remains stable even when names change or folders move between parents.

MediaManifest as Source of Truth

The manifest, defined in MediaManifest.swift, holds two parallel arrays:

struct MediaManifest: Codable, Sendable {
    var folders: [MediaFolder] = []   // Flat list of all folders
    var entries: [MediaAsset] = []    // Media files referencing folderId
}

This flat storage simplifies serialization but requires runtime indexing for efficient tree traversal.

Hierarchical Indexing with MediaFolderIndex

The MediaFolderIndex struct, implemented privately within EditorViewModel+Folders.swift, transforms the flat folders array into hash-based lookup tables.

Building the Lookup Tables (byId and childrenByParent)

When instantiated, the index builds two dictionaries:

  • byId: [String: MediaFolder] – Maps folder identifiers to their models in O(1) time.
  • childrenByParent: [String?: [MediaFolder]] – Groups folders by their parentFolderId using Dictionary(grouping:by:), enabling O(1) access to immediate children.
private init(_ folders: [MediaFolder]) {
    var map: [String: MediaFolder] = [:]
    for folder in folders { map[folder.id] = folder }
    self.byId = map
    self.childrenByParent = Dictionary(grouping: folders, by: \.parentFolderId)
}

Path Resolution and Descendant Traversal

path(for folderId: String?) -> [MediaFolder] reconstructs the ancestry chain from a leaf to the root by following parentFolderId pointers backward, then reverses the result to return root-to-leaf ordering. This powers breadcrumb UIs.

idsIncludingDescendants(_ ids: Set<String>) -> Set<String> expands a set of folder IDs to include every nested descendant recursively. The algorithm uses the childrenByParent map to traverse subtrees without scanning the entire manifest, operating in O(N) where N is the number of descendants.

Cycle Detection for Safe Operations

The isDescendant(folderId:of:) method walks upward from a potential child, tracking visited IDs in a Set<String>, and returns true if it encounters the proposed ancestor. This validation prevents users from dragging a folder into its own subtree—a necessary guard for maintaining the acyclic invariant.

EditorViewModel Folder Operations API

All mutations flow through the EditorViewModel extension in EditorViewModel+Folders.swift, which coordinates between the manifest and the indexing layer.

Reading the Hierarchy

  • folder(id:) – Returns a single folder via dictionary lookup.
  • subfolders(of:) – Returns direct children using childrenByParent.
  • folderPath(for:) – Returns [MediaFolder] from root to target.
  • assetsIn(folderId:) – Filters mediaManifest.entries by folderId.

Creating and Renaming Folders

createFolder(name:in:) appends a new MediaFolder to mediaManifest.folders, automatically generating a UUID. The method registers an undo snapshot via mediaLibraryUndoSnapshot() before mutation.

renameFolder(id:name:) locates the folder by ID and mutates only the name property, preserving the hierarchy and all references.

Moving Folders and Cycle Prevention

moveFoldersToFolder(folderIds:parentFolderId:) validates the move in two phases:

  1. Cycle check – Uses MediaFolderIndex.isDescendant to reject moves that would create a cycle.
  2. Batch update – Applies parent changes and registers inverse operations for undo/redo.

Deleting with Cascade Support

deleteFolders(ids:) utilizes MediaFolderIndex.idsIncludingDescendants to compute the complete closure of folders to remove. It then:

  1. Re-parents any affected timelines to the root folder.
  2. Removes associated assets.
  3. Deletes the folder records from the manifest.
  4. Registers a reversible undo block that restores the entire subtree.

Implementation Deep Dive: Algorithms and Performance

Index rebuild strategy – Rather than maintaining complex incremental updates, the view model constructs a fresh MediaFolderIndex on demand for each query. Because manifest sizes remain manageable (hundreds to thousands of folders), the O(N) reconstruction cost is negligible compared to the simplicity and correctness guarantees.

Complexity guarantees:

  • Lookup by ID: O(1) via byId dictionary.
  • Child enumeration: O(1) to retrieve the array, O(C) to iterate where C is child count.
  • Path resolution: O(D) where D is tree depth.
  • Descendant collection: O(N) for the subtree size.
  • Cycle detection: O(D) upward traversal with constant-time set insertions.

Practical Usage Examples

The following Swift patterns demonstrate typical interactions with the Palmier Pro folder system:

let viewModel: EditorViewModel = // ... obtained from environment

// 1️⃣ Create a nested hierarchy
let rootId = viewModel.createFolder(name: "Production")
let scenesId = viewModel.createFolder(name: "Scenes", in: rootId)
let shotsId = viewModel.createFolder(name: "Shots", in: scenesId)

// 2️⃣ Retrieve breadcrumb path
let path = viewModel.folderPath(for: shotsId)
// Returns: [MediaFolder("Production"), MediaFolder("Scenes"), MediaFolder("Shots")]
let breadcrumb = path.map { $0.name }.joined(separator: " > ")
// "Production > Scenes > Shots"

// 3️⃣ Safe move operation (cycle prevention built-in)
viewModel.moveFoldersToFolder(folderIds: [rootId], parentFolderId: shotsId)
// No-op: isDescendant detects that root contains shots

// 4️⃣ Delete with automatic cascade
viewModel.deleteFolders(ids: Set([scenesId]))
// Removes "Scenes" and "Shots", moves any assets to root, preserves undo history

Summary

  • MediaFolder models use parentFolderId to define a tree structure without cycles.
  • MediaManifest stores folders in a flat array, requiring runtime indexing for efficient navigation.
  • MediaFolderIndex provides O(1) ID lookups and O(N) subtree traversals via byId and childrenByParent dictionaries.
  • Cycle detection via isDescendant guards every move operation to maintain acyclic integrity.
  • Cascading deletes leverage idsIncludingDescendants to atomically remove entire subtrees while preserving undo granularity.
  • All operations route through EditorViewModel+Folders.swift, which manages snapshots for undo/redo and coordinates between the manifest and the indexing layer.

Frequently Asked Questions

How does Palmier Pro prevent users from moving a folder into its own subfolder?

The moveFoldersToFolder method in EditorViewModel+Folders.swift validates moves using MediaFolderIndex.isDescendant. This method walks upward from the proposed child, tracking visited IDs in a set, and aborts the operation if it encounters the target parent, ensuring the hierarchy remains a directed acyclic graph.

What is the time complexity of retrieving the full path for a folder?

Path resolution operates in O(D) time where D is the depth of the folder in the tree. The path(for:) method follows parentFolderId pointers upward until reaching the root, then reverses the collected array to return root-to-leaf ordering. Because the index rebuilds dictionaries on demand, each parent lookup is O(1), making the entire operation linear with respect to tree depth.

Why does Palmier Pro rebuild the MediaFolderIndex instead of updating it incrementally?

The MediaFolderIndex is a lightweight, immutable struct constructed fresh for each query using the current mediaManifest.folders array. Given typical project scales (hundreds to low-thousands of folders), the O(N) reconstruction cost is negligible compared to the complexity of maintaining incremental consistency across undo/redo boundaries, drag-and-drop cancellations, and batch deletions. This approach guarantees that every query sees a consistent snapshot of the hierarchy.

How does the delete operation handle assets contained within nested folders?

The deleteFolders(ids:) method first calls idsIncludingDescendants to compute the complete set of folder IDs to remove, including all descendants. It then filters mediaManifest.entries to identify assets within those folders, re-parents any affected timelines to the root, and finally removes the folders and assets atomically. The operation registers a reversible undo block that restores the entire subtree state.

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 →