orca/src/shared/contextual-tours.ts

235 lines
8.2 KiB
TypeScript
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.

import type { FeatureInteractionId } from './feature-interactions'
export type ContextualTourId =
| 'workspace-board'
| 'workspace-agent-sessions'
| 'browser'
| 'tasks'
| 'automations'
| 'floating-workspace'
| 'workspace-creation'
export type ContextualTourStepControl = {
kind: 'auto-rename-branch-from-work'
}
export type ContextualTourStepActionKind =
| 'next'
| 'complete'
| 'split-terminal-pane'
| 'create-worktree'
| 'show-worktrees'
| 'open-tasks'
| 'open-getting-started'
export type ContextualTourStepAction = {
kind: ContextualTourStepActionKind
label: string
}
export type ContextualTourStepPlacement = 'top' | 'right' | 'bottom' | 'left'
export type ContextualTourStep = {
// Stable anchor for localized copy — position-keyed translations shift onto the wrong step when one is inserted.
id?: string
title: string
body: string
targetSelector: string
requiredForStart?: boolean
fallbackCopy?: string
preferredPlacement?: ContextualTourStepPlacement
targetPulse?: boolean
hidePrimaryAction?: boolean
control?: ContextualTourStepControl
primaryAction?: ContextualTourStepAction
secondaryAction?: ContextualTourStepAction
advanceOnFeatureInteraction?: FeatureInteractionId
}
export type ContextualTour = {
id: ContextualTourId
allowedActiveModals?: readonly string[]
steps: readonly ContextualTourStep[]
}
export const CONTEXTUAL_TOURS = [
{
id: 'workspace-board',
steps: [
{
title: 'Plan work on the board',
body: 'Use the board when you want to see workspaces by status instead of by project.',
targetSelector: '[data-contextual-tour-target="workspace-board-center"]',
requiredForStart: true,
preferredPlacement: 'bottom'
},
{
title: 'Move work through lanes',
body: 'Drag workspaces between lanes as their status changes.',
targetSelector:
'[data-contextual-tour-target="workspace-board-done-lane"], [data-contextual-tour-target="workspace-board-lanes"]'
}
]
},
{
id: 'workspace-agent-sessions',
steps: [
{
title: 'Split a terminal pane',
body: 'Open a second terminal pane with {terminal.splitRight}, or right-click the pane for split options.',
targetSelector:
'[data-contextual-tour-target="terminal-pane-split-target"], [data-contextual-tour-target="workspace-agent-terminal-tip"]',
requiredForStart: true,
preferredPlacement: 'bottom',
primaryAction: { kind: 'split-terminal-pane', label: 'Split terminal' },
advanceOnFeatureInteraction: 'terminal-pane-split'
},
{
title: 'Start another task in parallel',
body: 'Each worktree gets its own branch, so parallel work stays separate.',
targetSelector: '[data-contextual-tour-target="workspace-create-control"]',
preferredPlacement: 'right',
targetPulse: true,
hidePrimaryAction: true
}
]
},
{
id: 'browser',
steps: [
{
title: 'Grab page context for agents',
body: "Use the grab tool to copy a page element's context for agents.",
targetSelector: '[data-contextual-tour-target="browser-grab-control"]',
requiredForStart: true,
preferredPlacement: 'bottom'
},
{
title: 'Mark design feedback in place',
body: 'Annotate elements and send those notes to an agent.',
targetSelector: '[data-contextual-tour-target="browser-annotation-control"]',
preferredPlacement: 'bottom'
},
{
title: 'Stay logged in',
body: 'Bring your existing logins into Orca to stay signed in immediately.',
// Prefer the always-visible Import button; fall back to the overflow-menu
// item only once the user has dismissed the import hint.
targetSelector:
'[data-contextual-tour-target="browser-import-hint"], [data-contextual-tour-target="browser-import-cookies-control"]',
// Sit below the Import button with the arrow pointing up at it.
preferredPlacement: 'bottom'
}
]
},
{
id: 'tasks',
steps: [
{
title: 'Choose the work source',
body: 'Switch between connected providers and project filters without changing pages.',
targetSelector: '[data-contextual-tour-target="tasks-source-filters"]',
requiredForStart: true
},
{
title: 'Filter to the work you need',
body: 'Use presets and search to narrow issues, reviews, merge requests, or tasks.',
targetSelector: '[data-contextual-tour-target="tasks-search-presets"]'
},
{
title: 'Start from work items',
body: 'Use Start or Open on a task, issue, review, or merge request to bring its context into a workspace.',
targetSelector:
'[data-contextual-tour-target="tasks-start-workspace"], [data-contextual-tour-target="tasks-actions"], [data-contextual-tour-target="tasks-search-presets"]'
}
]
},
{
id: 'automations',
steps: [
{
id: 'automations-intro',
title: 'What is an automation?',
body: 'Automations run agent work on a schedule. Add an automation by clicking this button.',
targetSelector: '[data-contextual-tour-target="automations-create"]',
requiredForStart: true
},
{
id: 'automations-results',
title: 'Find the results',
body: 'Runs show when automations ran, what happened, and where to inspect their output.',
targetSelector: '[data-contextual-tour-target="automations-runs"]'
}
]
},
{
id: 'floating-workspace',
steps: [
{
title: 'Run an agent across every repo',
body: 'Agents here run in any folder you choose. Point one at the directory above your services to work across all your repos at once.',
// Why: the per-action anchors only render in the empty state; fall back
// to the panel surface when floating tabs already exist.
targetSelector:
'[data-contextual-tour-target="floating-workspace-new-terminal"], [data-contextual-tour-target="floating-workspace-surface"]',
requiredForStart: true,
preferredPlacement: 'left'
},
{
title: 'Or use it as a scratchpad',
body: 'Open agents, scratch terminals, notes, and browser tabs without cluttering the worktree youre focused on.',
targetSelector:
'[data-contextual-tour-target="floating-workspace-new-markdown"], [data-contextual-tour-target="floating-workspace-surface"]',
preferredPlacement: 'left'
}
]
},
{
id: 'workspace-creation',
allowedActiveModals: ['new-workspace-composer'],
steps: [
{
title: 'Pick a project',
body: 'Orca isolates each task in its own worktree, branched off your base.',
targetSelector: '[data-contextual-tour-target="workspace-creation-project"]',
requiredForStart: true
},
{
title: 'Name it, or start from existing work',
body: 'Start from a linked task for a short issue or PR name. Or leave it blank to auto-name it from your first agent message.',
targetSelector: '[data-contextual-tour-target="workspace-creation-name"]',
control: { kind: 'auto-rename-branch-from-work' }
},
{
title: 'Choose what agent starts the work',
body: 'Pick the agent that should be opened when this worktree is created.',
targetSelector: '[data-contextual-tour-target="workspace-creation-agent"]'
}
]
}
] as const satisfies readonly ContextualTour[]
export const CONTEXTUAL_TOUR_IDS = CONTEXTUAL_TOURS.map((tour) => tour.id)
export function isContextualTourId(value: unknown): value is ContextualTourId {
return typeof value === 'string' && CONTEXTUAL_TOUR_IDS.includes(value as ContextualTourId)
}
export function getContextualTour(id: ContextualTourId): ContextualTour {
return CONTEXTUAL_TOURS.find((tour) => tour.id === id)!
}
export function normalizeContextualTourIds(value: unknown): ContextualTourId[] {
if (!Array.isArray(value)) {
return []
}
const seen = new Set<ContextualTourId>()
for (const item of value) {
if (isContextualTourId(item)) {
seen.add(item)
}
}
return [...seen]
}