# Dynamic Collections

Dynamic collections are editable feeds, such as custom playlists and playback queues, where users can add, remove, reorder, or delete items and collections.

When a feed is tagged with `"role": "dynamic_collection"`, the client UI renderer adapts from a static read-only view to an interactive editable interface driven by `dynamic_collection_options`.

## Configuration and properties

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| **`postUrl`** | string | **Yes** | The HTTPS endpoint where the client renderer dispatches standard Applicaster Cloud Events when users perform mutations. |
| **`operations`** | string | **Yes** | Comma-separated enabled mutation operations: `"remove"` (delete/remove affordances), `"reorder"` (drag-and-drop handles), `"add"` ("+Add" button). Examples: `"remove,reorder"`, `"add,remove,reorder"`, `"add"`. |
| **`events`** | object | Optional | Mapping of interactive event triggers to action arrays. `events.add` executes when user clicks the add/create button (typically opening a `showTextInput` modal). |

## Client renderer UI behavior

The client renderer inspects `operations` to dynamically inject UI controls:

- **`remove`**: Adds delete/remove affordance to each row (swipe-to-delete, trailing delete icon, context menu).
- **`reorder`**: Renders drag-and-drop handles for reordering rows.
- **`add`**: Renders primary create/add button in toolbar, header, or footer.

## Required item-scoped entry actions

When you declare `"operations": "remove,reorder"` at the feed level, every entry in the feed **MUST** define corresponding item-scoped actions in `extensions.entry_action` (or `extensions.entry_actions`):

- **`remove` operation**: Requires an entry action with **`alias: "remove_item"`**. When the user selects delete, the client renderer executes the item's `remove_item` action array, typically sending `com.applicaster.collection.remove.v1` for tracks or `com.applicaster.collection.delete.v1` for playlists.
- **`reorder` operation**: Requires an entry action with **`alias: "reorder_item"`**. When the user reorders an item, the client renderer executes the item's `reorder_item` action array, typically sending `com.applicaster.collection.reorder.v1`.

## Cloud Event routing

When an interactive operation occurs, the client dispatches a JSON Cloud Event via HTTP `POST` to `postUrl`. See [Cloud Events](./cloud-events.md) for the complete event contracts and receiver response requirements.

**Row removal in track/item list (`operations: "remove"`):**

```json
{
  "specversion": "1.0",
  "type": "com.applicaster.collection.remove.v1",
  "source": "client-app",
  "data": {
    "collectionId": "<current_feed_id>",
    "itemId": "<deleted_entry_id>"
  }
}
```

**Row deletion in collections list (`operations: "remove"`):**

```json
{
  "specversion": "1.0",
  "type": "com.applicaster.collection.delete.v1",
  "source": "client-app",
  "data": {
    "collectionId": "<deleted_collection_id>"
  }
}
```

**Row reordering (`operations: "reorder"`):**

```json
{
  "specversion": "1.0",
  "type": "com.applicaster.collection.reorder.v1",
  "source": "client-app",
  "data": {
    "collectionId": "<current_feed_id>",
    "itemIds": ["track-1", "track-2", "track-3"]
  }
}
```

**Triggering add/create (`operations: "add"` with `events.add`):**

```json
{
  "specversion": "1.0",
  "type": "com.applicaster.collection.create.v1",
  "source": "client-app",
  "data": {
    "name": "<user_input_title>",
    "itemId": "<optional_initial_track_id>"
  }
}
```

## Examples

### Editable playlist tracks

This example enables `remove` and `reorder` with item-scoped actions.

```json
{
  "extensions": {
    "role": "dynamic_collection",
    "dynamic_collection_options": {
      "postUrl": "https://server.com/cloud-events",
      "operations": "remove,reorder"
    }
  },
  "entry": [
    {
      "id": "song-101",
      "title": "Retro Beats",
      "extensions": {
        "entry_action": [
          {
            "button": { "alias": "remove_item" },
            "dismiss_on_action": true,
            "actions": [
              {
                "type": "sendCloudEvent",
                "options": {
                  "url": "https://server.com/cloud-events",
                  "type": "com.applicaster.collection.remove.v1",
                  "subject": "remove_item_from_collection",
                  "data": {
                    "collectionId": "my-playlist-id",
                    "itemId": "song-101"
                  }
                }
              },
              { "type": "refreshComponent" }
            ]
          },
          {
            "button": { "alias": "reorder_item" },
            "dismiss_on_action": false,
            "actions": [
              {
                "type": "sendCloudEvent",
                "options": {
                  "url": "https://server.com/cloud-events",
                  "type": "com.applicaster.collection.reorder.v1",
                  "subject": "reorder_item_in_collection",
                  "data": {
                    "collectionId": "my-playlist-id",
                    "itemId": "song-101"
                  }
                }
              },
              { "type": "refreshComponent" }
            ]
          }
        ]
      }
    }
  ]
}
```

### Editable playlists with inline creation

This example enables `add`, `remove`, and `reorder`.

```json
{
  "extensions": {
    "role": "dynamic_collection",
    "dynamic_collection_options": {
      "postUrl": "https://server.com/cloud-events",
      "operations": "add,remove,reorder",
      "events": {
        "add": [
          {
            "type": "showTextInput",
            "options": {
              "headerTitle": "Create New Playlist",
              "inputLabel": "Playlist Name",
              "defaultValue": "",
              "buttonLabel": "Create",
              "actions": [
                {
                  "type": "sendCloudEvent",
                  "options": {
                    "url": "https://server.com/cloud-events",
                    "type": "com.applicaster.collection.create.v1",
                    "subject": "create_playlist",
                    "data": { "name": "@{screen/playlistName}" }
                  }
                },
                {
                  "type": "showToast",
                  "options": { "message": "Playlist created!" }
                },
                { "type": "refreshComponent" }
              ]
            }
          }
        ]
      }
    }
  }
}
```

### Selector mode with "+ Create Playlist"

`dynamic_collection_options` can be combined with `role: "collection_selector"` so users can create a new playlist inside a track's "Add to Playlist" selector modal:

```json
{
  "extensions": {
    "role": "collection_selector",
    "behavior": {
      "select_mode": "multi",
      "current_selection": ["playlist-1"]
    },
    "dynamic_collection_options": {
      "postUrl": "https://server.com/cloud-events",
      "operations": "add",
      "events": {
        "add": [
          {
            "type": "showTextInput",
            "options": {
              "headerTitle": "Create New Playlist",
              "inputLabel": "Playlist Name",
              "defaultValue": "",
              "buttonLabel": "Create",
              "actions": [
                {
                  "type": "sendCloudEvent",
                  "options": {
                    "url": "https://server.com/cloud-events",
                    "type": "com.applicaster.collection.create.v1",
                    "subject": "create_collection",
                    "data": { "name": "@{screen/playlistName}" }
                  }
                },
                {
                  "type": "showToast",
                  "options": { "message": "Playlist created!" }
                },
                { "type": "refreshComponent" }
              ]
            }
          }
        ]
      }
    }
  }
}
```
