Files
galaxy/specs/harryalbert/APP-3809/TECH.md
T

239 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# HOA Onboarding Flow — Tech Spec
Linear: APP-3809
Product spec: `specs/harryalbert/APP-3809/PRODUCT.md`
## Problem
We need a 4-step guided onboarding flow for existing users that introduces HOA features (vertical tabs, agent inbox, tab configs). The flow must be shown exactly once, gated behind a feature flag, and must reuse existing rendering code from `SessionConfigModal` and the FTU callout pattern.
## Relevant Code
### Triggering & persistence
- `app/src/workspace/one_time_modal_model.rs``OneTimeModalModel`: existing pattern for one-time modals (Oz launch, build plan migration). Subscribes to auth events, waits for cloud preferences sync, then triggers.
- `app/src/root_view.rs:1614-1631``HAS_COMPLETED_ONBOARDING_KEY` / `private_user_preferences`: the other persistence approach (local-only, no cloud sync needed).
- `app/src/root_view.rs:2143-2155` — post-onboarding flow: opens vertical tabs panel + shows `SessionConfigModal` for new users.
### Session config modal (for extraction)
- `app/src/tab_configs/session_config_modal.rs``SessionConfigModal`: the full modal with session type pills, directory picker, worktree checkbox. All rendering is in private methods (`render_session_type_section`, `render_directory_section`, `render_checkboxes`).
- `app/src/tab_configs/session_config.rs``SessionConfigSelection`, `SessionType`, `is_git_repo`, `build_tab_config`, `write_tab_config`.
### FTU callout (for extraction)
- `app/src/ai/blocklist/agent_view/agent_input_footer/mod.rs:1758-1840``render_ftu_callout`: renders a bubble with a triangle arrow using stacked `CalloutTriangleBorderDown` / `CalloutTriangleFillDown` icons. Positions via `OffsetPositioning::offset_from_save_position_element`.
### Tab layout setting
- `app/src/workspace/tab_settings.rs:245``use_vertical_tabs: bool` setting.
- `app/src/workspace/view.rs:1607-1614``open_vertical_tabs_panel_if_enabled`: opens the vertical tabs panel based on the setting.
### Anchoring targets
- `app/src/workspace/view.rs:535``NOTIFICATIONS_MAILBOX_POSITION_ID = "workspace:notifications_mailbox"`: the inbox icon already uses `SavePosition` for anchoring (line 15262).
- `app/src/workspace/view.rs:515``TAB_BAR_POSITION_ID`: the tab bar already has a `SavePosition`.
### Feature flags
- `crates/warp_features/src/lib.rs:9``FeatureFlag` enum.
- `crates/warp_features/src/lib.rs:797``DOGFOOD_FLAGS` array.
### Workspace integration
- `app/src/workspace/view.rs:779-901``Workspace` struct: holds all modal view handles, panel state.
- `app/src/workspace/view.rs:1463-1494``build_session_config_modal`: how modals are constructed and subscribed.
- `app/src/workspace/view.rs:1617-1638``show_session_config_modal` / `close_session_config_modal`.
## Current State
- New users go through `AgentOnboardingView` → then post-onboarding opens vertical tabs + `SessionConfigModal` (`root_view.rs:2143-2155`).
- Existing users get no introduction to HOA features.
- The `SessionConfigModal` rendering methods are private and tightly coupled to the modal's `View` impl — they can't be reused without extraction.
- The FTU callout rendering is a standalone `fn render_ftu_callout` but hardcodes text, colors, and arrow direction. The general pattern (bubble + positioned triangle) is reusable.
- `OneTimeModalModel` handles one-time modal triggering for existing users (auth → cloud sync → check flag → show). It uses `AISettings` to persist "shown" state via cloud-synced settings.
## Proposed Changes
### 1. Feature flag
Add `HOAOnboardingFlow` to the `FeatureFlag` enum and `DOGFOOD_FLAGS`. Read the create feature flag skill for information on how to do this in a complete way.
File: `crates/warp_features/src/lib.rs`
### 2. Shared callout bubble component
Extract from the FTU callout into a general-purpose helper in a new file.
File: `app/src/view_components/callout_bubble.rs`
```rust
pub enum CalloutArrowDirection {
Up,
Left,
}
pub enum CalloutArrowPosition {
Start(f32),
Center,
End(f32),
}
pub struct CalloutBubbleConfig {
pub width: f32,
pub arrow_direction: CalloutArrowDirection,
pub arrow_position: CalloutArrowPosition,
}
```
A `render_callout_bubble(content, config, appearance) -> Box<dyn Element>` function that:
- Wraps `content` in a bordered, rounded container with accent-tinted background (matching FTU style).
- Appends/prepends a triangle arrow (using the existing `CalloutTriangleBorderDown` / `CalloutTriangleFillDown` icon stacking technique) positioned per the config.
This is reused by the HOA flow's Steps 24.
### 3. Shared session config rendering
Extract the rendering portions of `SessionConfigModal` into free functions that both the modal and the onboarding step can call.
File: `app/src/tab_configs/session_config_rendering.rs`
Extract these from `SessionConfigModal`:
- `render_session_type_pills(session_types, selected_index, pill_mouse_states, appearance) -> Box<dyn Element>`
- `render_directory_picker(selected_directory, mouse_state, appearance) -> Box<dyn Element>`
- `render_worktree_checkbox(enabled, is_git_repo, checkbox_mouse_state, tooltip_mouse_state, appearance) -> Box<dyn Element>`
`SessionConfigModal` calls these extracted functions in its `render` method instead of its current private methods. The onboarding Step 4 view calls the same functions.
The action dispatch, state management, and file picker logic remain in each caller (`SessionConfigModal` and the onboarding view) — only the rendering is shared.
### 4. HOA onboarding view
New module: `app/src/workspace/hoa_onboarding/`
- `mod.rs` — re-exports.
- `hoa_onboarding_flow.rs` — the main `HoaOnboardingFlow` view.
- `welcome_banner.rs` — Step 1 rendering.
- `tab_config_step.rs` — Step 4 rendering (uses shared session config rendering).
#### `HoaOnboardingFlow` view
```rust
enum HoaOnboardingStep {
WelcomeBanner,
VerticalTabsCallout,
AgentInboxCallout,
TabConfig,
}
struct HoaOnboardingFlow {
step: HoaOnboardingStep,
// Step 1 state
close_button: ViewHandle<ActionButton>,
// Step 2 state
switch_to_horizontal_checkbox_mouse_state: MouseStateHandle,
// Step 4 state (mirrors SessionConfigModal fields)
session_types: Vec<SessionType>,
selected_session_type_index: usize,
selected_directory: PathBuf,
is_git_repo: bool,
enable_worktree: bool,
session_pill_mouse_states: Vec<MouseStateHandle>,
directory_button_mouse_state: MouseStateHandle,
worktree_checkbox_mouse_state: MouseStateHandle,
worktree_tooltip_mouse_state: MouseStateHandle,
}
```
**Events**:
```rust
enum HoaOnboardingFlowEvent {
Completed(Option<SessionConfigSelection>),
Dismissed,
}
```
- `Completed(Some(selection))` — user clicked Finish with a valid tab config.
- `Completed(None)` — user completed the flow but didn't reach the tab config (shouldn't happen in normal flow, but covers edge cases).
- `Dismissed` — user clicked X.
**Rendering** dispatches to different renderers per step:
- Steps 1: a centered modal with scrim (similar to how `SessionConfigModal` is wrapped in a `Modal`).
- Steps 23: the shared `render_callout_bubble` positioned via `OffsetPositioning::offset_from_save_position_element`, anchored to the vertical tabs panel or `NOTIFICATIONS_MAILBOX_POSITION_ID`.
- Step 4: the shared `render_callout_bubble` with the tab config form body, anchored to the vertical tabs panel in vertical tabs mode or the tab bar in horizontal tabs mode.
### 5. Persistence
Add a new `private_user_preferences` key: `HasCompletedHOAOnboarding`.
Use the same pattern as `HAS_COMPLETED_ONBOARDING_KEY` in `root_view.rs:1614-1631` — a simple boolean read/write via `ctx.private_user_preferences()`. This should be cloud synced.
Helper functions (placed near the onboarding flow or in a shared utils module):
```rust
const HAS_COMPLETED_HOA_ONBOARDING_KEY: &str = "HasCompletedHOAOnboarding";
fn has_completed_hoa_onboarding(ctx: &AppContext) -> bool { ... }
fn mark_hoa_onboarding_completed(ctx: &AppContext) { ... }
```
### 6. Triggering
Integrate into `OneTimeModalModel`:
- Add `is_hoa_onboarding_open: bool` field.
- Add a `check_and_trigger_hoa_onboarding` method that:
1. Checks `FeatureFlag::HOAOnboardingFlow.is_enabled()`.
2. Checks `!has_completed_hoa_onboarding(ctx)`.
3. Checks `has_completed_local_onboarding(ctx)` — ensures this is an existing user (not someone mid-new-user-onboarding).
4. Sets `is_hoa_onboarding_open = true` and emits the event.
- Call this from `check_and_trigger_all_modals` alongside the existing Oz launch and build plan migration checks.
In `Workspace`, subscribe to `OneTimeModalEvent` and when the HOA onboarding should show:
- Ensure vertical tabs are enabled (set the `use_vertical_tabs` setting to `true` if not already).
- Open the vertical tabs panel.
- Show the `HoaOnboardingFlow` view as an overlay.
### 7. New-user onboarding integration
In `root_view.rs`, after new-user onboarding completes (`AgentOnboardingEvent::OnboardingCompleted`), call `mark_hoa_onboarding_completed(ctx)` to ensure the HOA flow never shows for new users (only if the hoa flow feature flag is enabled).
### 8. Workspace rendering integration
Add `hoa_onboarding_flow: Option<ViewHandle<HoaOnboardingFlow>>` to `Workspace`.
In the workspace's `render` method:
- When `hoa_onboarding_flow` is `Some` and the step is `WelcomeBanner`, render a full-window scrim overlay with the banner centered.
- When the step is `VerticalTabsCallout` or `AgentInboxCallout`, render the callout bubble as a positioned overlay (using `Stack::add_positioned_overlay_child`) anchored to the appropriate `SavePosition` element.
- When the step is `TabConfig`, render the tab config popover as a positioned overlay anchored to the content area.
Handle `HoaOnboardingFlowEvent`:
- On `Dismissed`: call `mark_hoa_onboarding_completed`, drop the flow view.
- On `Completed(Some(selection))`: call `mark_hoa_onboarding_completed`, process the tab config selection (reuse `handle_session_config_completed` logic), drop the flow view.
- On `Completed(None)`: call `mark_hoa_onboarding_completed`, drop the flow view.
## End-to-End Flow
1. Existing user launches Warp with `HOAOnboardingFlow` enabled.
2. `OneTimeModalModel` receives auth + cloud sync completion → calls `check_and_trigger_hoa_onboarding`.
3. Checks pass (flag enabled, not completed, is existing user) → emits `OneTimeModalEvent`.
4. `Workspace` receives event → enables vertical tabs setting → opens vertical tabs panel → creates `HoaOnboardingFlow` in `WelcomeBanner` step → renders scrim + banner.
5. User clicks "See what's new" → flow advances to `VerticalTabsCallout` → callout bubble rendered anchored to vertical tabs panel.
6. User optionally toggles "Switch back to horizontal tabs" → `use_vertical_tabs` setting toggled, UI updates live, callout re-anchors.
7. User clicks "Next" → flow advances to `AgentInboxCallout` → callout bubble rendered anchored to `NOTIFICATIONS_MAILBOX_POSITION_ID`.
8. User clicks "Next" → flow advances to `TabConfig` → popover with session config fields rendered.
9. User configures and clicks "Finish" → `HoaOnboardingFlowEvent::Completed(Some(selection))` emitted → tab config saved, flow closed, preferences key set.
## Risks and Mitigations
1. **Rendering extraction breaking `SessionConfigModal`**: The extraction of rendering functions is a refactor of existing code. Risk: subtle visual regressions. Mitigation: compare screenshots before/after extraction to verify pixel-identical output.
2. **Callout positioning edge cases**: If the vertical tabs panel or inbox icon aren't rendered yet when the callout tries to anchor, `offset_from_save_position_element` may position incorrectly. Mitigation: the flow ensures vertical tabs are opened before Step 2, and the inbox icon is always in the title bar when `HOANotifications` is enabled.
3. **Interaction with other one-time modals**: `OneTimeModalModel` runs checks sequentially — Oz launch modal takes priority. If the Oz launch modal shows, the HOA flow won't trigger until the next app launch. This is acceptable; the flow persists across launches until shown.
4. **Feature flag dependency**: The flow assumes `VerticalTabs`, `HOANotifications`, and `TabConfigs` flags are also enabled. If any are disabled, certain steps may reference UI that doesn't exist. Mitigation: gate the HOA flow on all required flags in `check_and_trigger_hoa_onboarding`.
## Testing and Validation
- **Unit tests**: persistence helpers (`has_completed_hoa_onboarding`, `mark_hoa_onboarding_completed`), step transitions in `HoaOnboardingFlow`, new-user exclusion logic.
- **Visual verification**: local walkthrough of all 4 steps after building.
- **Extraction regression**: verify `SessionConfigModal` still renders identically after extracting shared rendering functions.
- **Edge cases**: dismiss at Step 1, dismiss at Step 4, toggle horizontal tabs back and forth, select non-git directory for worktree.
## Follow-ups
- Remove `HOAOnboardingFlow` feature flag once stable (promote through preview → release).
- Refactor the existing FTU callout in `agent_input_footer` to use the shared `callout_bubble` component.
- Hero art asset: needs a static image to be produced and bundled.