Initial public release of Warp.
Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
# Don't auto-add indexed repos to Directory tab colors
|
||||
Linear: [APP-4115](https://linear.app/warpdotdev/issue/APP-4115/dont-auto-add-indexed-repos-including-worktrees-to-directory-tab)
|
||||
## Summary
|
||||
The Directory tab colors list in Settings > Appearance should stop auto-populating itself from the set of indexed codebases. Instead, the user explicitly chooses which directories to color, assisted by a searchable `Add directory color` dropdown when known repos are available. If there are no candidate repos left to show, the control falls back to a plain `Add directory color` button that opens the native folder picker directly.
|
||||
## Problem
|
||||
Today, every time `CodebaseIndexManager` emits a sync-state update, Warp merges all currently-indexed codebase paths into the user's `appearance.tabs.directory_tab_colors` setting as `Unassigned` entries. This produces two user-visible problems reported in `#feedback-app`:
|
||||
1. Worktrees under paths like `~/.warp-dev/worktrees/warp-internal/...` get indexed as codebases and then silently added to the colors list, flooding the settings panel with entries the user never wanted to manage.
|
||||
2. The Directory tab colors list grows to reflect indexing state rather than intentional user configuration, making the settings panel noisy and hard to use.
|
||||
The relevant subscription lives in `app/src/workspace/view.rs:2775-2790`. The existing folder-picker button lives in `app/src/settings_view/appearance_page.rs:4632-4671`.
|
||||
## Goals
|
||||
- Stop auto-adding any indexed codebase (including worktrees) to `directory_tab_colors`.
|
||||
- Make known repos one click to add when candidates are available.
|
||||
- Preserve a direct folder-picker path when there are no known repos left to offer.
|
||||
## Non-goals
|
||||
- Migrating or cleaning up entries that were already auto-added on prior app versions. Users remove clutter manually via the existing per-row X button.
|
||||
- Changing how `color_for_directory` resolves the active tab color for any working directory. Longest-prefix matching is unchanged.
|
||||
- Changing the set of ANSI color dots, the Suppressed state, or the overall layout of the Directory tab colors settings card.
|
||||
- Hiding worktrees from the candidate set. Worktrees remain selectable so users can intentionally color them.
|
||||
- Introducing a new setting to toggle the auto-add behavior.
|
||||
## Figma / design references
|
||||
Figma: none provided. Keep the searchable dropdown behavior aligned with the `New worktree config` sidecar in `app/src/workspace/view.rs:8143-8301`. Keep the fallback button aligned with the prior `Add directory color` button treatment in `app/src/settings_view/appearance_page.rs:4674-4720`.
|
||||
## User experience
|
||||
### Removal of auto-add
|
||||
- Newly indexed repos (including worktrees) never appear in `appearance.tabs.directory_tab_colors` on their own.
|
||||
- Existing indexed or ambiently-detected repos are not added when the user opens Settings, switches tabs, starts new sessions, or triggers reindexing.
|
||||
- Previously auto-added entries remain in the setting until the user removes them via the X button. The visible list and X-button behavior in `appearance_page.rs:4674-4815` is unchanged.
|
||||
### `Add directory color` control
|
||||
The header control is conditional:
|
||||
- If candidate repos exist, render the searchable `Add directory color` dropdown.
|
||||
- If no candidate repos exist, render a plain `Add directory color` action button with the leading plus icon.
|
||||
Candidate set:
|
||||
- The candidate set is the union of indexed codebase paths from `CodebaseIndexManager::get_codebase_paths` and persisted workspace paths from `PersistedWorkspace::workspaces()`.
|
||||
- Paths are deduped by canonical form.
|
||||
- Linked worktrees are kept in the list.
|
||||
- Entries whose canonical path is already a key in `directory_tab_colors` with a color other than `Suppressed` are filtered out.
|
||||
- Entries keyed as `Suppressed` remain selectable so users can undo a prior removal.
|
||||
- Entries whose path no longer exists on disk are filtered out.
|
||||
- Remaining entries are sorted alphabetically by canonical path, matching the visible colors list.
|
||||
When candidates exist:
|
||||
- Each dropdown row shows the user-friendly path, using the same `user_friendly_path` helper used by the list below.
|
||||
- The dropdown search field filters rows by case-insensitive substring match on the full path.
|
||||
- A pinned footer labeled `+ Add directory…` remains visible at the bottom of the dropdown.
|
||||
- Selecting a row adds the corresponding canonical path to `directory_tab_colors` with `DirectoryTabColor::Unassigned`.
|
||||
- Clicking the pinned footer closes the dropdown and opens the native folder picker.
|
||||
When no candidates exist:
|
||||
- Clicking the fallback button opens the native folder picker directly.
|
||||
- If the user cancels the folder picker, nothing changes.
|
||||
Interaction details and invariants:
|
||||
- Opening the dropdown or clicking the fallback button never mutates `directory_tab_colors` by itself. Mutation only happens on selecting a row or completing the folder picker.
|
||||
- The visible colors list below the header is rendered exactly as it is today: same row layout, same color dots, same X button, same alphabetical sort order.
|
||||
- The per-row color picker and X button continue to read/write through the existing `SetDefaultDirectoryTabColor` and `RemoveDefaultDirectoryTabColor` actions.
|
||||
- `RemoveDefaultDirectoryTabColor` continues to persist `Suppressed` rather than deleting the key.
|
||||
## Success criteria
|
||||
- Starting Warp fresh, opening and closing repos, triggering codebase indexing, or creating worktrees does **not** add any entries to `appearance.tabs.directory_tab_colors` in the TOML settings file.
|
||||
- When candidate repos exist, the `Add directory color` control is the searchable dropdown.
|
||||
- The dropdown lists indexed codebases and persisted workspaces that are not already keyed in `directory_tab_colors` with a non-`Suppressed` color. Entries are deduped by canonical path.
|
||||
- Worktrees (paths whose repository has an external gitdir) are included in the candidate set and can be added.
|
||||
- Selecting a dropdown row adds that path to `directory_tab_colors` as `Unassigned`. The row appears in the visible colors list below immediately.
|
||||
- The pinned `+ Add directory…` footer remains visible at the bottom of the dropdown and opens the native folder picker.
|
||||
- When every known repo is already present (with a non-`Suppressed` color), the control falls back to the plain `Add directory color` button and clicking it opens the native folder picker directly.
|
||||
- Entries that were auto-added by the old behavior on prior app versions are not removed, altered, or reordered by this change.
|
||||
- Longest-prefix matching in `DirectoryTabColors::color_for_directory` returns the same color for any given working directory that it returned before this change, given the same persisted configuration.
|
||||
## Validation
|
||||
- Unit tests on a helper that computes the candidate set, covering dedupe across indexed and persisted sources, filtering out keys present with non-`Suppressed` colors, keeping `Suppressed` keys, filtering out missing paths, and worktree inclusion.
|
||||
- Unit test confirming that handling a `CodebaseIndexManagerEvent::SyncStateUpdated` does not mutate `directory_tab_colors`.
|
||||
- Manual validation:
|
||||
1. With several worktrees under `~/.warp-dev/worktrees/warp-internal/...` indexed, confirm none appear in the Directory tab colors list on a fresh profile.
|
||||
2. Confirm that when candidate repos exist, `Add directory color` renders as the searchable dropdown and its list contains those worktrees.
|
||||
3. Select a repo from the dropdown, confirm it appears in the list below with the `Unassigned` dot selected.
|
||||
4. With all known repos added, confirm the control falls back to the plain button and clicking it opens the native folder picker.
|
||||
5. Pick a folder via either the dropdown footer or the fallback button, confirm it is added to the list just like the prior file-picker flow.
|
||||
- Visual check via `verify-ui-change-in-cloud` that the dropdown renders correctly when candidates exist and that the plain fallback button appears when there are none.
|
||||
## Open questions
|
||||
- None at spec creation.
|
||||
@@ -0,0 +1,101 @@
|
||||
# TECH — Don't auto-add indexed repos to Directory tab colors
|
||||
See also: `PRODUCT.md` in this directory.
|
||||
Linear: [APP-4115](https://linear.app/warpdotdev/issue/APP-4115/dont-auto-add-indexed-repos-including-worktrees-to-directory-tab).
|
||||
## Problem
|
||||
Two coupled code changes are needed to deliver the product behavior:
|
||||
1. Stop the `CodebaseIndexManager`-driven subscription that auto-populates `TabSettings.directory_tab_colors` with newly indexed paths.
|
||||
2. Make the `Add directory color` control conditional: render the existing searchable `FilterableDropdown` when candidate repos exist, and render a plain `ActionButton` that opens the folder picker when the candidate set is empty.
|
||||
## Relevant code
|
||||
- `app/src/workspace/view.rs:2775-2790` — the subscription that calls `DirectoryTabColors::merge_new_paths` on every `CodebaseIndexManagerEvent::SyncStateUpdated`.
|
||||
- `app/src/workspace/tab_settings.rs:168-243` — `DirectoryTabColor` / `DirectoryTabColors`, including `with_color` and `color_for_directory`.
|
||||
- `app/src/settings_view/appearance_page.rs:1371-1378` — construction of `DirectoryColorAddPicker`.
|
||||
- `app/src/settings_view/appearance_page.rs:2416-2425` — `handle_directory_color_add_picker_event`.
|
||||
- `app/src/settings_view/appearance_page.rs:4632-4671` — helper functions for adding a directory tab color directly and opening the folder picker.
|
||||
- `app/src/settings_view/appearance_page.rs:4674-4815` — `DirectoryTabColorsWidget`, delete buttons, and rendering of the colors list.
|
||||
- `app/src/settings_view/directory_color_add_picker.rs` — candidate computation, conditional rendering, and footer/button event emission.
|
||||
- `app/src/view_components/filterable_dropdown.rs` — existing searchable dropdown behavior and pinned footer support.
|
||||
- `app/src/ai/persisted_workspace.rs:714-721` — `PersistedWorkspace::workspaces()` iterator.
|
||||
- `crates/ai/src/index/full_source_code_embedding/manager.rs:556-558` — `get_codebase_paths`.
|
||||
- `crates/warp_util/src/path.rs:85-114` — `user_friendly_path` helper used in the visible list.
|
||||
## Current state
|
||||
- On startup and on every indexing sync-state update, the workspace view reads all `CodebaseIndexManager` paths and calls `DirectoryTabColors::merge_new_paths`, which inserts each canonical path into `directory_tab_colors` as `Unassigned` if not already present.
|
||||
- `DirectoryColorAddPicker` already wraps `FilterableDropdown` and computes the correct candidate set for the searchable known-repo flow.
|
||||
- The X button on each row dispatches `RemoveDefaultDirectoryTabColor`, which writes `DirectoryTabColor::Suppressed` rather than deleting the key.
|
||||
## Proposed changes
|
||||
### 1. Remove the auto-add subscription
|
||||
Delete the entire `if FeatureFlag::DirectoryTabColors.is_enabled() { ctx.subscribe_to_model(&CodebaseIndexManager::handle(ctx), …) }` block at `app/src/workspace/view.rs:2775-2790`. No replacement subscription is needed.
|
||||
Delete `DirectoryTabColors::merge_new_paths` and any tests or helpers whose only purpose was validating the old auto-add behavior.
|
||||
### 2. Keep `FilterableDropdown` and add a fallback button
|
||||
Keep `app/src/settings_view/directory_color_add_picker.rs` centered on the existing `FilterableDropdown` implementation. Add a second child view for the plain button and track whether the dropdown currently has any candidate rows.
|
||||
```rust path=null start=null
|
||||
pub struct DirectoryColorAddPicker {
|
||||
button: ViewHandle<ActionButton>,
|
||||
dropdown: ViewHandle<FilterableDropdown<DirectoryColorAddPickerAction>>,
|
||||
footer_mouse_state: MouseStateHandle,
|
||||
has_dropdown_items: bool,
|
||||
}
|
||||
```
|
||||
`DirectoryColorAddPicker::new`:
|
||||
- Creates the fallback `ActionButton::new("Add directory color", SecondaryTheme).with_icon(Icon::Plus)` and wires it to dispatch `DirectoryColorAddPickerAction::AddNewDirectory`.
|
||||
- Creates the existing `FilterableDropdown`, keeps its current searchable behavior, and preserves the pinned `+ Add directory…` footer.
|
||||
- Subscribes to `CodebaseIndexManager`, `PersistedWorkspace`, and `TabSettings` so the candidate list refreshes when any source changes.
|
||||
`refresh_items`:
|
||||
- Computes the candidate set using the existing pure helper.
|
||||
- Converts candidates into `DropdownItem<DirectoryColorAddPickerAction>` values and passes them to `dropdown.set_items(items, ctx)`.
|
||||
- Sets `has_dropdown_items = !items.is_empty()`.
|
||||
- If the list becomes empty, closes the dropdown so the view can switch cleanly to the fallback button.
|
||||
`render`:
|
||||
- If `has_dropdown_items` is true, render `ChildView::new(&self.dropdown)`.
|
||||
- Otherwise, render `ChildView::new(&self.button)`.
|
||||
`handle_action`:
|
||||
- `Select(path)` emits `DirectoryColorAddPickerEvent::Selected(path)` and lets `FilterableDropdown` close itself after dispatch.
|
||||
- `AddNewDirectory` closes the dropdown if needed and emits `DirectoryColorAddPickerEvent::RequestAddFromFilePicker`.
|
||||
### 3. Candidate-set helper
|
||||
Keep `compute_candidate_paths` as the pure helper that:
|
||||
- unions indexed and persisted paths,
|
||||
- canonicalizes keys the same way `DirectoryTabColors::with_color` does,
|
||||
- dedupes by canonical key,
|
||||
- filters out entries already present with a non-`Suppressed` color,
|
||||
- keeps `Suppressed` entries re-addable,
|
||||
- filters out missing paths,
|
||||
- sorts by canonical key.
|
||||
### 4. Appearance page wiring
|
||||
No appearance-page architectural change is needed beyond the existing `DirectoryColorAddPicker` integration:
|
||||
- `build_page` continues to construct the picker and subscribe to its events.
|
||||
- `handle_directory_color_add_picker_event` continues to map `Selected(path)` to `add_directory_tab_color_path(path, ctx)` and `RequestAddFromFilePicker` to `open_directory_tab_color_folder_picker(ctx)`.
|
||||
- The existing rebuild of `directory_tab_color_delete_buttons` and `color_picker_dot_states` in `handle_tab_settings_event` remains the source of truth for the visible list.
|
||||
### 5. `Suppressed` behavior
|
||||
No special-case behavior is needed beyond the existing helper and appearance-page add path:
|
||||
- If a candidate row corresponds to a `Suppressed` key, selecting it transitions that entry back to `Unassigned` through `with_color(path, DirectoryTabColor::Unassigned)`.
|
||||
- If the user removes a row and it still exists in the indexed/persisted candidate set, it will reappear in the dropdown.
|
||||
## End-to-end flow
|
||||
### Candidate repos exist
|
||||
1. `DirectoryColorAddPicker` renders the searchable dropdown.
|
||||
2. The user opens it, filters if needed, and either selects a row or clicks the pinned footer.
|
||||
3. Row selection emits `Selected(path)`.
|
||||
4. Footer click emits `RequestAddFromFilePicker`.
|
||||
### No candidate repos exist
|
||||
1. `DirectoryColorAddPicker` renders the plain `Add directory color` button.
|
||||
2. Clicking the button emits `RequestAddFromFilePicker` immediately.
|
||||
3. The native folder picker opens, and on success the selected path is added through the existing appearance-page helper.
|
||||
### Indexing sync state updates
|
||||
1. `CodebaseIndexManagerEvent::SyncStateUpdated` fires.
|
||||
2. `directory_tab_colors` is not modified.
|
||||
3. `DirectoryColorAddPicker` refreshes its candidate list and may switch between dropdown and button depending on whether any rows remain.
|
||||
## Risks and mitigations
|
||||
- **The control changes shape when the candidate count crosses zero.** This is intentional. The dropdown only adds value when there are known repos to show.
|
||||
- **`Suppressed` semantics become less obvious.** Keep the existing `Suppressed` write path unchanged so prefix-shadowing behavior remains stable.
|
||||
- **Stale dropdown state when candidates disappear.** Close the dropdown in `refresh_items` whenever the candidate list becomes empty.
|
||||
## Testing and validation
|
||||
- Unit tests on `compute_candidate_paths` covering dedupe, `Suppressed`, missing-path filtering, worktree inclusion, and sort order.
|
||||
- Regression test confirming that `CodebaseIndexManagerEvent::SyncStateUpdated` no longer mutates `directory_tab_colors`.
|
||||
- Manual validation of both control states:
|
||||
1. Dropdown shown when candidates exist.
|
||||
2. Fallback button shown when no candidates exist.
|
||||
3. Dropdown row selection adds an `Unassigned` entry.
|
||||
4. Dropdown footer opens the folder picker.
|
||||
5. Fallback button opens the folder picker.
|
||||
- `cargo check` and `cargo fmt --check`.
|
||||
- `verify-ui-change-in-cloud` for the final UI check.
|
||||
## Follow-ups
|
||||
None.
|
||||
Reference in New Issue
Block a user