Files
kandev/docs/specs/ui/task-workspace-content-search.md
T

193 lines
10 KiB
Markdown

---
status: shipped
created: 2026-07-27
owner: kandev
---
# Task Workspace Content Search
## Why
People working inside a task need to find code by what it says, not only by its
file name. Opening a terminal and running a repository-specific search breaks
the workbench flow, and it is especially awkward when a task contains several
repositories.
## What
- On a task route, **Cmd+Shift+F** on macOS or **Ctrl+Shift+F** elsewhere opens
the command palette directly in workspace content-search mode.
- The shortcut works while a workbench editor or text input has focus and takes
precedence over that surface's local find shortcut. It prevents the browser's
default search action.
- The command palette visibly exposes **Commands**, **Files**, and **Contents**
as low-chrome text tabs beside the search field. The active mode uses a subtle
underline, and each mode's direct shortcut remains discoverable from its
tooltip without adding a separate selector row.
- **Files** and **Contents** are available only on the active task-detail route
when its selected session belongs to that task. Elsewhere the palette is
command-only, does not intercept either workspace-search shortcut, and
normalizes a previously open workspace-search palette back to **Commands**.
- Clicking a mode or pressing **Tab** / **Shift+Tab** switches among those modes
without discarding the current query. The existing **Cmd/Ctrl+Shift+K**
shortcut still opens file-name and path search directly.
- File-name and path search covers every repository materialized for the active
task. Multi-repository results use task-root-relative, repository-prefixed
paths so same-named files remain distinguishable and open in the correct
repository. The palette groups those matches by repository and shows paths
relative to each group.
- For both **Files** and **Contents**, a Git workspace root remains searchable
when initialized submodules add named repository scopes. Root matches retain
the empty repository identity while child matches retain their task-root-relative
scope names. A bare task directory containing sibling repositories is not
treated as an additional repository.
- Search covers every repository materialized for the active task session.
Tracked files and untracked, non-ignored files are eligible; ignored files,
directories, and workspace metadata are not.
- Matching is case-insensitive and fuzzy within one line. A contiguous match
ranks ahead of a non-contiguous subsequence match. Adjacency, word boundaries,
and shorter gaps improve a fuzzy match's rank.
- Each result shows its repository, repository-relative path, one-based line
number, and a single-line content preview. Matched characters are highlighted
in the preview.
- Results are grouped by repository. A repository's display label may be
shortened for readability, but selecting a result retains the repository's
raw transport identity.
- Selecting a result opens or activates that repository's file and places the
cursor at the result's one-based line and column. Both Monaco and CodeMirror
center the result line after the target file model is active, including when
an existing preview tab swaps from another cached file without remounting the
editor.
- The selected result line receives a one-shot whole-line highlight for about
1.2 seconds so the destination is easy to locate. Selecting the result again
restarts the highlight. Reduced-motion users see the same bounded highlight
without a fade animation.
- Empty-query, searching, no-match, unavailable-session, and failed-search
states are distinguishable without closing the palette.
- Search is bounded: a query contains at most 200 characters, each repository
returns at most 50 results, and files larger than the existing 10 MiB
workspace-file limit are skipped.
- Binary files, invalid UTF-8, unreadable files, paths that escape the
repository, and files removed during a search are skipped without failing
the entire query.
- Match ranges use UTF-16 offsets into the returned preview so browser
highlighting remains correct for non-BMP Unicode text.
- Search does not require an external executable such as ripgrep, so it behaves
consistently in local, container, remote, and Windows executors.
## API surface
The browser requests workspace content search over the session-scoped WebSocket
action:
```text
workspace.content.search
```
The request contains `session_id`, `query`, and an optional
`limit_per_repo`. The response contains ordered result records:
| Field | Meaning |
| ----------------- | ------------------------------------------------------------- |
| `repository_name` | Raw repository identity used by workspace file APIs |
| `path` | Repository-relative slash-separated path |
| `line` | One-based source line |
| `column` | One-based UTF-16 source column of the first matched character |
| `preview` | Searchable source line presented by the palette |
| `match_ranges` | Half-open UTF-16 ranges into `preview` |
The backend-to-agentctl HTTP transport exposes the same result shape. Workspace
file-name search keeps its legacy `files` array and additionally returns
structured `results` entries containing `repository_name` and the
task-root-relative `path`, allowing grouping-aware consumers to separate
repositories without parsing path strings.
## Permissions and isolation
- A request is authorized through the session ID before its execution
workspace is accessed.
- Only repositories attached to that session's task execution are searched.
- Repository-relative paths are resolved through the existing workspace path
containment rules; symlinks or traversal cannot expand search scope.
- Result data contains no absolute host path.
## Failure modes
- Without an active task session, the palette explains that content search is
unavailable and sends no request.
- A blank query clears prior results and sends no request.
- A query over the maximum length is rejected consistently rather than scanning
with a silently different value.
- If one eligible file disappears, becomes unreadable, or is invalid while the
search runs, other files and repositories still return results.
- If the execution cannot be recovered or the transport request fails, the
palette presents a retryable failure state and keeps the entered query.
- Cancellation stops outstanding repository work and stale responses do not
replace results for a newer query.
## Scenarios
- **GIVEN** a task editor has focus, **WHEN** the user presses
**Cmd/Ctrl+Shift+F**, **THEN** task content search opens and the browser or
editor find UI does not.
- **GIVEN** the palette is open with a query, **WHEN** the user clicks another
top-level mode or presses **Tab**, **THEN** the mode changes, the query remains,
and the input keeps focus.
- **GIVEN** the user leaves the active task workbench, **WHEN** the palette is
opened or a workspace-search shortcut is pressed, **THEN** only
**Commands** is offered and the workspace-search shortcut keeps its native
behavior.
- **GIVEN** a line containing an exact occurrence and another containing only a
fuzzy subsequence, **WHEN** both match the query, **THEN** the exact occurrence
ranks first.
- **GIVEN** a task with two repositories containing the same search term,
**WHEN** the user searches, **THEN** both repositories have clearly separated
result groups with repository-relative paths.
- **GIVEN** a task with two repositories containing matching file names,
**WHEN** the user searches in **Files** mode, **THEN** matching paths from both
repositories appear with their repository prefixes.
- **GIVEN** a Git workspace root and an initialized submodule both contain
matching file names, **WHEN** the user searches in **Files** mode, **THEN**
matches from the root and submodule appear in separate repository groups.
- **GIVEN** an initialized submodule path matches a file-search query, **WHEN**
the user searches in **Files** mode, **THEN** the parent repository's Gitlink
path is not returned as a file and real files inside the child scope remain
searchable.
- **GIVEN** a Git workspace root and an initialized submodule both contain a
matching text query, **WHEN** the user searches in **Contents** mode,
**THEN** results from both repository scopes appear.
- **GIVEN** a bare task directory contains multiple sibling repositories,
**WHEN** the user searches in **Files** or **Contents** mode, **THEN** only
those repositories contribute results and no duplicate task-root group appears.
- **GIVEN** two repositories with the same relative path, **WHEN** the user
selects the result from the second repository, **THEN** Kandev opens the
second repository's file at the returned line and column.
- **GIVEN** a result file was previously opened and its preview tab now shows a
different cached file, **WHEN** the user selects an off-screen content result
with **Enter**, **THEN** the target model becomes active, the returned line is
centered in the editor, and its one-shot line highlight appears and clears.
- **GIVEN** either Monaco or CodeMirror displays a selected content result,
**WHEN** the same result is selected again, **THEN** its one-shot line
highlight restarts without changing file contents.
- **GIVEN** an untracked non-ignored text file and an ignored text file,
**WHEN** both contain the query, **THEN** only the untracked non-ignored file
appears.
- **GIVEN** a preview containing non-BMP Unicode before and inside a match,
**WHEN** results render, **THEN** the supplied match ranges highlight the
intended characters without splitting a surrogate pair.
- **GIVEN** a search is followed quickly by another query, **WHEN** the first
response arrives last, **THEN** the palette retains the second query's
results.
## Out of scope
- Replacing file-name and path search.
- Searching tasks other than the task currently open.
- Searching Git history, ignored files, generated artifacts, agent messages,
terminal output, or files outside attached repositories.
- Regular-expression, whole-word, case-sensitive, replace, or persistent search
options.
- Persistent editor markers or highlighting every fuzzy-match character in the
source line.
- A persistent full-text index or external search service.