first pass of merging in warp (doesn't build)

This commit is contained in:
Ryan Ward
2026-07-01 16:08:58 -05:00
parent 2f64909469
commit 4770ac06b5
3662 changed files with 414574 additions and 89772 deletions
@@ -0,0 +1,71 @@
# Auth Secret Deletion From Selector — Product Spec
## Summary
Users can delete Warp-managed harness auth secrets directly from the auth secret selector chip menu. The delete affordance appears as a right-aligned `X` button on each existing secret row, opens a confirmation modal, removes the secret from the server after confirmation, updates the menu state, and confirms success or failure with a toast.
## Figma
Figma: none provided. The prompt includes screenshots of the auth selector delete affordance and the requested destructive confirmation modal layout.
## Behavior
1. The feature applies to the auth secret selector chip menu shown for non-Oz cloud harnesses after auth-secret FTUX has been completed.
2. When the auth secret selector chip menu opens, each existing Warp-managed secret row shows:
- The secret name on the left.
- A right-aligned `X` button on the same row.
- Hover, selected, spacing, icon, and text styling consistent with existing Warp menus.
3. The `X` button appears only on existing managed-secret rows. It does not appear on:
- The header row.
- The "Inherit key from environment" row.
- Loading or error placeholder rows.
- The "New" row.
- Secret-type rows in the "New" sidecar submenu.
4. Selecting the secret row outside the `X` button preserves current behavior: the row becomes the selected auth secret for the active harness, the per-harness selection is persisted, and the menu closes.
5. Selecting the `X` button opens a confirmation modal for that specific secret. It must not select the secret first, must not open the "New" sidecar, and must not accidentally dispatch the row's normal select action.
6. The confirmation modal shows:
- Title: "Delete secret".
- Description: "Are you sure you want to delete {SECRET_NAME}? This action cannot be undone. Any agents or environments referencing this secret will no longer have access to it."
- Buttons: "Cancel" and "Delete".
- Layout and destructive styling consistent with existing Warp confirmation dialogs and the provided mock.
7. Choosing "Cancel", dismissing the modal, or otherwise closing it leaves the secret untouched and does not start a delete request. Choosing "Delete" starts deletion for the secret named in the modal.
8. Deletion is server-backed. A secret is considered deleted only after the server delete request succeeds. The UI must not present deletion as successful before the server confirms it.
9. While deletion is pending, the user should not be able to fire duplicate delete requests for the same harness-owned secret from the same open menu. Pending delete state must stay scoped to the deletion target rather than every same-named secret. Acceptable treatments include disabling the `X` button for that row, closing the menu, or otherwise preventing a second click.
10. On successful deletion:
- The deleted secret disappears from the selector menu the next time the menu is shown, and ideally immediately if the menu remains open.
- A success toast is shown.
- If the deleted secret was currently selected for the active harness, the selected auth secret is cleared and the chip falls back to the no-secret/inherit label.
- Any persisted last-selected secret for the deleted harness is cleared if it points to the deleted secret, even if the user changes active harnesses before the server response arrives.
11. On failed deletion:
- The secret remains available in the selector menu.
- The current selected secret does not change.
- A failure toast is shown.
- The user can retry deletion after the failure.
12. If the deleted secret is selected by another visible selector instance, that selector should refresh when it observes updated harness secret state. It must not keep showing a deleted secret as a valid selectable item after the secret list is refreshed.
13. If the secret list is stale and the server reports that the secret no longer exists, the delete attempt is treated as a failure unless the server explicitly reports the delete as successful. The failure toast should use the server/user-facing error text when available.
14. If the user is offline or unauthenticated when pressing "Delete" in the confirmation modal, the delete fails and shows a failure toast. The menu must not remove the secret locally unless a later server response confirms deletion.
15. The success and failure toasts are ephemeral, consistent with nearby app toasts, and do not require a custom action button.
16. Keyboard behavior must remain usable:
- Existing keyboard navigation for selecting a secret row remains unchanged.
- Adding the `X` button must not make the menu trap focus or prevent Escape/close behavior.
- If the `X` button is reachable by keyboard, it must have an accessible label such as "Delete API key".
17. The menu layout remains stable for long secret names. Long names should truncate or shrink according to existing menu behavior rather than overlapping the right-aligned `X` button.
18. The feature does not add editing, renaming, creating, or bulk-deleting secrets. It only deletes one existing secret at a time from the auth selector menu.
@@ -0,0 +1,175 @@
# Auth Secret Deletion From Selector — Tech Spec
## Context
This spec implements the behavior in `PRODUCT.md` for the ambient cloud-mode auth selector chip.
Relevant current code:
- `app/src/terminal/view/ambient_agent/auth_secret_selector.rs:61` defines `AuthSecretSelectorAction`; it currently supports toggling the menu, selecting a secret, clearing/inheriting, opening the "New" sidecar, and selecting a new-secret type.
- `app/src/terminal/view/ambient_agent/auth_secret_selector.rs:147` subscribes the selector to `HarnessAvailabilityModel` auth-secret load/create/fetch-failure events and refreshes the menu/button.
- `app/src/terminal/view/ambient_agent/auth_secret_selector.rs:389` builds the menu. Loaded secrets are rendered as `MenuItem::Item` rows with `SelectSecret(name)` actions; the "New" row is separate and opens the sidecar.
- `app/src/terminal/view/ambient_agent/auth_secret_selector.rs:498` handles selector actions and persists selected/cleared secrets in `CloudAgentSettings.last_selected_auth_secret`.
- `app/src/ai/harness_availability.rs:55` stores per-harness auth secret fetch state as `AuthSecretFetchState::Loaded(Vec<AuthSecretEntry>)`.
- `app/src/ai/harness_availability.rs:184` fetches harness auth secrets from `ManagedSecretsClient::list_harness_auth_secrets`.
- `app/src/ai/harness_availability.rs:250` already centralizes secret creation through `ManagedSecretManager`, updates the local cache, and emits create/failure events.
- `crates/managed_secrets/src/manager.rs:87` exposes `ManagedSecretManager::delete_secret(owner, name)`.
- `app/src/server/server_api/managed_secrets.rs:132` wires `delete_managed_secret` to the server GraphQL mutation.
- `app/src/menu.rs:394` defines reusable `MenuItemFields`. It already has right-side label/icon rendering, but right-side icons are decorative and do not dispatch a separate action from the row.
- Existing destructive confirmation dialogs such as `app/src/settings_view/delete_environment_confirmation_dialog.rs` and `app/src/settings_view/mcp_servers/destructive_mcp_confirmation_dialog.rs` establish the preferred `Dialog` + `DangerPrimaryTheme` pattern for the new modal.
The current `AuthSecretEntry` contains only `name`, but server `ManagedSecret` results include `owner`. Deleting team-owned secrets correctly requires preserving enough owner information to call `delete_secret` with `SecretOwner::Team { team_uid }` instead of always assuming `CurrentUser`.
## Proposed Changes
1. Extend auth-secret cache entries with owner metadata.
Update `AuthSecretEntry` in `app/src/ai/harness_availability.rs` to include `owner: SecretOwner`. Convert `warp_graphql::object::Space` from fetched/created `ManagedSecret` values into `SecretOwner`:
- `SpaceType::User` -> `SecretOwner::CurrentUser`
- `SpaceType::Team` -> `SecretOwner::Team { team_uid: space.uid.into_inner() }`
This keeps deletion ownership close to the existing fetch/create cache and avoids guessing owner at click time.
2. Add deletion to `HarnessAvailabilityModel`.
Add a method similar to `create_auth_secret`:
```rust
pub fn delete_auth_secret(
&mut self,
harness: Harness,
name: String,
owner: SecretOwner,
ctx: &mut ModelContext<Self>,
)
```
The method should call `ManagedSecretManager::delete_secret(owner, name.clone())`. On success, remove the matching owner/name entry from `AuthSecretFetchState::Loaded` for that harness and emit a new event. On failure, leave the cache unchanged, report the error, and emit a failure event with the harness/name/owner/error.
Add events:
- `AuthSecretDeleted { harness, name, owner }`
- `AuthSecretDeletionFailed { harness, name, owner, error }`
Existing subscribers that only care about menu freshness should refresh on `AuthSecretDeleted`; subscribers that do not care should explicitly ignore both new events so match exhaustiveness remains clear.
3. Add a selector action for deleting a secret.
Extend `AuthSecretSelectorAction` with enough data to delete the row, for example:
```rust
DeleteSecret {
name: String,
owner: SecretOwner,
}
```
Track pending deletes in `AuthSecretSelector` with a small set keyed by `(harness, name, owner)` or by a stable local row id if one is introduced. This satisfies PRODUCT behavior 9 and avoids duplicate delete requests from the same menu.
4. Render the right-aligned `X` affordance using the menu system's existing styling.
Preferred implementation: extend `MenuItemFields` with an optional right-side action, keeping the visual rendering path for `with_right_side_icon(Icon::X)` consistent with other menus while allowing the right icon to dispatch a different action than the row.
The right-side action should:
- Stop mouse event propagation so clicking `X` does not also dispatch `SelectSecret`.
- Close the menu or disable the row while the delete request is pending.
- Preserve the existing row action when the user clicks anywhere outside the `X`.
If changing `MenuItemFields` is too broad during implementation, an acceptable alternative is a custom-label row in `auth_secret_selector.rs` that composes the existing text/icon primitives and explicitly separates row-click from `X` click. Prefer the reusable menu extension only if it stays small and does not alter existing right-side icon behavior.
5. Add a dedicated confirmation dialog before deleting.
Add a small destructive dialog view under `app/src/terminal/view/ambient_agent/` that mirrors existing confirmation dialogs:
- Title: `Delete secret`
- Body: `Are you sure you want to delete {SECRET_NAME}? This action cannot be undone. Any agents or environments referencing this secret will no longer have access to it.`
- Buttons: `Cancel` and destructive `Delete`
- Dismiss-to-cancel behavior and centered overlay placement
The dialog should retain a captured deletion payload containing harness, secret name, and owner. This avoids deleting the wrong target if selector state changes while the modal is open.
6. Wire deletion action handling in `AuthSecretSelector`.
In the `DeleteSecret` branch:
- Read the active harness from `AmbientAgentViewModel`.
- Close the selector menu/sidecar.
- Show the confirmation dialog with the captured deletion payload.
On confirmation:
- Mark the row pending.
- Call `HarnessAvailabilityModel::delete_auth_secret(harness, name, owner, ctx)`.
- Refresh the menu into a disabled pending state.
On cancel or dismiss:
- Hide the dialog.
- Leave pending-delete state untouched and do not call the server-backed delete API.
In the selector's `HarnessAvailabilityModel` subscription:
- On `AuthSecretDeleted`, clear pending state for the matching `(harness, name, owner)` target and show a success toast when this selector initiated that exact deletion.
- Always remove `CloudAgentSettings.last_selected_auth_secret[harness.config_name()]` if it equals the deleted name, even when the user has switched to another active harness before the server responds.
- Only when the deleted harness is still active, clear `AmbientAgentViewModel::selected_harness_auth_secret_name()` if it matches the deleted name and refresh the visible menu/button state.
- On `AuthSecretDeletionFailed`, clear pending state for the matching `(harness, name, owner)` target, refresh the visible menu only when that harness is active, and show a failure toast containing the user-facing error when available.
Use `DismissibleToast::success(...)` and `DismissibleToast::error(...)` via `ToastStack::add_ephemeral_toast`, matching nearby toast patterns.
7. Keep adjacent auth-secret surfaces consistent.
`AuthSecretFtuxDropdown`, orchestration pickers, and model selector subscribers must compile with the new deletion events. They can ignore failure events and refresh on success where they render auth secret lists. This ensures a secret deleted from the chip no longer appears in other already-mounted selectors after the model emits.
## Testing and Validation
No new helper-only unit tests are retained in this implementation pass. The following behaviors remain suitable for future targeted coverage if higher-value seams are introduced:
1. Cache update behavior for PRODUCT behaviors 8, 10, and 11:
- Successful deletion removes only the matching auth secret from the loaded list and emits `AuthSecretDeleted`.
- Failed deletion leaves the list unchanged and emits `AuthSecretDeletionFailed`.
- Team-owned entries call delete with `SecretOwner::Team`, user-owned entries call delete with `SecretOwner::CurrentUser`.
2. Selector and confirmation routing for PRODUCT behaviors 4, 5, 7, 9, and 10:
- Clicking/selecting the row still selects the secret.
- Dispatching the delete affordance opens the confirmation dialog without deleting immediately.
- Cancelling or dismissing the confirmation dialog does not start deletion.
- Confirming deletion transitions into the existing pending-delete flow.
- Duplicate confirmed delete dispatches for a pending secret are ignored.
- Deleting the currently selected secret clears the selected model value and persisted setting.
3. Menu rendering coverage if the reusable `MenuItemFields` right-side action API is introduced:
- Existing right-side icon rendering remains decorative when no right-side action is set.
- A right-side action dispatches independently from the row action.
4. Manual validation against the screenshot state, if revisited later:
- Open the auth selector chip menu with multiple secrets.
- Confirm only existing secret rows have a right-aligned `X`.
- Confirm the "New" row and sidecar rows do not show an `X`.
- Click `X` and verify the destructive confirmation modal appears with the specified copy.
- Cancel/dismiss the modal and verify the row remains untouched.
- Confirm deletion for an unselected secret and verify it disappears plus a success toast appears.
- Confirm deletion for the selected secret and verify the chip label falls back to no-secret/inherit state.
- Force a delete failure/offline state and verify the failure toast appears and the row remains.
5. Run focused checks:
```sh
cargo check -p warp
cargo fmt -- --check
```
Skip test execution for this implementation pass per request.
## Parallelization
Parallel agents are not recommended. The work is small and tightly coupled across one UI view, one shared model, and a small optional menu extension; splitting would increase merge risk around action/event names without materially reducing wall-clock time.
## Risks and Mitigations
- **Accidental row selection when clicking `X`:** stop event propagation on the `X` hit target and test the separate action path.
- **Deleting the wrong owner scope:** store `SecretOwner` on each fetched `AuthSecretEntry`; do not infer owner from the active workspace at click time.
- **Stale selected secret after deletion:** clear both `AmbientAgentViewModel` selection and `CloudAgentSettings.last_selected_auth_secret` when the deleted name matches the active harness selection.
- **Over-broad menu API changes:** keep any `MenuItemFields` addition opt-in so existing menus with right-side icons remain visually and behaviorally unchanged.