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

> Dive into Palmier Pro's media folder organization and indexing. Explore its hierarchical structure, efficient indexing with O(1) lookups, and the EditorViewModel for seamless operations.

- Repository: [Palmier/palmier-pro](https://github.com/palmier-io/palmier-pro)
- Tags: architecture
- Published: 2026-07-20

---

**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](https://github.com/palmier-io/palmier-pro/blob/main/Sources/PalmierPro/Models/MediaFolder.swift) as a Codable struct:

```swift
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](https://github.com/palmier-io/palmier-pro/blob/main/Sources/PalmierPro/Models/MediaManifest.swift), holds two parallel arrays:

```swift
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](https://github.com/palmier-io/palmier-pro/blob/main/Sources/PalmierPro/Editor/ViewModel/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.

```swift
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](https://github.com/palmier-io/palmier-pro/blob/main/Sources/PalmierPro/Editor/ViewModel/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:

```swift
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](https://github.com/palmier-io/palmier-pro/blob/main/Sources/PalmierPro/Editor/ViewModel/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.