---
title: View-Aware Command Filtering
read_when:
  - "registering a new TUI command with view-mode filtering"
  - "understanding how commands are filtered by view mode (plans, learn, objectives)"
  - "adding objective-specific commands to the command palette"
  - "implementing streaming commands in the TUI"
tripwires:
  - action: "registering a new TUI command without a view-mode predicate"
    warning: "Every command must use _is_plan_view() or _is_objectives_view() to prevent it from appearing in the wrong view. Commands without view predicates appear in all views."
  - action: "adding streaming commands without using the streaming operation pattern"
    warning: "Streaming ACTION commands need the streaming operation wrapper with status bar tracking. See streaming-output.md for the current pattern."
---

# View-Aware Command Filtering

The TUI command palette filters commands by the active view mode. Plan commands appear only in Plans/Learn views; objective commands appear only in the Objectives view. This filtering is implemented through view-mode predicates in the command registry.

## View Mode Predicates

<!-- Source: src/erk/tui/commands/registry.py:21-28 -->

Two predicates partition the command space by view mode:

<!-- See _is_plan_view and _is_objectives_view in src/erk/tui/commands/registry.py:21-28 -->

- `_is_plan_view(ctx)`: Returns `True` when not in Objectives view (i.e., Plans or Learn) — checks `ctx.view_mode != ViewMode.OBJECTIVES`
- `_is_objectives_view(ctx)`: Returns `True` when in Objectives view — checks `ctx.view_mode == ViewMode.OBJECTIVES`

The `CommandContext.view_mode` field carries the active `ViewMode` enum value. `MainListCommandProvider` reads this dynamically from `self._app._view_mode`, while `PlanCommandProvider` hardcodes `ViewMode.PLANS` since the detail modal is always in plan context.

## Why Predicates, Not Separate Registries

A single `get_all_commands()` list defines both plan and objective commands. View filtering happens inside each command's `is_available` predicate, composed with data-availability checks:

```python
# Plan command: needs plan view + PR number
is_available=lambda ctx: _is_plan_view(ctx) and ctx.row.pr_number is not None

# Objective command: needs objectives view only
is_available=lambda ctx: _is_objectives_view(ctx)
```

This keeps command definitions in one place. Adding a command to the wrong view is prevented by requiring the view predicate — without it, the command would appear everywhere.

## Shortcut Reuse Across Views

<!-- Source: src/erk/tui/commands/registry.py, shortcut assignments -->

Plan and objective commands safely reuse the same keyboard shortcuts because the view predicates guarantee mutual exclusivity:

| Shortcut | Plan View Command | Objectives View Command |
| -------- | ----------------- | ----------------------- |
| `s`      | Dispatch to Queue | Implement (One-Shot)    |
| `5`      | Rebase Remote     | Check Objective         |
| `i`      | Open Issue        | Open Objective          |
| `1`      | Copy Prepare      | Copy Implement          |
| `3`      | Copy Dispatch     | Copy View               |

A shortcut collision would only occur if both a plan and objective command with the same shortcut had overlapping view predicates. The `_is_plan_view` / `_is_objectives_view` split prevents this.

## Objective Command Definitions

<!-- Source: src/erk/tui/commands/registry.py:218-357 -->

Six objective commands are registered, spanning all three categories:

| ID                | Category | Shortcut | Display Name Generator            |
| ----------------- | -------- | -------- | --------------------------------- |
| `one_shot_plan`   | ACTION   | `s`      | `erk objective plan N --one-shot` |
| `check_objective` | ACTION   | `5`      | `erk objective check N`           |
| `close_objective` | ACTION   | —        | `erk objective close N --force`   |
| `open_objective`  | OPEN     | `i`      | Issue URL or "Objective"          |
| `copy_plan`       | COPY     | `1`      | `erk objective plan N`            |
| `copy_view`       | COPY     | `3`      | `erk objective view N`            |

All six use `_is_objectives_view(ctx)` as their sole view predicate. Objective commands don't have compound availability conditions (unlike plan commands which check for PR, issue URL, etc.) because objective rows always have an issue number.

## Provider Context Building

<!-- Source: src/erk/tui/commands/provider.py:85-94, 174-181 -->

The two command providers resolve `CommandContext` differently:

- **`MainListCommandProvider._get_context()`**: Returns `CommandContext | None`. Reads `view_mode` dynamically from `self._app._view_mode`, which changes as the user switches tabs. This is what enables objective commands to appear when the Objectives tab is active.

- **`PlanCommandProvider._get_context()`**: Returns `CommandContext` (never None). Hardcodes `view_mode=ViewMode.PLANS` because the detail modal always shows a plan. This means objective commands never appear in the detail modal's command palette — by design, since the modal is scoped to a single plan.

## Streaming ACTION Commands

ACTION commands that run long-running subprocess operations (land, rebase, address) use status-bar-based streaming. The pattern is: register in the status bar, stream subprocess output with line buffering, then clean up the status bar entry. Results are shown via toast notifications.

For the full streaming pattern reference, see [TUI Streaming Output](streaming-output.md).

## Backend as Third Dimension

View mode and data availability are the first two filter dimensions. **Plan backend** is the third. Some commands only make sense for one backend:

- `copy_prepare` and `copy_prepare_activate` are hidden when `plan_backend == "github-draft-pr"` because the "prepare" workflow uses issue numbers that don't apply to draft-PR plans.

The `plan_backend` field is available on `CommandContext` (alongside `view_mode` and `row`). The `_is_github_backend(ctx)` predicate checks `ctx.plan_backend == "github"`.

See [Backend-Aware Commands](backend-aware-commands.md) for the complete reference.

## Related Documentation

- [TUI Command Architecture](action-inventory.md) — Data-driven availability predicates and category mapping
- [Dual Provider Pattern](dual-handler-pattern.md) — How two providers share one registry
- [TUI Streaming Output](streaming-output.md) — Cross-thread UI updates for ACTION commands
