Files

13 KiB
Raw Permalink Blame History

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.rsOneTimeModalModel: 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-1631HAS_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.rsSessionConfigModal: 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.rsSessionConfigSelection, 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-1840render_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:245use_vertical_tabs: bool setting.
  • app/src/workspace/view.rs:1607-1614open_vertical_tabs_panel_if_enabled: opens the vertical tabs panel based on the setting.

Anchoring targets

  • app/src/workspace/view.rs:535NOTIFICATIONS_MAILBOX_POSITION_ID = "workspace:notifications_mailbox": the inbox icon already uses SavePosition for anchoring (line 15262).
  • app/src/workspace/view.rs:515TAB_BAR_POSITION_ID: the tab bar already has a SavePosition.

Feature flags

  • crates/warp_features/src/lib.rs:9FeatureFlag enum.
  • crates/warp_features/src/lib.rs:797DOGFOOD_FLAGS array.

Workspace integration

  • app/src/workspace/view.rs:779-901Workspace struct: holds all modal view handles, panel state.
  • app/src/workspace/view.rs:1463-1494build_session_config_modal: how modals are constructed and subscribed.
  • app/src/workspace/view.rs:1617-1638show_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

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

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:

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):

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.