Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions docs/ai/design/2026-08-16-feature-agent-list-filter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
phase: design
title: Agent Name Filter Design
description: Architecture for inline console agent filtering
---

# Agent Name Filter Design

## Architecture Overview

```mermaid
flowchart LR
Source[useAgentList ordered agents] --> Shell[ConsoleAppShell filter state]
Shell --> Pure[filterAgents / match positions]
Pure --> Visible[visibleAgents]
Visible --> Nav[selection and j/k routing]
Visible --> Pane[AgentListPane rendering and scroll]
Shell --> Pause[ConsoleProvider text-entry/poll pause]
Routing[consoleKeyRouting] --> Shell
```

`ConsoleAppShell` owns the query and a `'filter'` focus sub-state, derives `visibleAgents` from the ordered source, and supplies that same array to selection, navigation, preview coherence, and `AgentListPane`. Filter editing is intercepted before global/list commands. The provider receives a generalized text-entry-active signal so polling is paused for message composition or a filter session.

## Data Models

- `AgentFilterState = { text: string }`; `ConsoleFocus` adds `'filter'` for editing. The object shape is an extension seam, not an invitation to add other dimensions now.
- `visibleAgents = filterAgents(agents, filter.text)`; input order is preserved.
- `findMatchPositions(name, query): number[] | null` returns flat `[start, end, ...]` ranges for every non-overlapping matched occurrence; empty query returns `[]`, no match returns `null`.
- Filter session/poll pause: `editing || text.length > 0`.

## API Design

- `matchAgentByName(name, query): boolean`
- `findMatchPositions(name, query): number[] | null`
- `filterAgents(agents, query): AgentInfo[]`
- `resolveConsoleKeyAction` receives `filterActive`, returns `open-filter` only for `/` in list focus without an active query, and returns `clear-filter` for Esc in list focus with an active query. `'filter'` focus returns `noop`; the controlled input owns printable text, Enter, and Esc.
- `AgentListPane` receives the already-filtered agents plus total count, query, and editing state; it does not reorder agents.

No external API, authentication, storage, or new dependency is introduced.

## Component Breakdown

- `filter/agentFilter.ts`: pure case-insensitive substring logic.
- `ConsoleApp.tsx`: owns state; derives visible agents; validates selection; intercepts `'filter'` before global shortcuts; resumes with immediate refresh after Esc-clear.
- `consoleKeyRouting.ts`: exposes tested open/clear/no-op transitions while preserving list/detail/input actions.
- `AgentListPane.tsx`: renders the input/confirmed chip, counts, empty result, highlighted clipped names, filtered scroll clamping, and filtered more indicators.
- `ConsoleContext.tsx`: pauses agent/channel polling for any active text-entry/filter session.
- Footer/key hints: advertises `/ filter` only in the relevant list state and explains clear behavior when active.

## Design Decisions

- Substring matching is chosen for deterministic behavior and minimum complexity; fuzzy/subsequence and ranking are rejected.
- Parent-owned filter state prevents list, preview, and navigation from observing different arrays.
- A frozen filtered snapshot prevents polling-induced row movement; Esc both clears and immediately refreshes.
- Confirmed `/` is a no-op and editing `/` is literal, avoiding implicit destructive query replacement.
- Filtering composes over source order and contains no pin-specific logic.

## Non-Functional Requirements

- Filtering is linear in agent/name size and runs in-process.
- New pure logic and routing branches require 100% statement/branch/function/line coverage.
- Name clipping counts every rendered character while preserving highlight spans and existing row chrome/channel widths.
- Existing error handling and console shortcuts remain reliable; filter text is local ephemeral UI state and creates no security boundary.

## Design Review

Reviewed 2026-08-16 against every requirement and the current console implementation. Parent ownership covers selection, navigation, preview, footer, and polling; the dedicated focus state plus pre-global interception guarantees command characters remain text. Alternatives rejected are pane-local state (incoherent consumers), a modal (hides incremental results), and always-on entry (shortcut ambiguity). The incoming array remains the only ordering authority, so parallel pin behavior composes without feature coupling. No design gaps remain.
72 changes: 72 additions & 0 deletions docs/ai/implementation/2026-08-16-feature-agent-list-filter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
---
phase: implementation
title: Implementation Guide
description: Technical implementation notes, patterns, and code guidelines
---

# Agent Name Filter Implementation

## Development Setup
**How do we get started?**

- Use the repository Node/npm workspace with existing dependencies.
- Run focused Vitest from the repository root and CLI lint/build through the package scripts.
- No configuration, migration, or new dependency is required.

## Code Structure
**How is the code organized?**

- `packages/cli/src/tui/console/filter/agentFilter.ts`: pure name-filter operations.
- `packages/cli/src/__tests__/tui/console/filter/agentFilter.test.ts`: behavior and coverage contract.
- `packages/cli/src/tui/console/ConsoleApp.tsx`: owns query/focus state, derives the visible ordered array, keeps selection coherent, routes navigation, and drives immediate refresh on clear.
- `packages/cli/src/tui/console/AgentListPane.tsx`: renders inline editing/confirmed state, counts, no-match messaging, highlights, remote markers, and clamped filtered scrolling.
- `packages/cli/src/tui/console/state/ConsoleContext.tsx`: pauses agent and channel polling for message entry or a filter session.
- `packages/cli/src/tui/console/HelpPane.tsx` and `StatusFooter.tsx`: advertise `/ filter` normally and `Esc clear filter` during a session.

## Implementation Notes
**Key technical details to remember:**

### Core Features

- Task 1.1: case-insensitive substring matching uses plain `toLowerCase()`, returns every non-overlapping occurrence range, preserves arbitrary input order, and returns the original array for an empty query.
- Task 1.2: `ConsoleFocus` includes `'filter'`; the pure router opens only from an unfiltered list, clears only an active list filter on Esc, treats confirmed `/` as a no-op, and leaves filter-focus keystrokes to the controlled input.
- Task 2.1: the shell derives `visibleAgents` with `filterAgents`, uses it for selection and navigation, pauses both polling subscriptions while editing or applied, and clears with an immediate `refresh()`.
- Task 2.2: the list renders `(matched/total)`, a live `TextInput`, a confirmed indicator, all visible match spans in bold, filtered empty state, channel markers, and scroll clamping based on the filtered length.
- Task 2.3: help includes `/` and the footer suppresses command hints in favor of `Esc clear filter` for the whole session.

### Patterns & Best Practices
- Keep matching pure and dependency-free.
- Treat the received agent order as authoritative; never sort or partition.
- Drive each behavior through a failing focused test before production code.

## Integration Points
**How do pieces connect?**

- The filter module consumes `AgentInfo[]`; `ConsoleAppShell` composes it over the received order without sorting or pin-specific logic.
- Selection and navigation share `visibleAgents`; preview resolution continues through the selected name against the frozen source snapshot.
- No database, external API, or third-party integration is involved.

## Error Handling
**How do we handle failures?**

- No exceptions or logging are introduced. A non-match is represented as `null` positions or an omitted agent.

## Performance Considerations
**How do we keep it fast?**

- Matching is linear over the received array and names. Empty query avoids allocation by returning the input array.

## Security Notes
**What security measures are in place?**

- Query text is ephemeral local UI state and is not executed or persisted. No authentication, encryption, or secret handling changes.

## Validation

- Focused integration/unit suite: 6 files, 35 tests passed.
- Pure filter coverage: 100% statements, branches, functions, and lines.
- Key-routing coverage: 100% statements, branches, functions, and lines.
- CLI suite: 83 files, 1001 tests passed.
- CLI lint: exit 0 with five pre-existing unused-catch warnings outside this feature.
- CLI build: SWC and TypeScript declaration generation completed successfully.
- Feature docs lint: all configured docs and worktree checks passed.
88 changes: 88 additions & 0 deletions docs/ai/planning/2026-08-16-feature-agent-list-filter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
---
phase: planning
title: Agent Name Filter Implementation Plan
description: Ordered TDD tasks for inline console name filtering
---

# Agent Name Filter Implementation Plan

## Milestones

- [x] Milestone 1: Pure substring semantics and key-state transitions are test-driven and complete.
- [x] Milestone 2: Console state, polling, navigation, and list rendering integrate over one ordered filtered array.
- [x] Milestone 3: Edge-case coverage, docs, lint, full tests, and review are complete.

## Task Breakdown

### Phase 1: Foundation

- [x] Task 1.1 — Pure filter logic (TDD)
- Outcome: add `matchAgentByName`, `findMatchPositions`, and `filterAgents` with substring-only semantics, basic Unicode folding, all-occurrence positions, identity-on-empty, and preserved order.
- Dependencies: none; no new packages.
- Evidence: focused failing-then-passing unit tests and 100% module coverage.
- Scenarios: pure filter logic section of the testing strategy.
- [x] Task 1.2 — Filter key transitions (TDD)
- Outcome: add filter focus plus router actions for open, clear, active-filter slash no-op, and unchanged Esc/detail/input behavior.
- Dependencies: Task 1.1 only for shared terminology.
- Evidence: focused routing tests with 100% new-branch coverage.
- Scenarios: routing/Esc matrix and printable-command isolation.

### Phase 2: Core Integration

- [x] Task 2.1 — Shell state, selection, navigation, and polling
- Outcome: parent-owned query, filtered refs, selection fallback/null/clear behavior, filtered j/k navigation, pre-global editing interception, paused poll through editing/confirmed states, and immediate refresh on clear.
- Dependencies: Tasks 1.1–1.2.
- Evidence: ConsoleApp/ConsoleContext interaction tests and existing regressions.
- Scenarios: selection rules, polling lifecycle, active slash no-op, incidental refresh stability, pin-agnostic ordering.
- [x] Task 2.2 — Inline list rendering
- Outcome: TextInput under title, confirmed chip, `(matched/total)`, no-match state, highlighted clipped substrings, remote marker preservation, and filtered scroll clamp/more indicators.
- Dependencies: Task 2.1 supplies props and visible order.
- Evidence: AgentListPane render tests covering width, count, highlight, error precedence, and narrow/widen scroll transitions.
- Scenarios: list rendering/selection section and long-query/manual accessibility checks.
- [x] Task 2.3 — Footer and help affordances
- Outcome: `/ filter` appears in list help/footer; active state advertises Esc-clear without leaking editing keystrokes.
- Dependencies: Task 2.1 filter state.
- Evidence: HelpPane/StatusFooter tests and snapshots/text assertions.

### Phase 3: Verification & Polish

- [x] Task 3.1 — Reconcile implementation and lifecycle docs
- Outcome: implementation notes match code; completed plan tasks and test checkboxes carry fresh evidence.
- Dependencies: all implementation tasks.
- Evidence: feature lint and reviewed diffs.
- [x] Task 3.2 — Full quality gates
- Outcome: focused coverage, CLI lint/build/test, repository-appropriate regression suite, and manual rendering smoke checks pass.
- Dependencies: Task 3.1.
- Evidence: fresh command output recorded in testing/implementation docs.
- [x] Task 3.3 — Final review and publication
- Outcome: holistic review finds no blockers; commits are scoped; branch is pushed and PR is merged-ready.
- Dependencies: Task 3.2.
- Evidence: clean status, reviewed commit range, PR checks, and feature lint.

## Dependencies

Pure semantics precede shell integration; shell ownership precedes pane/footer wiring; implementation tasks are followed immediately by planning reconciliation. The parallel pin feature is not a code dependency: filtering consumes the received order and adds no pin-specific logic. Optional task tracing is unavailable (`npx ai-devkit@latest task list --name agent-list-filter --json` reports unknown command), so docs and commits provide progress traceability.

## Timeline & Estimates

- Foundation: small, 1–2 focused implementation checkpoints.
- Core integration: medium, 2–3 checkpoints with component/hook tests.
- Verification and review: medium, driven by regressions and coverage findings.
- Target: complete in the current lifecycle session; no date-based rollout or migration is required.

## Risks & Mitigation

- Global shortcuts leak while typing: intercept filter focus before all global handlers and test dangerous printable keys.
- Selection/preview disagree: derive and route through one `visibleAgents` array in the shell.
- Stale scroll after narrowing: clamp offset independently against filtered length and capacity.
- Poll resumes without fresh data: Esc-clear explicitly invokes `refresh` after unpausing.
- Highlight clipping breaks row width: split only the clipped name and include all row chrome/channel columns in width tests.
- Parallel pin assumptions creep in: assert preserved arbitrary input order and reject all sorting/partition code.

## Resources Needed

Existing React/Ink, `ink-text-input`, Vitest, console fixtures, lifecycle docs, and repository scripts only. No new services, dependencies, migrations, or additional agents are required.

## Progress Summary

Tasks 1.1–3.3 and all milestones are complete. The shell owns one substring-filtered ordered array for selection, navigation, preview, and rendering; provider polling pauses throughout editing and confirmed-filter states; list/footer/help rendering and edge cases are covered. Fresh focused coverage is 100% for matcher and routing, 35 focused tests pass, and the full CLI test/lint/build gates pass. Final review found no blockers, the branch was synchronized with `origin/main` without a rebase, and PR #168 was opened for review. No scope changes or blockers were discovered.
63 changes: 63 additions & 0 deletions docs/ai/requirements/2026-08-16-feature-agent-list-filter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
phase: requirements
title: Agent Name Filter Requirements
description: Inline name filtering for the console agent list
---

# Agent Name Filter Requirements

## Problem Statement

Operators with many running agents must currently move through the console agent list one row at a time. They need a predictable keyboard-first way to narrow the existing ordered list by agent name without disrupting selection, preview coherence, or live text entry.

## Goals & Objectives

- Add a vim-style `/` inline name filter to the agent list.
- Live-filter by case-insensitive substring while preserving the input array order.
- Keep selection, scrolling, preview, channel markers, and list counts coherent with the visible result.
- Pause polling for the entire filter session so the snapshot does not shift under the operator.
- Make the matcher and routing behavior independently testable with 100% coverage.

Non-goals:

- Fuzzy, subsequence, prefix-only, ranked, status, type, project, or pin-aware filtering.
- New dependencies, subprocess search, locale-aware grapheme matching, or persistence across console launches.
- Changing the ordering or partition semantics supplied by the agent source.

## User Stories & Use Cases

- As an operator in list focus, I press `/` with no active filter to edit a name query inline.
- As an operator editing a query, every printable key—including `/`, `j`, `k`, `v`, `i`, `m`, and `q`—is literal text and never a console command.
- As an operator, I press Enter to confirm the current query and browse the frozen filtered snapshot.
- As an operator, I press Esc while editing or with a confirmed filter to clear it, retain the current selection when possible, resume polling, and refresh immediately.
- As an operator, I see `(matched/total)`, bold matching name substrings, an active-filter indicator after confirmation, and `No agents match "query"` for an empty result.
- As an operator, pressing `/` with a confirmed filter is a no-op; I clear with Esc before starting another query.

## Success Criteria

- Matching uses exactly `name.toLowerCase().includes(query.toLowerCase())`; empty query returns the original array by identity and matching preserves source order.
- Match positions support bolding every non-overlapping occurrence and cover basic Unicode case folding such as `Ä`/`ä`.
- Filtering out the selected agent selects the first visible agent; no matches select `null`; clearing preserves the current selection if it exists in the full list.
- Navigation, preview lookup, scroll bounds, and more indicators use the filtered array. Narrowing and widening never leave a stale scroll offset.
- The input is rendered under `AGENTS` with placeholder `Filter by name…`; confirmed state displays an indicator and `(matched/total)` is correct.
- Existing error precedence remains: an error with an empty source list wins over the filter-empty state. Remote channel markers remain intact.
- Polling is paused while editing or while a non-empty filter is applied. Esc-clear resumes polling and triggers an immediate refresh.
- Existing detail, message input, modal pane, shortcut, and Esc behavior is unchanged outside the filter session.
- Targeted and full CLI tests pass; all new pure filter logic and routing branches have 100% coverage.

## Constraints & Assumptions

- State belongs in `ConsoleAppShell`, because routing, navigation, selection, preview, footer hints, and polling all depend on it.
- A filter session is active while the editor is open or a non-empty query is applied. An empty confirmed query is equivalent to no active filter.
- `toLowerCase()` code-point folding is sufficient for the MVP.
- The feature consumes whatever ordered `AgentInfo[]` it receives and contains zero pin-specific sorting or partition logic.
- Existing `ink-text-input` patterns and repository test tooling are reused; no dependencies are added.
- Query text may exceed pane width; the established input clipping/scrolling behavior is acceptable.

## Questions & Open Items

None. Matcher, key bindings, selection, polling, rendering, Unicode scope, and parallel pin composition are binding user decisions.

## Requirements Review

Reviewed 2026-08-16 against the requirements template and verified console architecture. The problem, users, goals, non-goals, workflows, measurable acceptance criteria, constraints, validation, and rollout scope are complete. Alternatives considered were inline, modal, and always-on filtering; inline `/` is accepted because it preserves incremental list context while providing an explicit text-entry boundary. No material gaps or open questions remain.
Loading
Loading