# 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` 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 2–4. ### 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` - `render_directory_picker(selected_directory, mouse_state, appearance) -> Box` - `render_worktree_checkbox(enabled, is_git_repo, checkbox_mouse_state, tooltip_mouse_state, appearance) -> Box` `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, // Step 2 state switch_to_horizontal_checkbox_mouse_state: MouseStateHandle, // Step 4 state (mirrors SessionConfigModal fields) session_types: Vec, selected_session_type_index: usize, selected_directory: PathBuf, is_git_repo: bool, enable_worktree: bool, session_pill_mouse_states: Vec, directory_button_mouse_state: MouseStateHandle, worktree_checkbox_mouse_state: MouseStateHandle, worktree_tooltip_mouse_state: MouseStateHandle, } ``` **Events**: ```rust enum HoaOnboardingFlowEvent { Completed(Option), 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 2–3: 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>` 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.