Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions __tests__/e2e/onboarding-invocation.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,37 @@ const IDE_CASES = [
downPresses: 0,
command: 'claude',
expectedArgs: ['--print', '--output-format', 'stream-json', '--verbose'],
expectedPromptSnippets: ['Confidence SDK', 'React', 'mcp__confidence-flags__'],
expectedPromptSnippets: [
'Confidence SDK',
'.claude/skills/analyze-project/SKILL.md',
'existing codebase',
'mcp__confidence-flags__',
],
},
{
name: 'Cursor',
downPresses: 1,
command: 'cursor',
expectedArgs: ['--print', '--output-format', 'stream-json', '--approve-mcps', '--yolo'],
expectedPromptSnippets: ['Confidence SDK', 'React', 'mcp__confidence-flags__'],
expectedPromptSnippets: [
'Confidence SDK',
'.cursor/skills/analyze-project/SKILL.md',
'existing codebase',
'mcp__confidence-flags__',
],
firstArg: 'agent',
},
{
name: 'Codex',
downPresses: 2,
command: 'codex',
expectedArgs: ['exec', '--json', '--sandbox', 'danger-full-access', '-'],
expectedPromptSnippets: ['Confidence SDK', 'React', 'confidence-flags:'],
expectedPromptSnippets: [
'Confidence SDK',
'.agents/skills/analyze-project/SKILL.md',
'existing codebase',
'confidence-flags:',
],
},
] as const;

Expand Down
2 changes: 1 addition & 1 deletion __tests__/e2e/skip-onboarding.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ describe('when the user skips onboarding', () => {
await session.waitForText("What's next?");

// Docs and dashboard links still appear
await session.waitForText('Docs:');
await session.waitForText('Documentation:');
await session.waitForText('Dashboard:');
});

Expand Down
2 changes: 1 addition & 1 deletion __tests__/e2e/skip-plugins.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ describe('when the user skips installing AI plugin', () => {
await session.waitForText('onboarding complete', { timeout: 60_000 });

// Done — onboarding ran so report file and code changes appear
await session.waitForText('What we set up');
await session.waitForText('What we have set up');
await session.waitForText('CONFIDENCE_QUICKSTART.md');
});
});
2 changes: 1 addition & 1 deletion __tests__/ui/screens/DoneScreen.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ describe('DoneScreen', () => {
using sut = renderScreen(<DoneScreen />, { screen: ScreenId.Done });
store.setCodeChanges(['Added @spotify-confidence/sdk', 'Created confidence.config.ts']);
await waitFor(() => {
expect(sut.lastFrame()).toContain('What we set up');
expect(sut.lastFrame()).toContain('What we have set up');
expect(sut.lastFrame()).toContain('confidence.config.ts');
});
});
Expand Down
1 change: 1 addition & 0 deletions src/integrations/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { join } from 'node:path';
import { SKILLS_BASE_URL } from '@lib/constants.js';

export const PLUGIN_SKILLS = [
'analyze-project',
'onboard-confidence',
'onboard-confidence-dry-run',
'setup-warehouse',
Expand Down
28 changes: 20 additions & 8 deletions src/lib/onboarding-prompt/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { ChosenIde, OnboardingGoal } from '../session.js';
import { addIf } from '../prompt-utils.js';
import { preflight, scaffold } from './preflight.js';
import { determineSDK, resolveClient } from './sdk.js';
import { integrateSDK } from './integrate.js';
import { integrateSDK, integrateViaSkill } from './integrate.js';
import { determineRecordingSDK, integrateRecording } from './session-recording.js';
import { migrateFlags } from './migration.js';
import { generateReport, summary, rules } from './report.js';
Expand Down Expand Up @@ -33,20 +33,32 @@ export function buildOnboardingPrompt({
hasPlugins = false,
}: PromptOptions): string {
const steps = new StepCounter(isEmptyProject ? 2 : 1);
const includeFlags = goal === 'feature-flags' || goal === 'all';
const includeRecording = goal === 'session-recording' || goal === 'all';
const tools = buildToolVars(ide);

const includingFlags = goal === 'feature-flags' || goal === 'all';
const includingRecording = goal === 'session-recording' || goal === 'all';
const usingSkill = includingFlags && hasPlugins;

const sections = [
preamble(framework, projectDir, isEmptyProject, goal),
preflight(tools),
addIf(isEmptyProject, () => scaffold(framework, steps.next())),
addIf(includeFlags, () => determineSDK(framework, steps.next(), tools)),
addIf(includeFlags, () => resolveClient(framework, steps.next(), tools)),
addIf(includeFlags, () => integrateSDK(steps.next(), steps.current - 2, isEmptyProject, tools)),
addIf(includeRecording, () => determineRecordingSDK(framework, steps.next(), tools)),
addIf(includeRecording, () => integrateRecording(steps.next(), isEmptyProject)),

usingSkill
? [integrateViaSkill(framework, steps.next(), isEmptyProject, ide)]
: [
addIf(includingFlags, () => determineSDK(framework, steps.next(), tools)),
addIf(includingFlags, () => resolveClient(framework, steps.next(), tools)),
addIf(includingFlags, () =>
integrateSDK(steps.next(), steps.current - 2, isEmptyProject, tools),
),
],

addIf(includingRecording, () => determineRecordingSDK(framework, steps.next(), tools)),
addIf(includingRecording, () => integrateRecording(steps.next(), isEmptyProject)),

...migrations.map((m) => migrateFlags(m, steps.next())),

generateReport({ step: steps.next(), isEmptyProject, goal, hasPlugins }),
summary(steps.next()),
rules(),
Expand Down
28 changes: 28 additions & 0 deletions src/lib/onboarding-prompt/integrate.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,34 @@
import type { ChosenIde } from '../session.js';
import { CONFIDENCE_DOCS_URL } from '../constants.js';
import { loadStep } from './steps/load.js';

const SKILLS_DIR: Record<ChosenIde, string> = {
claude: '.claude/skills',
cursor: '.cursor/skills',
codex: '.agents/skills',
};

export function integrateViaSkill(
framework: string,
step: number,
isEmptyProject: boolean,
ide: ChosenIde,
): string {
return loadStep('integrate-via-skill.md', {
STEP: step,
FRAMEWORK: framework,
SKILLS_DIR: SKILLS_DIR[ide],

DOMAIN_CONTEXT: isEmptyProject
? "The project was just scaffolded — treat the sample app's features as the domain."
: 'The project is an existing codebase. Study its code to understand the domain, UI flows, and business logic.',

INSERTION_HINT: isEmptyProject
? 'For fresh scaffolds, use the scaffold\'s default heading or welcome text as the insertion point — the "aha" moment works just as well on boilerplate. Demonstrate at least two use cases (e.g. a gradual rollout for a heading change and a kill switch for a feature section).'
: 'Read the top 2–3 candidate files and pick the best one: a single visible string or component, no complex conditionals already wrapping it, in a file the user will recognize.',
});
}

const REACT_GOTCHAS = `

**React/Next.js gotchas:**
Expand Down
31 changes: 31 additions & 0 deletions src/lib/onboarding-prompt/steps/integrate-via-skill.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
## {{STEP}}. Integrate feature flags

Print "STATUS: Analyzing project for flag integration points..."

Read `{{SKILLS_DIR}}/analyze-project/SKILL.md` as a **methodology reference** — use it for what to analyze, how to identify flag candidates, which SDK to pick, and how to create and wire flags. Ignore its output formatting entirely: no step tracker, no EDUCATE blocks, no AskUserQuestion calls.

Execute the skill's workflow automatically, without pausing for user input:

- **Analysis** (skill steps 1–5): scan the project, check for existing providers, identify flag candidates, look up best practices, and select the best proposal(s).
- **Implementation** (skill step 6): determine the SDK, set up the client, create the flag(s), install packages, add integration code, and verify the build.

{{DOMAIN_CONTEXT}}

{{INSERTION_HINT}}

**Output format — use this instead of anything in the skill:**

The only user-visible output is STATUS-prefixed lines (~60 chars max). No step tracker boxes, no EDUCATE blocks, no headers, no AskUserQuestion. Print STATUS lines before each phase and periodically within longer phases:

- "STATUS: Scanning for existing flag usage..."
- "STATUS: Determining the right Confidence SDK..."
- "STATUS: Resolving SDK client and secret..."
- "STATUS: Creating feature flags..."
- After each flag: "STATUS: Created flag: <flag-name>"
- "STATUS: Installing Confidence SDK packages..."
- "STATUS: Adding provider and flag evaluation code..."
- After wiring each flag: "STATUS: Integrated flag: <flag-name>"
- "STATUS: Verifying project builds..."

Read the client secret from CONFIDENCE_CLIENT_SECRET env var in all generated code.
Use the OpenFeature API with local resolve where supported. Access flag values via dot notation: `flag-name.property`.
4 changes: 2 additions & 2 deletions src/ui/tui/screens/about/AboutScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ export function AboutScreen() {

<Box flexDirection="column" marginTop={1}>
<Text>
<Text color={Colors.muted}>{'Docs: '}</Text>
<Text color={Colors.muted}>{' Docs: '}</Text>
<Text color={Colors.primary}>{CONFIDENCE_DOCS_URL}</Text>
</Text>
<Text>
<Text color={Colors.muted}>{'Dashboard: '}</Text>
<Text color={Colors.muted}>{'Dashboard: '}</Text>
<Text color={Colors.primary}>{CONFIDENCE_DASHBOARD_URL}</Text>
</Text>
</Box>
Expand Down
52 changes: 26 additions & 26 deletions src/ui/tui/screens/done/DoneScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ import { launchChatSession, getIntegration } from '@integrations/index.js';
import { useSession, $session } from '../../store.js';
import { track } from '@lib/telemetry.js';
import { doneActionSelected } from './telemetry-events.js';
import { useIsShort } from '@ui/tui/hooks/useIsShort.js';

const MAX_SHOWN_CHANGES = 5;

export function DoneScreen() {
const session = useSession();
const { exit } = useApp();
const isShort = useIsShort();
const narrow = useIsNarrow();
const { reportFile, codeChanges, projectDir, ide } = session;
const align = narrow ? HAlign.Left : HAlign.Center;
Expand All @@ -28,42 +30,40 @@ export function DoneScreen() {
</Text>
</Box>

{codeChanges.length > 0 && (
<Box flexDirection="column" marginBottom={1} alignItems={align}>
<Box>
<Text bold>What we set up:</Text>
{codeChanges.length > 0 && !isShort && (
<>
<Box alignItems={align}>
<Text bold>What we have set up:</Text>
</Box>
{codeChanges
.toReversed()
.slice(0, MAX_SHOWN_CHANGES)
.map((change, i) => (
<Box key={i} gap={1}>
<Text color={Colors.success}>{Icons.bullet}</Text>
<Text>{change}</Text>
</Box>
))}
</Box>
<Box flexDirection="column" marginBottom={1}>
{codeChanges
.toReversed()
.slice(0, MAX_SHOWN_CHANGES)
.map((change, i) => (
<Box key={i} gap={1}>
<Text color={Colors.success}>{Icons.bullet}</Text>
<Text>{change}</Text>
</Box>
))}
</Box>
</>
)}

{reportFile && (
<Box flexDirection="column" marginBottom={1} alignItems={align}>
<Text>Full details:</Text>
<Box gap={1}>
<Text color={Colors.success}>{Icons.bullet}</Text>
<Box flexDirection="column" marginTop={1}>
{reportFile && (
<Text color={Colors.muted}>
{' Full report: '}
<TerminalLink url={`file://${join(projectDir, reportFile)}`}>
{reportFile}
</TerminalLink>
</Box>
</Box>
)}

<Box flexDirection="column" alignItems={align}>
</Text>
)}
<Text color={Colors.muted}>
{'Docs: '}
{'Documentation: '}
<Text color={Colors.primary}>{CONFIDENCE_DOCS_URL}</Text>
</Text>
<Text color={Colors.muted}>
{'Dashboard: '}
{' Dashboard: '}
<Text color={Colors.primary}>{CONFIDENCE_DASHBOARD_URL}</Text>
</Text>
</Box>
Expand Down
6 changes: 3 additions & 3 deletions src/ui/tui/screens/welcome/WelcomeScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,12 @@ export function WelcomeScreen() {

<Box flexDirection="column">
<Text>
<Text color={Colors.muted}>{' Directory '}</Text>
<Text color={Colors.muted}>{'Directory '}</Text>
<Text color={Colors.success}>{Icons.check}</Text>
<Text> {dir}</Text>
</Text>
<Text>
<Text color={Colors.muted}>{' Framework '}</Text>
<Text color={Colors.muted}>{'Framework '}</Text>
{detectionAttempted ? (
<>
<Text color={frameworkColor}>{frameworkIcon}</Text>
Expand All @@ -116,7 +116,7 @@ export function WelcomeScreen() {
)}
</Text>
<Text>
<Text color={Colors.muted}>{' Telemetry '}</Text>
<Text color={Colors.muted}>{'Telemetry '}</Text>
{telemetryOn ? (
<>
<Text color={Colors.success}>{Icons.check}</Text>
Expand Down