Initial public release of Warp.

Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
David Stern
2026-04-28 08:43:33 -05:00
commit 0dbd3d567a
4982 changed files with 1431549 additions and 0 deletions
+143
View File
@@ -0,0 +1,143 @@
# Notification Toast & Mailbox UI Updates
## Summary
Update the notification toast and mailbox UI to match the new Figma designs. This covers layout, spacing, and content changes across both the floating toast notifications and the persistent mailbox dropdown. The changes introduce a branch name context row, line-clamped text with expand affordances, shared avatar rendering with vertical tabs, and various padding/spacing refinements.
## Problem
The current notification UI was built as a first pass behind the `hoa_notifications` feature flag. The toast and mailbox designs have since been refined with better information density (branch context), improved visual consistency with other surfaces (avatar rendering matching vertical tabs), and more polished spacing/sizing.
## Goals
- Match the Figma spec for both toast and mailbox UI
- Introduce branch name context so users can immediately see which branch a notification relates to
- Improve text density with character-count truncation and expand affordances
- Unify avatar rendering between notifications and vertical tabs
## Non-goals
- Inline approve/decline buttons on toasts (punted)
- Changes to notification data model beyond adding branch context
- Changes to notification timing, auto-dismiss, or sound behavior
- Changes to the notification mailbox's keyboard navigation or action dispatch
## Figma
- Toast: https://www.figma.com/design/CsBdBW4YoLgSAbr5eSkwV6/House-of-Agents?node-id=7411-88337&m=dev
- Mailbox: https://www.figma.com/design/CsBdBW4YoLgSAbr5eSkwV6/House-of-Agents?node-id=7411-91197&m=dev
## User Experience
### Toast
#### Container
- Width: 420px (was 360px).
- Padding: 12px uniform (was 10px vertical / 16px horizontal).
- Corner radius: 8px (was 6px).
- No drop shadow.
#### Branch name header row
- When a notification has branch context, show a row above the title containing:
- Left: git-branch icon (10×10) + branch name text (12px, sub_text color).
- Right: chevron icon (expand/collapse toggle, shown only when content is truncated).
- When a notification has no branch context, this row is omitted and the toast shows the title only (no timestamp — see below).
#### Title
- Truncated at 100 characters (collapsed) / 500 characters (expanded).
- Appends `…` when truncated.
#### Message / subtext
- Truncated at 100 characters (collapsed) / 500 characters (expanded).
- Appends `…` when truncated.
- The expand chevron (in the branch/title row) is only rendered when content is actually truncated.
#### Timestamp and unread dot
- Removed from the toast entirely.
(Toasts are inherently unread and short-lived, so these indicators add no value.)
#### Avatar
- Use the same avatar rendering system as vertical tabs (`render_pane_icon_with_status` with `IconWithStatusVariant`).
- This gives proper brand colors for CLI agents, `main_text_color` for Oz, and status badges with cutout rings.
- The notification's `NotificationCategory` (Complete/Request/Error) maps to the same status badge icons/colors.
#### Close button
- Positioned at the **top-left** corner of the toast, partially outside the bounds.
- Only visible on hover (current behavior preserved).
- Has a visible border on the circle.
#### Keybinding hint
- Still shown on the newest toast ("Open conversation" + keyboard shortcut).
- No change from current behavior.
#### Artifact buttons
- 48px left padding to align past the avatar column.
### Mailbox
#### Outer container
- Add 4px top padding before the header.
- No drop shadow.
#### Header
- Left side: "Notifications" text (14px, **regular** weight — was semibold).
- Right side: X close button.
- Padding: `8px vertical, 12px horizontal` (was 8px vertical, 16px left, 8px right).
#### Filter bar
- Button gap: 2px (was 4px).
- "All" filter now shows count: e.g. "All tabs (11)" instead of just "All Tabs".
- Bar padding: `12px vertical, 12px left, 6px right` (was 12px vertical, 16px horizontal).
#### Rich notification items (with branch context)
- Padding: 12px uniform (was 12px vertical, 16px horizontal).
- Branch row at top of the text column:
- Left: git-branch icon (10px) + branch name (12px, sub_text color).
- Right: timestamp (12px, disabled gray) + unread dot (when unread).
- Title: 14px semibold, truncated at 100 characters (collapsed) / 500 characters (expanded).
- Message: 14px regular, sub_text color, truncated at 100 characters (collapsed) / 500 characters (expanded).
- Artifact buttons: 48px left padding.
#### Simple notification items (without branch context)
- Padding: stays 12px vertical, 16px horizontal (unchanged).
- Title row: SpaceBetween with title (14px semibold) | timestamp + optional unread dot (current layout, unchanged).
- Title and message use the same 100-character collapsed / 500-character expanded truncation behavior as rich items.
- In the mailbox, simple items do not render an expand chevron; in toasts, the chevron appears in the title row when content is truncated.
#### Shared item changes (both layouts)
- Avatar: use vertical tabs `render_pane_icon_with_status` system.
- Timestamp font size: 12px (was 14px).
### Data model
- `NotificationItem` needs a new `branch: Option<String>` field.
- When present, items render using the "rich" layout (with branch row).
- When absent, items render using the "simple" layout (current style).
- The branch value must be threaded from the conversation or CLI session context when creating notifications.
## Success Criteria
1. Toast container is 420px wide with 12px uniform padding, 8px corner radius, and no drop shadow.
2. When a notification has branch context, the toast shows a branch name header row with git-branch icon, branch name, and chevron-right button when the title or message is truncated.
3. Toast title text is truncated at 100 characters when collapsed.
4. Toast message text is truncated at 100 characters with an expand chevron shown only when content is truncated. Clicking the chevron reveals the full message (up to 500 characters).
5. Toast does not display a timestamp or unread dot.
6. Toast close button is positioned at the top-left, partially outside the toast, visible only on hover, with a border.
7. Toast keybinding hint ("Open conversation" + shortcut) is still shown on the newest toast.
8. Toast artifact buttons have 48px left padding.
9. All toast and mailbox avatars use the vertical tabs `render_pane_icon_with_status` system with proper brand colors, Oz styling, and status badge cutout rings.
10. Mailbox outer container has 4px top padding and no drop shadow.
11. Mailbox header has regular-weight "Notifications" title on the left, close button on the right, padded at 8px vertical / 12px horizontal.
12. Mailbox filter bar has 2px button gap, the "All" filter shows a count, and padding is 12px vertical / 12px left / 6px right.
13. Mailbox rich items (with branch) use 12px uniform padding, show a branch row (with timestamp + unread dot on right), truncated title, truncated message, and 48px left-padded artifact buttons.
14. Mailbox simple items (without branch) keep the current layout with 12px vertical / 16px horizontal padding.
15. Timestamp font size is 12px in both mailbox item layouts.
## Validation
- Build the app and visually compare toasts and mailbox against the Figma mocks.
- Verify toast hover shows close button at top-left with border.
- Verify long titles (>100 chars) are truncated with `…` in both toast and mailbox.
- Verify long messages (>100 chars) show the expand chevron, and clicking expands the full text (up to 500 chars).
- Verify notifications with branch context render the branch row; notifications without branch context render the simple layout.
- Verify avatars match vertical tabs (Oz icon with dark background, CLI agents with brand colors, status badges with cutout rings).
- Verify keybinding hint still appears on the newest toast.
- Verify filter bar "All tabs" shows count and spacing matches Figma.
## Open Questions
1. ~~Where does the branch name come from?~~ Resolved: uses `TerminalView::current_git_branch()`, the same source as the branch chip and vertical tabs.
2. ~~What does the settings gear icon in the mailbox header do?~~ Deferred: settings gear removed from this pass.
3. ~~Should the expand affordance collapse back after expanding?~~ Resolved: yes, toggle behavior — clicking the chevron toggles between collapsed and expanded.
+281
View File
@@ -0,0 +1,281 @@
# Notification Toast & Mailbox UI Updates — Tech Spec
## Problem
The notification toast and mailbox UI need visual updates to match new Figma designs. The changes span container sizing, a new branch context row, line-clamped text with expand affordances, shared avatar rendering with vertical tabs, and various padding/spacing adjustments. See `specs/harryalbert/APP-3752/PRODUCT.md` for the full product spec.
## Relevant Code
### Notification module
- `app/src/ai/agent_management/notifications/mod.rs` — module root, re-exports
- `app/src/ai/agent_management/notifications/item.rs` — `NotificationItem`, `NotificationItems`, `NotificationCategory`, `NotificationSourceAgent`
- `app/src/ai/agent_management/notifications/item_rendering.rs` — shared rendering: `render_notification_item_content`, `render_agent_avatar`
- `app/src/ai/agent_management/notifications/toast_stack.rs` — `AgentNotificationToastStack`, `render_toast`, `render_close_button`, `render_keybinding_hint`
- `app/src/ai/agent_management/notifications/view.rs` — `NotificationMailboxView`, header, filter bar, item rendering
### Avatar rendering (vertical tabs)
- `app/src/ui_components/icon_with_status.rs` — `IconWithStatusVariant`, `IconWithStatusSizing`, `render_icon_with_status`
- `app/src/workspace/view/vertical_tabs.rs:84-240` — vertical-tab sizing presets and `render_pane_icon_with_status`
- `app/src/workspace/view/vertical_tabs.rs:1227-1327` — `resolve_icon_with_status_variant` (maps pane types to avatar variants)
### Status icons
- `app/src/ai/agent/conversation.rs:3531-3538` — `ConversationStatus::status_icon_and_color`
- `app/src/ai/conversation_status_ui.rs` — `render_status_element` (used by agent management views)
### Notification creation
- `app/src/ai/agent_management/agent_management_model.rs:287-408` — `handle_history_event_for_mailbox`, `add_notification` (where Oz conversation notifications are created)
- `app/src/ai/agent_management/agent_management_model.rs:123-191` — `handle_cli_agent_session_event` (where CLI session notifications are created)
### CLI session context
- `app/src/terminal/cli_agent_sessions/mod.rs:39-48` — `CLIAgentSessionContext` (cwd, project, query, etc. — no branch field currently)
### Existing text primitives
- `warp_core/src/ui/builder.rs:826-840` — `wrappable_text` (the text component used in notifications)
- No existing max-lines / line-clamp primitive in the UI framework.
## Current State
### Notification items
`NotificationItem` (`item.rs:64-78`) holds: `id`, `origin`, `title`, `message`, `category`, `agent`, `is_read`, `created_at`, `terminal_view_id`, `artifacts`. No branch context.
### Shared rendering
`render_notification_item_content` (`item_rendering.rs:62-164`) is used by both toasts and the mailbox. It renders: avatar + title row (title | timestamp + unread dot) + message + optional artifact buttons. The title and message text wrap freely with no line limits.
### Avatar rendering
Notifications use `render_agent_avatar` (`item_rendering.rs:166-236`) which has its own icon/color logic. Vertical tabs use `render_pane_icon_with_status` with `IconWithStatusVariant` (`vertical_tabs.rs:108-240`) which has better styling: proper CLI brand colors, cutout rings on status badges, and background color matching the panel. These two implementations are independent and visually inconsistent.
### Toast
`render_toast` (`toast_stack.rs:347-435`): 360px wide, 10v/16h padding, 6px radius, default shadow. Close button at top-right via `OffsetPositioning`. Keybinding hint on newest toast.
### Mailbox
`NotificationMailboxView::render` (`view.rs:340-408`): 420px wide, 500px max height. Header has "Notifications" (semibold) + X close button. Filter bar has 4px button gap, 16px horizontal padding. "All" filter doesn't show a count. Items use 12v/16h padding.
## Proposed Changes
### 1. Add `branch` field to `NotificationItem`
**File**: `app/src/ai/agent_management/notifications/item.rs`
Add `pub branch: Option<String>` to `NotificationItem` and the `new()` constructor. This determines whether the item renders with the "rich" layout (branch row) or "simple" layout (current style).
### 2. Thread branch context through notification creation
**File**: `app/src/ai/agent_management/agent_management_model.rs`
Update `add_notification` to accept an `Option<String>` branch parameter. Pass it through to `NotificationItem::new`.
The branch name should come from the same source as the branch chip in the prompt and the vertical tabs subtitle. The vertical tabs already call `TerminalView::current_git_branch(ctx)` (`app/src/terminal/view/tab_metadata.rs:49-63`), which reads the `ShellGitBranch` context chip or falls back to `GitRepoStatusModel`. Since `add_notification` already receives `terminal_view_id`, the implementation should:
1. Resolve the `TerminalView` from `terminal_view_id`.
2. Call `terminal_view.current_git_branch(ctx)` to get the branch name.
3. Pass it as the `branch` parameter.
This works for both Oz conversations and CLI sessions since both are associated with a terminal view that has prompt chip state.
**Fallback**: If the terminal view is not accessible (e.g. window closed), pass `None`. The UI gracefully falls back to the simple layout.
### 3. Extract shared avatar rendering
**New file**: `app/src/ui_components/icon_with_status.rs`
Extract the circle icon rendering into a shared UI component that both toasts/mailbox and vertical tabs can use.
Extract from `vertical_tabs.rs`:
- `render_pane_icon_with_status` and its helpers (`render_with_optional_status_badge`)
- The `IconWithStatusVariant` enum and the icon sizing constants
The extracted functions should accept a **size parameter** rather than using hardcoded constants, since vertical tabs and notifications use different sizes:
- Vertical tabs: `CIRCLE_NEUTRAL_ICON_SIZE = 16.`, `CIRCLE_AGENT_ICON_SIZE = 10.`, `CIRCLE_NEUTRAL_PADDING = 4.`, `CIRCLE_AGENT_PADDING = 5.`
- Notifications (current `render_agent_avatar`): icon 16px, padding 8px → 32px circle total
The shared rendering function should take a size config struct or individual size parameters so both call sites can specify their dimensions.
Keep a notification-specific mapping helper in `item_rendering.rs`:
```rust
fn render_notification_avatar(
agent: NotificationSourceAgent,
category: NotificationCategory,
theme: &WarpTheme,
) -> Box<dyn Element>
```
This function maps `NotificationSourceAgent` → `IconWithStatusVariant` and `NotificationCategory` → status badge, then delegates to the shared circle icon renderer with notification-appropriate sizing.
The mapping:
- `NotificationSourceAgent::Oz` → `IconWithStatusVariant::OzAgent` with `status` derived from category
- `NotificationSourceAgent::CLI(agent)` → `IconWithStatusVariant::CLIAgent` with `status` derived from category
For the `status` parameter, add a helper that converts `NotificationCategory` → `ConversationStatus`:
- `NotificationCategory::Complete` → `ConversationStatus::Success`
- `NotificationCategory::Request` → `ConversationStatus::Blocked { blocked_action: String::new() }`
- `NotificationCategory::Error` → `ConversationStatus::Error`
This reuses `ConversationStatus::status_icon_and_color` (`conversation.rs:3531`) which the vertical tabs already use.
### 4. Split `render_notification_item_content` into rich and simple variants
**File**: `app/src/ai/agent_management/notifications/item_rendering.rs`
Currently `render_notification_item_content` is one function. Refactor into:
**`render_rich_text_column`** (when `item.branch.is_some()`):
- Branch row: git-branch icon (10px) + branch name (12px, sub_text color) on left.
- Toast: chevron-right icon on right (when content is truncated).
- Mailbox: timestamp + unread dot on right.
- Title: 14px semibold, character-count truncated.
- Message: 14px regular, sub_text, character-count truncated.
- Artifact buttons with 48px left padding.
**`render_simple_text_column`** (when `item.branch.is_none()`):
- Current layout: title | timestamp + unread dot in SpaceBetween row.
- Message below.
- Title and message use the same character-count truncation behavior as rich items.
- Mailbox simple rows do not show an expand chevron; toast simple rows show the chevron in the title row when content is truncated.
Both variants call the shared avatar renderer from step 3.
Extract shared sub-rendering into free functions that both variants can use:
- `render_branch_row(branch, right_side_content, appearance)` — the git-branch icon + text row
- `render_clamped_title(title, max_lines, appearance)` — title text with line clamping
- `render_clamped_message(message, max_lines, expand_affordance, appearance)` — message with optional expand
- `render_timestamp_with_dot(created_at, is_read, appearance)` — timestamp + optional unread dot
The existing `render_git_branch_text` in `vertical_tabs.rs:1899-1921` is a good reference for the branch row rendering and could be reused or extracted to a shared location.
A public entry point selects the variant:
```rust
fn render_notification_item_content(
item: &NotificationItem,
artifact_buttons: Option<&ViewHandle<ArtifactButtonsRow>>,
context: NotificationRenderContext,
appearance: &Appearance,
) -> Box<dyn Element>
```
Where `NotificationRenderContext` is an enum:
```rust
enum NotificationRenderContext {
Toast,
Mailbox,
}
```
This context determines whether the branch row's right side shows a chevron (toast) or timestamp + unread dot (mailbox).
### 5. Implement character-count truncation with expand
Truncate title and message text using a character-count heuristic rather than a visual line-clamp primitive (which doesn't exist in the UI framework).
Constants:
- `COLLAPSED_MAX_CHARS = 100` — max characters when collapsed
- `EXPANDED_MAX_CHARS = 500` — max characters when expanded
A `truncate_text(text, max_chars)` helper appends `…` when the text exceeds the limit.
A `content_is_truncated(title, message)` helper returns true when either exceeds `COLLAPSED_MAX_CHARS`, which controls whether the expand chevron is rendered.
For the expand affordance:
- Add `message_expanded: bool` state to `NotificationToastItem` (toast_stack.rs) and per-item state in `NotificationMailboxView` (view.rs).
- Both toast and mailbox use a chevron icon (ChevronRight when collapsed, ChevronDown when expanded) as the expand affordance.
- The chevron is rendered in the branch row (rich layout) or title row (simple layout, toast only).
### 6. Update toast container and close button
**File**: `app/src/ai/agent_management/notifications/toast_stack.rs`
In `render_toast`:
- Width: `360.` → `420.` (line 390)
- Padding: `.with_vertical(10.).with_horizontal(16.)` → `.with_uniform(12.)` (line 383)
- Corner radius: `Radius::Pixels(6.)` → `Radius::Pixels(8.)` (line 386)
- Drop shadow: removed (was `DropShadow::default()`)
In `render_close_button`:
- Change `PositionedElementAnchor::TopRight` / `ChildAnchor::TopRight` → `PositionedElementAnchor::TopLeft` / `ChildAnchor::TopLeft` (line 414)
- Adjust offset vector from `(4., -4.)` → `(-4., -4.)` or similar to position outside the top-left
- Add a visible border to the close button circle (add `Border::all(0.67)` with outline color)
### 7. Remove timestamp and unread dot from toast
In the rich/simple layout variants, when `context == NotificationRenderContext::Toast`:
- Rich layout: no timestamp/dot in branch row (chevron-right only when truncated).
- Simple layout: no timestamp — show only the title (plus expand chevron when truncated).
### 8. Update mailbox header
**File**: `app/src/ai/agent_management/notifications/view.rs`
In `render_header`:
- Change title font weight from `Weight::Semibold` → remove the weight override (defaults to regular).
- Update padding from `.with_vertical(8.).with_left(16.).with_right(8.)` → `.with_vertical(8.).with_horizontal(12.)` (line 436-440).
### 9. Update mailbox filter bar
**File**: `app/src/ai/agent_management/notifications/view.rs`
In `render_filter_bar`:
- Change filter button spacing from `4.` → `2.` (line 456).
- Update "All" filter label to include count: change the conditional at lines 465-473 so `NotificationFilter::All` also shows `"All tabs ({count})"`.
- Update bar padding from `.with_vertical(12.).with_horizontal(16.)` → `.with_vertical(12.).with_left(12.).with_right(6.)` (line 537).
### 10. Update mailbox item padding
**File**: `app/src/ai/agent_management/notifications/view.rs`
In `render_notification_item`:
- Rich items (branch present): padding `Padding::uniform(12.)` (was `.with_vertical(12.).with_horizontal(16.)`)
- Simple items (branch absent): keep `.with_vertical(12.).with_horizontal(16.)`
### 11. Timestamp font size
In both rich and simple item layouts, change timestamp font size from `14.` → `12.` in `item_rendering.rs`.
## End-to-End Flow
### Notification creation
1. Conversation status changes or CLI agent session event fires.
2. `AgentNotificationsModel::add_notification` is called with the new `branch` parameter.
3. `NotificationItem` is created with `branch: Option<String>`.
4. `AgentManagementEvent::NotificationAdded` is emitted.
### Toast rendering
1. `AgentNotificationToastStack` receives the event, creates a `NotificationToastItem`.
2. `render_toast` builds the toast container with updated sizing (420px, 12px padding, 8px radius, no drop shadow).
3. `render_notification_item_content` is called with `NotificationRenderContext::Toast`.
4. If `item.branch.is_some()`, renders rich layout (branch row with chevron, clamped title/message).
5. If `item.branch.is_none()`, renders simple layout (title only, no timestamp).
6. Avatar uses `render_notification_avatar` → delegates to vertical tabs primitives.
7. Close button positioned at top-left with border.
### Mailbox rendering
1. `NotificationMailboxView::render` builds the popup with updated container (4px top padding, no drop shadow).
2. Header renders with regular-weight title + close button.
3. Filter bar renders with 2px gap, count on "All", updated padding.
4. Each item calls `render_notification_item_content` with `NotificationRenderContext::Mailbox`.
5. Rich items show branch row (with timestamp + unread dot on right), truncated title/message.
6. Simple items show current layout with timestamp in title row.
## Risks and Mitigations
**Text truncation detection**: Precisely detecting whether wrapped text exceeds N lines is non-trivial without a max-lines primitive. Mitigation: use `ConstrainedBox::with_max_height` for visual clamping and a character-length heuristic to decide whether to show the expand affordance. This won't be pixel-perfect but will be correct for the vast majority of cases.
**Avatar extraction scope**: Moving `IconWithStatusVariant` and friends out of `vertical_tabs.rs` touches a high-traffic file. Mitigation: keep the extraction mechanical — move types and functions without changing behavior, then add the notification-specific mapping in the new `avatar.rs` module.
**Branch availability**: Branch data comes from `TerminalView::current_git_branch()` which reads the shell's git branch chip. It is available for any terminal in a git repo but may be `None` for sessions outside a repo. The UI gracefully falls back to the simple layout when branch is `None`.
## Testing and Validation
- `cargo check` to verify compilation after each step.
- Visual comparison against Figma mocks for both toast and mailbox.
- Verify toast: 420px width, 12px padding, 8px radius, close button at top-left on hover, keybinding hint on newest.
- Verify mailbox: 4px top padding, header with title + close, filter bar spacing, "All tabs (N)" count.
- Verify rich items: branch row, truncated title, truncated message, expand chevron works.
- Verify simple items: current layout preserved, timestamp at 12px font.
- Verify avatars match vertical tabs (Oz, CLI brand colors, status badges with cutout rings).
- Existing notification unit tests in `item_tests.rs` should still pass after adding the `branch` field.
## Follow-ups
- **CLI session branch**: Extend the CLI agent plugin protocol to surface the working branch name in `CLIAgentSessionContext`.
- **Settings gear**: Punted from this pass. Add to mailbox header when design is finalized.
- **Approve/decline on toasts**: Punted from this pass. Will need plumbing to dispatch permission responses directly from toasts.
- **Precise line measurement**: If the character-count heuristic for expand affordances proves insufficient, invest in a proper max-lines text primitive in the UI framework.