mirror of
https://github.com/stablyai/orca.git
synced 2026-10-29 16:17:51 +00:00
Add new-user parallel work activation tour
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -20,12 +20,15 @@ describe('contextual tour definitions', () => {
|
||||
expect(CONTEXTUAL_TOURS.map((tour) => tour.id)).toEqual(expectedIds)
|
||||
for (const tour of CONTEXTUAL_TOURS) {
|
||||
expect(tour.steps[0]?.requiredForStart).toBe(true)
|
||||
if (tour.steps.length === 1) {
|
||||
expect(tour.steps[0]?.advanceOnFeatureInteraction).toBeTruthy()
|
||||
const stepCount = (tour.steps as readonly unknown[]).length
|
||||
if (stepCount === 1) {
|
||||
expect(
|
||||
(tour.steps[0] as ContextualTour['steps'][number]).advanceOnFeatureInteraction
|
||||
).toBeTruthy()
|
||||
} else {
|
||||
expect(tour.steps.length).toBeGreaterThanOrEqual(2)
|
||||
expect(stepCount).toBeGreaterThanOrEqual(2)
|
||||
}
|
||||
expect(tour.steps.length).toBeLessThanOrEqual(3)
|
||||
expect(stepCount).toBeLessThanOrEqual(tour.id === 'workspace-agent-sessions' ? 5 : 3)
|
||||
for (const step of tour.steps) {
|
||||
expect(step.title.length).toBeGreaterThan(0)
|
||||
expect(step.body.length).toBeGreaterThan(0)
|
||||
@@ -35,6 +38,45 @@ describe('contextual tour definitions', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('defines the workspace agent sessions value tour with split and navigation actions', () => {
|
||||
const tour = CONTEXTUAL_TOURS.find((entry) => entry.id === 'workspace-agent-sessions') as
|
||||
| ContextualTour
|
||||
| undefined
|
||||
|
||||
expect(tour?.steps.map((step) => step.title)).toEqual([
|
||||
'Work side by side',
|
||||
'Split the terminal',
|
||||
'Keep separate tasks isolated',
|
||||
'Start from real work',
|
||||
'Orchestrate capable agents'
|
||||
])
|
||||
expect(tour?.steps[0]).toMatchObject({
|
||||
requiredForStart: true,
|
||||
primaryAction: { kind: 'next', label: 'Show me' }
|
||||
})
|
||||
expect(tour?.steps[1]).toMatchObject({
|
||||
primaryAction: { kind: 'split-terminal-pane', label: 'Split terminal' },
|
||||
advanceOnFeatureInteraction: 'terminal-pane-split'
|
||||
})
|
||||
expect(tour?.steps[1]?.targetSelector).toContain('terminal-pane-split-target')
|
||||
expect(tour?.steps[1]?.targetSelector).not.toContain('terminal-split-control')
|
||||
expect(tour?.steps[1]?.secondaryAction).toBeUndefined()
|
||||
expect(tour?.steps[2]).toMatchObject({
|
||||
primaryAction: { kind: 'show-worktrees', label: 'Show worktrees' },
|
||||
secondaryAction: { kind: 'next', label: 'Skip' }
|
||||
})
|
||||
expect(tour?.steps[2]?.targetSelector).toContain('workspace-list')
|
||||
expect(tour?.steps[3]).toMatchObject({
|
||||
primaryAction: { kind: 'open-tasks', label: 'Show tasks' },
|
||||
secondaryAction: { kind: 'next', label: 'Skip' }
|
||||
})
|
||||
expect(tour?.steps[3]?.targetSelector).toContain('sidebar-tasks')
|
||||
expect(tour?.steps[4]).toMatchObject({
|
||||
primaryAction: { kind: 'open-getting-started', label: 'Open Getting started' },
|
||||
secondaryAction: { kind: 'complete', label: 'Done' }
|
||||
})
|
||||
})
|
||||
|
||||
it('allows only workspace creation over its workspace composer modal', () => {
|
||||
const modalTours = (CONTEXTUAL_TOURS as readonly ContextualTour[]).filter(
|
||||
(tour) => tour.allowedActiveModals?.length
|
||||
|
||||
@@ -12,6 +12,19 @@ export type ContextualTourStepControl = {
|
||||
kind: 'auto-rename-branch-from-work'
|
||||
}
|
||||
|
||||
export type ContextualTourStepActionKind =
|
||||
| 'next'
|
||||
| 'complete'
|
||||
| 'split-terminal-pane'
|
||||
| 'show-worktrees'
|
||||
| 'open-tasks'
|
||||
| 'open-getting-started'
|
||||
|
||||
export type ContextualTourStepAction = {
|
||||
kind: ContextualTourStepActionKind
|
||||
label: string
|
||||
}
|
||||
|
||||
export type ContextualTourStepPlacement = 'top' | 'right' | 'bottom' | 'left'
|
||||
|
||||
export type ContextualTourStep = {
|
||||
@@ -22,6 +35,8 @@ export type ContextualTourStep = {
|
||||
fallbackCopy?: string
|
||||
preferredPlacement?: ContextualTourStepPlacement
|
||||
control?: ContextualTourStepControl
|
||||
primaryAction?: ContextualTourStepAction
|
||||
secondaryAction?: ContextualTourStepAction
|
||||
advanceOnFeatureInteraction?: FeatureInteractionId
|
||||
}
|
||||
|
||||
@@ -57,12 +72,48 @@ export const CONTEXTUAL_TOURS = [
|
||||
id: 'workspace-agent-sessions',
|
||||
steps: [
|
||||
{
|
||||
title: 'Split panes for agents',
|
||||
body: 'Use the right-click menu or {terminal.splitRight} to split the terminal and keep multiple agent sessions visible at once.',
|
||||
title: 'Work side by side',
|
||||
body: 'Orca is built for running more than one thread of work at once.',
|
||||
targetSelector: '[data-contextual-tour-target="workspace-agent-terminal-tip"]',
|
||||
requiredForStart: true,
|
||||
preferredPlacement: 'bottom',
|
||||
primaryAction: { kind: 'next', label: 'Show me' }
|
||||
},
|
||||
{
|
||||
title: 'Split the terminal',
|
||||
body: 'Split right to make room for another agent, a server, or a follow-up.',
|
||||
targetSelector:
|
||||
'[data-contextual-tour-target="terminal-pane-split-target"], [data-contextual-tour-target="workspace-agent-terminal-tip"]',
|
||||
preferredPlacement: 'bottom',
|
||||
primaryAction: { kind: 'split-terminal-pane', label: 'Split terminal' },
|
||||
advanceOnFeatureInteraction: 'terminal-pane-split'
|
||||
},
|
||||
{
|
||||
title: 'Keep separate tasks isolated',
|
||||
body: 'Use separate worktrees when you want different tasks or agents isolated from each other.',
|
||||
targetSelector:
|
||||
'[data-contextual-tour-target="workspace-list"], [data-contextual-tour-target="workspace-create-control"], [data-contextual-tour-target="sidebar-workspaces"]',
|
||||
preferredPlacement: 'right',
|
||||
primaryAction: { kind: 'show-worktrees', label: 'Show worktrees' },
|
||||
secondaryAction: { kind: 'next', label: 'Skip' }
|
||||
},
|
||||
{
|
||||
title: 'Start from real work',
|
||||
body: 'Start from tasks, issues, reviews, or merge requests so agents begin with the right context.',
|
||||
targetSelector:
|
||||
'[data-contextual-tour-target="sidebar-tasks"], [data-contextual-tour-target="tasks-source-filters"], [data-contextual-tour-target="sidebar-navigation"]',
|
||||
preferredPlacement: 'right',
|
||||
primaryAction: { kind: 'open-tasks', label: 'Show tasks' },
|
||||
secondaryAction: { kind: 'next', label: 'Skip' }
|
||||
},
|
||||
{
|
||||
title: 'Orchestrate capable agents',
|
||||
body: 'Use browser context, setup scripts, notifications, orchestration, and action skills so agents can coordinate beyond the terminal.',
|
||||
targetSelector:
|
||||
'[data-contextual-tour-target="browser-toolbar"], [data-contextual-tour-target="setup-guide-entry"], [data-contextual-tour-target="sidebar-navigation"]',
|
||||
preferredPlacement: 'right',
|
||||
primaryAction: { kind: 'open-getting-started', label: 'Open Getting started' },
|
||||
secondaryAction: { kind: 'complete', label: 'Done' }
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -15,6 +15,9 @@ describe('feature education telemetry constants', () => {
|
||||
expect(normalizeFeatureEducationSource('workspace_agent_sessions_visible')).toBe(
|
||||
'workspace_agent_sessions_visible'
|
||||
)
|
||||
expect(normalizeFeatureEducationSource('setup_guide_parallel_work')).toBe(
|
||||
'setup_guide_parallel_work'
|
||||
)
|
||||
expect(normalizeFeatureEducationSource('https://example.com/private')).toBe('unknown')
|
||||
expect(normalizeFeatureEducationSource(null)).toBe('unknown')
|
||||
})
|
||||
|
||||
@@ -17,6 +17,7 @@ export const FEATURE_EDUCATION_SOURCES = [
|
||||
'automations_open',
|
||||
'workspace_creation_visible',
|
||||
'workspace_creation_modal',
|
||||
'setup_guide_parallel_work',
|
||||
'unknown'
|
||||
] as const
|
||||
|
||||
|
||||
@@ -15,19 +15,14 @@ export type FeatureWallSetupStep = {
|
||||
readonly description: string
|
||||
}
|
||||
|
||||
export const FEATURE_WALL_SETUP_PARALLEL_WORK_STEP_IDS = [
|
||||
'two-agents',
|
||||
'three-workspaces'
|
||||
] as const satisfies readonly FeatureWallSetupStepId[]
|
||||
|
||||
export type FeatureWallSetupSectionId = 'parallel-work' | 'setup'
|
||||
|
||||
export const FEATURE_WALL_SETUP_STEPS: readonly FeatureWallSetupStep[] = [
|
||||
{
|
||||
id: 'default-agent',
|
||||
name: 'Pick a default agent',
|
||||
subtitle: 'Pick a default agent',
|
||||
description: 'Start new work with the agent you trust most, without choosing every time.'
|
||||
},
|
||||
{
|
||||
id: 'notifications',
|
||||
name: 'Configure notifications',
|
||||
subtitle: 'Configure notifications',
|
||||
description: 'Know when agents need attention, finish work, or get blocked.'
|
||||
},
|
||||
{
|
||||
id: 'two-agents',
|
||||
name: 'Start 2 agents in one worktree',
|
||||
@@ -40,18 +35,24 @@ export const FEATURE_WALL_SETUP_STEPS: readonly FeatureWallSetupStep[] = [
|
||||
subtitle: 'Create 2 worktrees',
|
||||
description: 'Keep separate tasks in separate worktrees so agents can work independently.'
|
||||
},
|
||||
{
|
||||
id: 'notifications',
|
||||
name: 'Configure notifications',
|
||||
subtitle: 'Configure notifications',
|
||||
description: 'Know when agents need attention, finish work, or get blocked.'
|
||||
},
|
||||
{
|
||||
id: 'default-agent',
|
||||
name: 'Pick a default agent',
|
||||
subtitle: 'Pick a default agent',
|
||||
description: 'Start new work with the agent you trust most, without choosing every time.'
|
||||
},
|
||||
{
|
||||
id: 'task-sources',
|
||||
name: 'Enable task sources',
|
||||
subtitle: 'Enable task sources',
|
||||
description: 'Start work directly from your tasks and keep PR status in view.'
|
||||
},
|
||||
{
|
||||
id: 'agent-capabilities',
|
||||
name: 'Install CLI & Skills',
|
||||
subtitle: 'Install CLI & Skills',
|
||||
description: 'Enable the tools that let agents work more independently.'
|
||||
},
|
||||
{
|
||||
id: 'setup-script',
|
||||
name: 'Configure a setup script',
|
||||
@@ -64,6 +65,12 @@ export const FEATURE_WALL_SETUP_STEPS: readonly FeatureWallSetupStep[] = [
|
||||
name: 'Add 2 projects',
|
||||
subtitle: 'Add 2 projects',
|
||||
description: 'Bring your key repos into Orca and run agent work across them in parallel.'
|
||||
},
|
||||
{
|
||||
id: 'agent-capabilities',
|
||||
name: 'Install CLI & Skills',
|
||||
subtitle: 'Install CLI & Skills',
|
||||
description: 'Unlock browser, orchestration, and computer-use actions for agents.'
|
||||
}
|
||||
] as const
|
||||
|
||||
@@ -72,3 +79,34 @@ export const FEATURE_WALL_SETUP_STEP_IDS = FEATURE_WALL_SETUP_STEPS.map((step) =
|
||||
export function getFeatureWallSetupSteps(): readonly FeatureWallSetupStep[] {
|
||||
return FEATURE_WALL_SETUP_STEPS
|
||||
}
|
||||
|
||||
export function getFeatureWallSetupSectionId(
|
||||
stepId: FeatureWallSetupStepId
|
||||
): FeatureWallSetupSectionId {
|
||||
return FEATURE_WALL_SETUP_PARALLEL_WORK_STEP_IDS.includes(
|
||||
stepId as (typeof FEATURE_WALL_SETUP_PARALLEL_WORK_STEP_IDS)[number]
|
||||
)
|
||||
? 'parallel-work'
|
||||
: 'setup'
|
||||
}
|
||||
|
||||
export function getFeatureWallSetupStepsForSection(
|
||||
sectionId: FeatureWallSetupSectionId
|
||||
): readonly FeatureWallSetupStep[] {
|
||||
return FEATURE_WALL_SETUP_STEPS.filter(
|
||||
(step) => getFeatureWallSetupSectionId(step.id) === sectionId
|
||||
)
|
||||
}
|
||||
|
||||
export function getFirstIncompleteFeatureWallSetupStepId(
|
||||
stepDone: Partial<Record<FeatureWallSetupStepId, boolean>>
|
||||
): FeatureWallSetupStepId {
|
||||
const parallelStep = getFeatureWallSetupStepsForSection('parallel-work').find(
|
||||
(step) => !stepDone[step.id]
|
||||
)
|
||||
if (parallelStep) {
|
||||
return parallelStep.id
|
||||
}
|
||||
const setupStep = getFeatureWallSetupStepsForSection('setup').find((step) => !stepDone[step.id])
|
||||
return setupStep?.id ?? FEATURE_WALL_SETUP_STEPS[0].id
|
||||
}
|
||||
|
||||
@@ -24,6 +24,24 @@ describe('feature education telemetry event schemas', () => {
|
||||
expect(parsed.success).toBe(true)
|
||||
})
|
||||
|
||||
it('accepts setup-guide contextual tour sources for shown and outcome telemetry', () => {
|
||||
const shown = eventSchemas.contextual_tour_shown.safeParse({
|
||||
tour_id: 'workspace-agent-sessions',
|
||||
source: 'setup_guide_parallel_work',
|
||||
was_feature_previously_interacted: false
|
||||
})
|
||||
const outcome = eventSchemas.contextual_tour_outcome.safeParse({
|
||||
tour_id: 'workspace-agent-sessions',
|
||||
source: 'setup_guide_parallel_work',
|
||||
outcome: 'completed',
|
||||
steps_seen: 2,
|
||||
total_steps: 5
|
||||
})
|
||||
|
||||
expect(shown.success).toBe(true)
|
||||
expect(outcome.success).toBe(true)
|
||||
})
|
||||
|
||||
it('rejects contextual tour outcome payloads with impossible progress', () => {
|
||||
const parsed = eventSchemas.contextual_tour_outcome.safeParse({
|
||||
tour_id: 'tasks',
|
||||
|
||||
Reference in New Issue
Block a user