# `Choreo.Planner`
[🔗](https://github.com/code-shoily/choreo/blob/v0.14.1/lib/choreo/planner.ex#L1)

Project planning and task management diagram builder on top of Yog.

`Choreo.Planner` models projects as typed multigraphs where tasks,
milestones, users, and labels are nodes and relationships (hierarchy,
dependencies, assignments, tags) are edges.

It supports three Mermaid render targets:

  * **Kanban** — native Mermaid `kanban` syntax with status columns
  * **Gantt** — native Mermaid `gantt` syntax with dependency scheduling
  * **Flowchart** — standard `graph TD` dependency network

Analysis functions identify ready work, blocked tasks, critical paths,
and bottlenecks.

## Quick Start

    project =
      Choreo.Planner.new("Launch v1")
      |> Choreo.Planner.add_milestone(:v1, title: "V1 Launch")
      |> Choreo.Planner.add_task(:design, title: "Design", status: :done, estimate_hours: 16)
      |> Choreo.Planner.add_task(:impl, title: "Implement", status: :backlog, estimate_hours: 24)
      |> Choreo.Planner.add_task(:test, title: "Test", status: :backlog, estimate_hours: 8)
      |> Choreo.Planner.add_user(:alice, name: "Alice")
      |> Choreo.Planner.contains(:v1, :design)
      |> Choreo.Planner.contains(:v1, :impl)
      |> Choreo.Planner.contains(:v1, :test)
      |> Choreo.Planner.depends_on(:impl, :design)
      |> Choreo.Planner.depends_on(:test, :impl)
      |> Choreo.Planner.assign(:design, :alice)

    Choreo.Planner.ready(project)
    # => [{:impl, %{status: :backlog, ...}}]

    Choreo.Planner.Analysis.critical_path(project, milestone: :v1)
    # => {:ok, [:design, :impl, :test], total_estimate: 48}

    kanban = Choreo.Planner.to_mermaid(project, syntax: :kanban)
    gantt  = Choreo.Planner.to_mermaid(project, syntax: :gantt)
    flow   = Choreo.Planner.to_mermaid(project, syntax: :flowchart)

## Node Types

| Type | Builder | Default Properties |
|------|---------|-------------------|
| `:task` | `add_task/3` | `status: :backlog`, `priority: :medium` |
| `:milestone` | `add_milestone/3` | none |
| `:user` | `add_user/3` | none |
| `:label` | `add_label/3` | none |

## Edge Types

| Type | Builder | Direction | Meaning |
|------|---------|-----------|---------|
| `:contains` | `contains/3` | milestone → task | Hierarchy |
| `:depends_on` | `depends_on/3` | dependency → task | Finish-to-start |
| `:blocks` | `blocks/3` | blocker → blocked | Semantic blocker |
| `:assigned_to` | `assign/3` | task → user | Ownership |
| `:tagged_with` | `tag/3` | task → label | Categorization |
| `:relates_to` | `relates/3` | bidirectional | Loose association |

# `t`

```elixir
@type t() :: %Choreo.Planner{
  edge_meta: %{optional(Yog.Multi.Graph.edge_id()) =&gt; map()},
  graph: Yog.Multi.Graph.t(),
  name: String.t() | nil
}
```

# `add_label`

```elixir
@spec add_label(t(), Yog.node_id(), keyword()) :: t()
```

Adds a label node for categorizing tasks.

## Options

  * `:title` — display name

# `add_milestone`

```elixir
@spec add_milestone(t(), Yog.node_id(), keyword()) :: t()
```

Adds a milestone node.

## Options

  * `:title` — display name
  * `:due_date` — target date

# `add_task`

```elixir
@spec add_task(t(), Yog.node_id(), keyword()) :: t()
```

Adds a task node.

## Options

  * `:status` — `:backlog` (default), `:todo`, `:in_progress`, `:in_review`, `:done`, `:cancelled`
  * `:priority` — `:low`, `:medium` (default), `:high`, `:critical`
  * `:title` — display name
  * `:due_date` — target date (`Date.t()`)
  * `:estimate_hours` — numeric estimate
  * `:actual_hours` — numeric actual

# `add_user`

```elixir
@spec add_user(t(), Yog.node_id(), keyword()) :: t()
```

Adds a user node.

## Options

  * `:name` — display name
  * `:email` — contact email

# `assign`

```elixir
@spec assign(t(), Yog.node_id(), Yog.node_id()) :: t()
```

Assigns a task to a user. Edge: task -> user.

# `assigned_tasks`

```elixir
@spec assigned_tasks(t(), Yog.node_id()) :: [Yog.node_id()]
```

Returns all task IDs assigned to a given user.

# `assignee`

```elixir
@spec assignee(t(), Yog.node_id()) :: Yog.node_id() | nil
```

Returns the user ID assigned to a task, or `nil`.

If a task is assigned to multiple users, this returns the first one found.
Use `assignees/2` to retrieve the full list.

# `assignees`

```elixir
@spec assignees(t(), Yog.node_id()) :: [Yog.node_id()]
```

Returns all user IDs assigned to a task.

# `blocks`

```elixir
@spec blocks(t(), Yog.node_id(), Yog.node_id()) :: t()
```

Declares that `blocker` blocks `blocked`.

Semantically identical to `depends_on/3` but carries blocking intent.
Both must be tasks.

# `children`

```elixir
@spec children(t(), Yog.node_id()) :: [Yog.node_id()]
```

Returns child task IDs for a given milestone (connected via `:contains` edge).

# `contains`

```elixir
@spec contains(t(), Yog.node_id(), Yog.node_id()) :: t()
```

Parent -> child containment edge.

The parent must be a milestone and the child must be a task.

# `dependencies`

```elixir
@spec dependencies(t(), Yog.node_id()) :: [Yog.node_id()]
```

Returns the IDs of nodes that `id` depends on.

Includes both `:depends_on` and `:blocks` edges.

# `dependents`

```elixir
@spec dependents(t(), Yog.node_id()) :: [Yog.node_id()]
```

Returns the IDs of nodes that depend on `id`.

Includes both `:depends_on` and `:blocks` edges.

# `depends_on`

```elixir
@spec depends_on(t(), Yog.node_id(), Yog.node_id()) :: t()
```

Declares that `task` depends on `dependency`.

Creates an edge `dependency -> task` meaning *dependency must finish
before task can start*. Both must be tasks.

# `edges_with_meta`

```elixir
@spec edges_with_meta(t()) :: [{Yog.node_id(), Yog.node_id(), any(), map()}]
```

Returns all edges with their metadata as `{from, to, weight, meta}` tuples.

# `labels`

```elixir
@spec labels(t()) :: [{Yog.node_id(), map()}]
```

Returns all label nodes as `{id, data}` tuples.

# `milestones`

```elixir
@spec milestones(t()) :: [{Yog.node_id(), map()}]
```

Returns all milestone nodes as `{id, data}` tuples.

# `new`

```elixir
@spec new(keyword() | String.t() | nil) :: t()
```

Creates a new empty planner.

# `parent`

```elixir
@spec parent(t(), Yog.node_id()) :: Yog.node_id() | nil
```

Returns the parent milestone ID for a given task, or `nil`.

If a task is contained in multiple milestones, this returns the first one
found. Use `parents/2` to retrieve the full list.

# `parents`

```elixir
@spec parents(t(), Yog.node_id()) :: [Yog.node_id()]
```

Returns all parent milestone IDs for a given task.

# `relates`

```elixir
@spec relates(t(), Yog.node_id(), Yog.node_id()) :: t()
```

Creates a bidirectional `:relates_to` relationship between two nodes.

# `remove_task`

```elixir
@spec remove_task(t(), Yog.node_id()) :: t()
```

Removes a task node and all edges connected to it.

# `tag`

```elixir
@spec tag(t(), Yog.node_id(), Yog.node_id()) :: t()
```

Tags a task with a label. Edge: task -> label.

# `tagged_tasks`

```elixir
@spec tagged_tasks(t(), Yog.node_id()) :: [Yog.node_id()]
```

Returns all task IDs tagged with a given label.

# `tags`

```elixir
@spec tags(t(), Yog.node_id()) :: [Yog.node_id()]
```

Alias for `task_labels/2`.

# `task_labels`

```elixir
@spec task_labels(t(), Yog.node_id()) :: [Yog.node_id()]
```

Returns all label IDs tagged on a task.

# `tasks`

```elixir
@spec tasks(t()) :: [{Yog.node_id(), map()}]
```

Returns all task nodes as `{id, data}` tuples.

# `tasks_by_status`

```elixir
@spec tasks_by_status(t(), atom()) :: [{Yog.node_id(), map()}]
```

Returns tasks filtered by status.

# `theme`

```elixir
@spec theme(atom(), keyword()) :: Choreo.Theme.t()
```

Returns a theme for `Choreo.Planner`.

## Examples

    iex> theme = Choreo.Planner.theme(:default, graph_rankdir: :lr)
    iex> theme.graph_rankdir
    :lr

# `to_dot`

```elixir
@spec to_dot(t(), keyword()) :: String.t()
```

Renders the planner to a DOT (Graphviz) diagram string.

# `to_graph`

```elixir
@spec to_graph(t()) :: Yog.Multi.Graph.t()
```

Returns the raw `Yog.Multi.Graph` struct underpinning the planner.

# `to_mermaid`

```elixir
@spec to_mermaid(t(), keyword()) :: String.t()
```

Renders the planner to a Mermaid diagram string.

## Options

  * `:syntax` — `:kanban` (default), `:kanban_compat`, `:gantt`, `:flowchart`, or `:swimlane`
  * Other options passed to the specific renderer

# `update_task`

```elixir
@spec update_task(t(), Yog.node_id(), keyword()) :: t()
```

Updates an existing task node's properties.

Merges the given options into the task's current data.
Raises `ArgumentError` if the node does not exist or is not a task.

# `users`

```elixir
@spec users(t()) :: [{Yog.node_id(), map()}]
```

Returns all user nodes as `{id, data}` tuples.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
