Skip to content

Commit d8b5395

Browse files
bespoyasovclaude
andauthored
feat(quickstart): adjust welcome screen content when features preset (#113)
* chore: add string utils * feat: reflect preset features on welcome screen * fix(quickstart): deduplicate --features goals to prevent repeated labels Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(quickstart): preserve flag example step when combined with other goals Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(quickstart): guard welcome content against empty preset goals Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(quickstart): show incompatibility notice on welcome screen when preset features --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 6fcecf8 commit d8b5395

18 files changed

Lines changed: 466 additions & 74 deletions

File tree

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { capitalize, conjoin } from '../../src/utils/strings.js';
2+
3+
describe('capitalize', () => {
4+
const sut = capitalize;
5+
6+
it('uppercases the first character', () => {
7+
expect(sut('hello')).toBe('Hello');
8+
});
9+
10+
it('leaves an already-capitalized string unchanged', () => {
11+
expect(sut('Hello')).toBe('Hello');
12+
});
13+
14+
it('handles a single character', () => {
15+
expect(sut('a')).toBe('A');
16+
});
17+
18+
it('handles an empty string', () => {
19+
expect(sut('')).toBe('');
20+
});
21+
});
22+
23+
describe('conjoin', () => {
24+
const sut = conjoin;
25+
26+
it('returns an empty string for an empty list', () => {
27+
expect(sut([])).toBe('');
28+
});
29+
30+
it('returns the single item for a one-element list', () => {
31+
expect(sut(['apples'])).toBe('apples');
32+
});
33+
34+
it('joins two items with "and"', () => {
35+
expect(sut(['apples', 'oranges'])).toBe('apples and oranges');
36+
});
37+
38+
it('joins three items with an Oxford comma', () => {
39+
expect(sut(['apples', 'oranges', 'bananas'])).toBe('apples, oranges, and bananas');
40+
});
41+
42+
it('joins four items with an Oxford comma', () => {
43+
expect(sut(['a', 'b', 'c', 'd'])).toBe('a, b, c, and d');
44+
});
45+
46+
it('accepts a custom conjunction', () => {
47+
expect(sut(['apples', 'oranges', 'bananas'], 'or')).toBe('apples, oranges, or bananas');
48+
});
49+
50+
it('uses custom conjunction for two items', () => {
51+
expect(sut(['apples', 'oranges'], 'or')).toBe('apples or oranges');
52+
});
53+
});

‎packages/core/src/utils/index.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export { ensureJsonString, validateJsonString } from './json.js';
22
export { addIf, interpolate } from './prompt-utils.js';
33
export { isNewerVersion } from './semver.js';
4+
export { capitalize, conjoin } from './strings.js';

‎packages/core/src/utils/strings.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export function capitalize(s: string): string {
2+
return s.charAt(0).toUpperCase() + s.slice(1);
3+
}
4+
5+
export function conjoin(items: string[], conjunction = 'and'): string {
6+
if (items.length <= 2) return items.join(` ${conjunction} `);
7+
return items.slice(0, -1).join(', ') + `, ${conjunction} ` + items.at(-1);
8+
}

‎packages/quickstart/__tests__/e2e/__snapshots__/empty-project.e2e.ts.snap‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
22

3-
exports[`when the project is empty > shows "Select framework" instead of "Start setup" on the Welcome screen > welcome-no-framework 1`] = `
3+
exports[`when the project is empty > shows "Change framework" instead of "Start setup" on the Welcome screen > welcome-no-framework 1`] = `
44
" Confidence by Spotify https://confidence.spotify.com/
55
66
77
Confidence Quickstart
8-
98
Feature flags and experiments, set up with AI in minutes.
109
This wizard will help you get started with Confidence.
1110
@@ -24,7 +23,7 @@ exports[`when the project is empty > shows "Select framework" instead of "Start
2423
──────────────────────────────────────────────────────────────────────────────────────────────────
2524
Select your framework
2625
27-
❯ Select framework
26+
❯ Change framework
2827
About Confidence
2928
Quit
3029
@@ -36,7 +35,6 @@ exports[`when the project is empty > shows "Start setup" after selecting a frame
3635
3736
3837
Confidence Quickstart
39-
4038
Feature flags and experiments, set up with AI in minutes.
4139
This wizard will help you get started with Confidence.
4240

‎packages/quickstart/__tests__/e2e/__snapshots__/happy-path.e2e.ts.snap‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,6 @@ exports[`happy-path flow > navigates Welcome → SystemCheck → SelectGoal →
155155
156156
157157
Confidence Quickstart
158-
159158
Feature flags and experiments, set up with AI in minutes.
160159
This wizard will help you get started with Confidence.
161160

‎packages/quickstart/__tests__/e2e/__snapshots__/welcome-navigation.e2e.ts.snap‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ exports[`welcome screen navigation > exits cleanly on Quit > welcome-quit-select
55
66
77
Confidence Quickstart
8-
98
Feature flags and experiments, set up with AI in minutes.
109
This wizard will help you get started with Confidence.
1110
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`welcome screen with preset features > shows incompatibility notice when recordings preset on non-browser framework > welcome-preset-recordings-incompatible 1`] = `
4+
" Confidence by Spotify https://confidence.spotify.com/
5+
6+
7+
Confidence Quickstart
8+
Session recordings, set up with AI in minutes.
9+
Recordings not supported for non-browser frameworks.
10+
11+
1. It will check your system
12+
2. Sign you in to Confidence workspace
13+
3. Teach your AI agent about Confidence
14+
4. Integrate the SDK into your project
15+
5. Set up session recordings in your project
16+
17+
18+
Directory ✔ <project-dir>
19+
Framework ◆ Python (detected)
20+
Telemetry ✘ off
21+
22+
23+
──────────────────────────────────────────────────────────────────────────────────────────────────
24+
Ready to get started?
25+
26+
❯ Change framework
27+
About Confidence
28+
Quit
29+
30+
↑↓ navigate enter confirm vX.Y.Z"
31+
`;
32+
33+
exports[`welcome screen with preset features > shows tailored intro for preset features > welcome-preset-events 1`] = `
34+
" Confidence by Spotify https://confidence.spotify.com/
35+
36+
37+
Confidence Quickstart
38+
Event tracking, set up with AI in minutes.
39+
This wizard will set up event tracking in your project.
40+
41+
1. It will check your system
42+
2. Sign you in to Confidence workspace
43+
3. Teach your AI agent about Confidence
44+
4. Integrate the SDK into your project
45+
5. Set up event tracking in your project
46+
47+
48+
Directory ✔ <project-dir>
49+
Framework ✔ React (detected)
50+
Telemetry ✘ off
51+
52+
53+
──────────────────────────────────────────────────────────────────────────────────────────────────
54+
Ready to get started?
55+
56+
❯ Start setup
57+
Change framework
58+
About Confidence
59+
Quit
60+
61+
↑↓ navigate enter confirm vX.Y.Z"
62+
`;
63+
64+
exports[`welcome screen with preset features > shows tailored tagline for multiple preset features > welcome-preset-flags-events 1`] = `
65+
" Confidence by Spotify https://confidence.spotify.com/
66+
67+
68+
Confidence Quickstart
69+
Feature flags and event tracking, set up with AI in minutes.
70+
This wizard will set up feature flags and event tracking in your project.
71+
72+
1. It will check your system
73+
2. Sign you in to Confidence workspace
74+
3. Teach your AI agent about Confidence
75+
4. Integrate the SDK into your project
76+
5. Show a working feature flag example
77+
6. Set up event tracking in your project
78+
79+
80+
Directory ✔ <project-dir>
81+
Framework ✔ React (detected)
82+
Telemetry ✘ off
83+
84+
85+
──────────────────────────────────────────────────────────────────────────────────────────────────
86+
Ready to get started?
87+
88+
❯ Start setup
89+
Change framework
90+
About Confidence
91+
Quit
92+
93+
↑↓ navigate enter confirm vX.Y.Z"
94+
`;
95+
96+
exports[`welcome screen with preset features > shows tailored tagline for single preset feature > welcome-preset-flags 1`] = `
97+
" Confidence by Spotify https://confidence.spotify.com/
98+
99+
100+
Confidence Quickstart
101+
Feature flags, set up with AI in minutes.
102+
This wizard will set up feature flags in your project.
103+
104+
1. It will check your system
105+
2. Sign you in to Confidence workspace
106+
3. Teach your AI agent about Confidence
107+
4. Integrate the SDK into your project
108+
5. Show a working feature flag example
109+
110+
111+
Directory ✔ <project-dir>
112+
Framework ✔ React (detected)
113+
Telemetry ✘ off
114+
115+
116+
──────────────────────────────────────────────────────────────────────────────────────────────────
117+
Ready to get started?
118+
119+
❯ Start setup
120+
Change framework
121+
About Confidence
122+
Quit
123+
124+
↑↓ navigate enter confirm vX.Y.Z"
125+
`;

‎packages/quickstart/__tests__/e2e/empty-project.e2e.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
import { createSession } from '@spotify-confidence/testing/e2e';
22

33
describe('when the project is empty', () => {
4-
it('shows "Select framework" instead of "Start setup" on the Welcome screen', async () => {
4+
it('shows "Change framework" instead of "Start setup" on the Welcome screen', async () => {
55
using session = createSession({ project: 'empty' });
66

77
await session.waitForText('Confidence Quickstart');
88
await session.waitForText('Could not auto-detect');
99
await session.waitForText('Select your framework');
10-
await session.waitForText('Select framework');
10+
await session.waitForText('Change framework');
1111
expect(session.snapshot()).toMatchSnapshot('welcome-no-framework');
1212
});
1313

1414
it('shows "Start setup" after selecting a framework', async () => {
1515
using session = createSession({ project: 'empty' });
1616

1717
// Welcome — no framework detected
18-
await session.waitForText('Select framework');
18+
await session.waitForText('Change framework');
1919
await session.press('Enter');
2020

2121
// SelectFramework

‎packages/quickstart/__tests__/e2e/goal-flag.e2e.ts‎

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -45,45 +45,32 @@ describe('--features flag', () => {
4545
});
4646

4747
describe('incompatible recordings preset', () => {
48-
it('shows error when recordings are pre-set with a non-browser SDK', async () => {
49-
using session = createSession({
50-
project: 'python-statsig',
51-
extraArgs: ['--features', 'recordings'],
52-
});
53-
54-
await navigatePastWelcome(session);
55-
56-
await session.waitForText('Incompatible feature selection');
57-
await session.waitForText('Session recordings are not available');
58-
await session.waitForText('Quit');
59-
});
60-
61-
it('offers continue option when other goals are also pre-set', async () => {
48+
it('blocks start when recordings and other goals are pre-set on non-browser SDK', async () => {
6249
using session = createSession({
6350
project: 'python-statsig',
6451
extraArgs: ['--features', 'flags', '--features', 'recordings'],
6552
});
6653

67-
await navigatePastWelcome(session);
68-
69-
await session.waitForText('Incompatible feature selection');
70-
await session.waitForText('Continue without recordings');
54+
await session.waitForText('Recordings not supported');
55+
await session.waitForText('Change framework');
56+
expect(session.screen).not.toContain('Start setup');
7157
});
7258

73-
it('advances with remaining goals after choosing continue', async () => {
59+
it('advances after changing to a compatible framework', async () => {
7460
using session = createSession({
7561
project: 'python-statsig',
7662
extraArgs: ['--features', 'flags', '--features', 'recordings'],
7763
});
7864

79-
await navigatePastWelcome(session);
65+
await session.waitForText('Change framework');
66+
await session.press('Enter');
8067

81-
await session.waitForText('Continue without recordings');
68+
// SelectFramework — pick React (first option, browser SDK)
69+
await session.waitForText('Select Framework');
8270
await session.press('Enter');
8371

84-
// Advances to Authenticate after goal resolution
85-
await navigatePastAuth(session);
86-
await session.waitForText('Which CLI agent would you like to use?');
72+
// Back to Welcome — now compatible, Start setup appears
73+
await session.waitForText('Start setup');
8774
});
8875

8976
it('skips goal screen when recordings are pre-set with a browser SDK', async () => {
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { createSession } from '@spotify-confidence/testing/e2e';
2+
3+
describe('welcome screen with preset features', () => {
4+
it('shows tailored tagline for single preset feature', async () => {
5+
using session = createSession({ extraArgs: ['--features', 'flags'] });
6+
7+
await session.waitForText('Feature flags, set up with AI in minutes');
8+
await session.waitForText('Start setup');
9+
expect(session.snapshot()).toMatchSnapshot('welcome-preset-flags');
10+
});
11+
12+
it('shows tailored tagline for multiple preset features', async () => {
13+
using session = createSession({
14+
extraArgs: ['--features', 'flags', '--features', 'events'],
15+
});
16+
17+
await session.waitForText('Feature flags and event tracking, set up with AI in minutes');
18+
await session.waitForText('Start setup');
19+
expect(session.snapshot()).toMatchSnapshot('welcome-preset-flags-events');
20+
});
21+
22+
it('shows tailored intro for preset features', async () => {
23+
using session = createSession({ extraArgs: ['--features', 'events'] });
24+
25+
await session.waitForText('set up event tracking in your project');
26+
await session.waitForText('Start setup');
27+
expect(session.snapshot()).toMatchSnapshot('welcome-preset-events');
28+
});
29+
30+
it('shows tailored step 5 for non-flag preset', async () => {
31+
using session = createSession({ extraArgs: ['--features', 'events'] });
32+
33+
await session.waitForText('Set up event tracking in your project');
34+
});
35+
36+
it('preserves default step 5 for flags-only preset', async () => {
37+
using session = createSession({ extraArgs: ['--features', 'flags'] });
38+
39+
await session.waitForText('Show a working feature flag example');
40+
});
41+
42+
it('shows incompatibility notice when recordings preset on non-browser framework', async () => {
43+
using session = createSession({
44+
project: 'python-statsig',
45+
extraArgs: ['--features', 'recordings'],
46+
});
47+
48+
await session.waitForText('Recordings not supported');
49+
await session.waitForText('Change framework');
50+
expect(session.snapshot()).not.toContain('Start setup');
51+
expect(session.snapshot()).toMatchSnapshot('welcome-preset-recordings-incompatible');
52+
});
53+
});

0 commit comments

Comments
 (0)