# `MingaEditor.State.TabBar`
[🔗](https://github.com/jsmestad/minga/blob/main/lib/minga_editor/state/tab_bar.ex#L1)

Ordered list of open tabs with an active tab pointer.

The tab bar is the primary navigation structure. Each tab (file or agent)
carries a context snapshot of per-tab editor state. Buffer processes live
in a shared pool, not inside individual tabs.

## Invariants

- There is always at least one tab.
- `active_id` always refers to an existing tab.
- Tab ids are unique and monotonically increasing.

# `t`

```elixir
@type t() :: %MingaEditor.State.TabBar{
  active_id: MingaEditor.State.Tab.id(),
  agent_groups: [MingaEditor.State.AgentGroup.t()],
  next_group_id: pos_integer(),
  next_id: MingaEditor.State.Tab.id(),
  tabs: [MingaEditor.State.Tab.t()]
}
```

Tab bar state.

# `active`

```elixir
@spec active(t()) :: MingaEditor.State.Tab.t() | nil
```

Returns the active tab.

# `active_group`

```elixir
@spec active_group(t()) :: MingaEditor.State.AgentGroup.t() | nil
```

Returns the active agent group.

Derived from the active tab's group_id, not stored separately.
The active agent group is always the agent group of the tab you're looking at.

# `active_group_id`

```elixir
@spec active_group_id(t()) :: non_neg_integer()
```

Returns the active agent group id, derived from the active tab.

# `active_index`

```elixir
@spec active_index(t()) :: non_neg_integer()
```

Returns the index of the active tab (0-based).

# `add`

```elixir
@spec add(t(), MingaEditor.State.Tab.kind(), String.t()) ::
  {t(), MingaEditor.State.Tab.t()}
```

Adds a new tab after the active tab and makes it active.

Returns `{updated_tab_bar, new_tab}` so the caller can use the tab's id.

# `add_agent_group`

```elixir
@spec add_agent_group(t(), String.t(), pid() | nil) ::
  {t(), MingaEditor.State.AgentGroup.t()}
```

Adds an agent agent group and returns `{updated_tab_bar, agent group}`.

The agent group is appended to the agent groups list. The `session` pid
is stored so we can track which agent owns the agent group.

# `any_attention?`

```elixir
@spec any_attention?(t()) :: boolean()
```

Returns true if any tab has its attention flag set.

# `count`

```elixir
@spec count(t()) :: pos_integer()
```

Returns the number of tabs.

# `disclosure_tier`

```elixir
@spec disclosure_tier(t()) :: 0 | 1 | 2 | 3
```

Returns the progressive disclosure tier (0-3) based on agent group count.

# `filter_by_kind`

```elixir
@spec filter_by_kind(t(), MingaEditor.State.Tab.kind()) :: [MingaEditor.State.Tab.t()]
```

Returns all tabs matching the given kind.

# `find_by_kind`

```elixir
@spec find_by_kind(t(), MingaEditor.State.Tab.kind()) ::
  MingaEditor.State.Tab.t() | nil
```

Returns the first tab matching the given kind, or nil.

# `find_by_session`

```elixir
@spec find_by_session(t(), pid()) :: MingaEditor.State.Tab.t() | nil
```

Returns the agent tab whose session matches the given pid, or nil.

# `find_group_by_session`

```elixir
@spec find_group_by_session(t(), pid()) :: MingaEditor.State.AgentGroup.t() | nil
```

Returns the agent group matching the given session pid, or nil.

# `find_sessionless_agent`

```elixir
@spec find_sessionless_agent(t()) :: MingaEditor.State.Tab.t() | nil
```

Returns an agent tab that has no session assigned, or nil.

Used by `start_agent_session` to find the correct tab to bind a
new session to, avoiding ambiguity when multiple agent tabs exist.
Falls back to the active tab if it's an agent tab.

# `get`

```elixir
@spec get(t(), MingaEditor.State.Tab.id()) :: MingaEditor.State.Tab.t() | nil
```

Returns the tab with the given id, or nil.

# `get_group`

```elixir
@spec get_group(t(), non_neg_integer()) :: MingaEditor.State.AgentGroup.t() | nil
```

Returns the agent group with the given id, or nil.

# `has_agent_groups?`

```elixir
@spec has_agent_groups?(t()) :: boolean()
```

Returns true if any agent agent_groups exist.

# `has_tab?`

```elixir
@spec has_tab?(t(), MingaEditor.State.Tab.id()) :: boolean()
```

Returns true if a tab with the given id exists.

# `insert`

```elixir
@spec insert(t(), MingaEditor.State.Tab.kind(), String.t()) ::
  {t(), MingaEditor.State.Tab.t()}
```

Inserts a new tab next to the active tab without switching to it.

Returns `{updated_tab_bar, new_tab}`. The caller is responsible for
calling `switch_to/2` or `EditorState.switch_tab/2` to activate it.
This is the primitive that `add/3` and `EditorState.add_buffer/2` build on.

# `most_recent_of_kind`

```elixir
@spec most_recent_of_kind(t(), MingaEditor.State.Tab.kind()) ::
  MingaEditor.State.Tab.t() | nil
```

Returns the most recently used tab of the given kind that is NOT the
active tab. Useful for "switch back to previous file/agent" commands.

Tabs are searched right-to-left from the active position (wrapping), so
the nearest neighbor of the requested kind is returned.

# `move_tab_to_group`

```elixir
@spec move_tab_to_group(t(), MingaEditor.State.Tab.id(), non_neg_integer()) :: t()
```

Moves a tab to a different agent group.

# `new`

```elixir
@spec new(MingaEditor.State.Tab.t()) :: t()
```

Creates a tab bar with a single initial tab.

# `next`

```elixir
@spec next(t()) :: t()
```

Switches to the next tab, wrapping around.

# `next_agent_group`

```elixir
@spec next_agent_group(t()) :: t()
```

Switches to the next agent group, wrapping around. No-op if no groups.

# `next_of_kind`

```elixir
@spec next_of_kind(t(), MingaEditor.State.Tab.kind()) :: t()
```

Cycles to the next tab of the given kind, wrapping around.
If the active tab is already of that kind, jumps to the next one.
If the active tab is a different kind, jumps to the first of the
requested kind. Returns unchanged if no tabs of that kind exist.

# `prev`

```elixir
@spec prev(t()) :: t()
```

Switches to the previous tab, wrapping around.

# `prev_agent_group`

```elixir
@spec prev_agent_group(t()) :: t()
```

Switches to the previous agent group, wrapping around. No-op if no groups.

# `remove`

```elixir
@spec remove(t(), MingaEditor.State.Tab.id()) :: {:ok, t()} | :last_tab
```

Removes the tab with the given id.

If the removed tab was active, switches to the nearest neighbor (prefer
right, then left). Returns `{:ok, updated_tab_bar}` or `:last_tab` if
this is the only tab (can't remove the last one).

# `remove_group`

```elixir
@spec remove_group(t(), non_neg_integer()) :: t()
```

Removes a agent group and migrates its tabs to ungrouped (group_id 0).

Cannot remove group_id 0 (ungrouped tabs).

# `set_attention_by_session`

```elixir
@spec set_attention_by_session(t(), pid(), boolean()) :: t()
```

Sets the attention flag on the tab matching the given session pid.

# `switch_to`

```elixir
@spec switch_to(t(), MingaEditor.State.Tab.id()) :: t()
```

Switches the active tab to the one with the given id.

# `switch_to_group`

```elixir
@spec switch_to_group(t(), non_neg_integer()) :: t()
```

Switches to the given agent group by activating its first tab.

Returns unchanged if the agent group doesn't exist or has no tabs.

# `tab_at`

```elixir
@spec tab_at(t(), pos_integer()) :: MingaEditor.State.Tab.t() | nil
```

Returns the tab at the given 1-based position index, or nil.

# `tabs_in_group`

```elixir
@spec tabs_in_group(t(), non_neg_integer()) :: [MingaEditor.State.Tab.t()]
```

Returns all tabs belonging to the given agent group.

# `update_context`

```elixir
@spec update_context(t(), MingaEditor.State.Tab.id(), MingaEditor.State.Tab.context()) ::
  t()
```

Updates the context of the tab with the given id.

# `update_group`

```elixir
@spec update_group(t(), non_neg_integer(), (MingaEditor.State.AgentGroup.t() -&gt;
                                        MingaEditor.State.AgentGroup.t())) ::
  t()
```

Updates a agent group by applying `fun` to it.

Returns unchanged tab bar if no agent group matches.

# `update_label`

```elixir
@spec update_label(t(), MingaEditor.State.Tab.id(), String.t()) :: t()
```

Updates the label of the tab with the given id.

# `update_tab`

```elixir
@spec update_tab(t(), MingaEditor.State.Tab.id(), (MingaEditor.State.Tab.t() -&gt;
                                               MingaEditor.State.Tab.t())) ::
  t()
```

Applies `fun` to the tab with `id`, replacing it in the list.

Returns the updated tab bar. If no tab matches, returns unchanged.

---

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