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
kanbansyntax with status columns - Gantt — native Mermaid
ganttsyntax with dependency scheduling - Flowchart — standard
graph TDdependency 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 |
Summary
Functions
Adds a label node for categorizing tasks.
Adds a milestone node.
Adds a task node.
Adds a user node.
Assigns a task to a user. Edge: task -> user.
Returns all task IDs assigned to a given user.
Returns the user ID assigned to a task, or nil.
Returns all user IDs assigned to a task.
Declares that blocker blocks blocked.
Returns child task IDs for a given milestone (connected via :contains edge).
Parent -> child containment edge.
Returns the IDs of nodes that id depends on.
Returns the IDs of nodes that depend on id.
Declares that task depends on dependency.
Returns all edges with their metadata as {from, to, weight, meta} tuples.
Returns all label nodes as {id, data} tuples.
Returns all milestone nodes as {id, data} tuples.
Creates a new empty planner.
Returns the parent milestone ID for a given task, or nil.
Returns all parent milestone IDs for a given task.
Creates a bidirectional :relates_to relationship between two nodes.
Removes a task node and all edges connected to it.
Tags a task with a label. Edge: task -> label.
Returns all task nodes as {id, data} tuples.
Returns tasks filtered by status.
Renders the planner to a DOT (Graphviz) diagram string.
Returns the raw Yog.Multi.Graph struct underpinning the planner.
Renders the planner to a Mermaid diagram string.
Updates an existing task node's properties.
Returns all user nodes as {id, data} tuples.
Types
@type t() :: %Choreo.Planner{ edge_meta: %{optional(Yog.Multi.Graph.edge_id()) => map()}, graph: Yog.Multi.Graph.t(), name: String.t() | nil }
Functions
@spec add_label(t(), Yog.node_id(), keyword()) :: t()
Adds a label node for categorizing tasks.
Options
:title— display name
@spec add_milestone(t(), Yog.node_id(), keyword()) :: t()
Adds a milestone node.
Options
:title— display name:due_date— target date
@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
@spec add_user(t(), Yog.node_id(), keyword()) :: t()
Adds a user node.
Options
:name— display name:email— contact email
@spec assign(t(), Yog.node_id(), Yog.node_id()) :: t()
Assigns a task to a user. Edge: task -> user.
@spec assigned_tasks(t(), Yog.node_id()) :: [Yog.node_id()]
Returns all task IDs assigned to a given user.
@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.
@spec assignees(t(), Yog.node_id()) :: [Yog.node_id()]
Returns all user IDs assigned to a task.
@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.
@spec children(t(), Yog.node_id()) :: [Yog.node_id()]
Returns child task IDs for a given milestone (connected via :contains edge).
@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.
@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.
@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.
@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.
@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.
@spec labels(t()) :: [{Yog.node_id(), map()}]
Returns all label nodes as {id, data} tuples.
@spec milestones(t()) :: [{Yog.node_id(), map()}]
Returns all milestone nodes as {id, data} tuples.
Creates a new empty planner.
@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.
@spec parents(t(), Yog.node_id()) :: [Yog.node_id()]
Returns all parent milestone IDs for a given task.
@spec relates(t(), Yog.node_id(), Yog.node_id()) :: t()
Creates a bidirectional :relates_to relationship between two nodes.
@spec remove_task(t(), Yog.node_id()) :: t()
Removes a task node and all edges connected to it.
@spec tag(t(), Yog.node_id(), Yog.node_id()) :: t()
Tags a task with a label. Edge: task -> label.
@spec tasks(t()) :: [{Yog.node_id(), map()}]
Returns all task nodes as {id, data} tuples.
@spec tasks_by_status(t(), atom()) :: [{Yog.node_id(), map()}]
Returns tasks filtered by status.
Renders the planner to a DOT (Graphviz) diagram string.
@spec to_graph(t()) :: Yog.Multi.Graph.t()
Returns the raw Yog.Multi.Graph struct underpinning the planner.
Renders the planner to a Mermaid diagram string.
Options
:syntax—:kanban(default),:kanban_compat,:gantt, or:flowchart- Other options passed to the specific renderer
@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.
@spec users(t()) :: [{Yog.node_id(), map()}]
Returns all user nodes as {id, data} tuples.