diff --git a/__tests__/e2e/select-goal.e2e.ts b/__tests__/e2e/select-goal.e2e.ts
index d58bd48..fe67b29 100644
--- a/__tests__/e2e/select-goal.e2e.ts
+++ b/__tests__/e2e/select-goal.e2e.ts
@@ -93,6 +93,41 @@ describe('SelectGoal screen', () => {
await session.waitForText('Toggle features to set up');
});
+ it('shows plain time estimate for a single goal', async () => {
+ using session = createSession();
+
+ await navigateToGoalSelection(session);
+ session.checkpoint();
+ await session.press('Space');
+ await session.press('Enter');
+
+ // OnboardProject — confirm start
+ await session.waitForText('Start onboarding?');
+ await session.press('Enter');
+
+ await session.waitForText('3–5 min.');
+ expect(session.snapshot()).not.toContain('per feature');
+ });
+
+ it('shows per-feature time estimate for multiple goals', async () => {
+ using session = createSession();
+
+ await navigateToGoalSelection(session);
+ session.checkpoint();
+
+ // Toggle Feature Flags (1st) and Event Tracking (2nd)
+ await session.press('Space');
+ await session.press('ArrowDown');
+ await session.press('Space');
+ await session.press('Enter');
+
+ // OnboardProject — confirm start
+ await session.waitForText('Start onboarding?');
+ await session.press('Enter');
+
+ await session.waitForText('3–5 min per feature');
+ });
+
it('shows goal selection for non-browser project without recording option', async () => {
using session = createSession({ project: 'statsig-node' });
diff --git a/__tests__/ui/screens/OnboardingFlow.test.tsx b/__tests__/ui/screens/OnboardingFlow.test.tsx
index 2befd60..bbb3501 100644
--- a/__tests__/ui/screens/OnboardingFlow.test.tsx
+++ b/__tests__/ui/screens/OnboardingFlow.test.tsx
@@ -206,6 +206,50 @@ describe('Onboarding flow', () => {
});
});
+ it('shows plain time estimate for a single goal', async () => {
+ using project = createProjectDir();
+ mockNextSpawn({ hang: true });
+
+ using sut = renderScreen(, {
+ screen: ScreenId.OnboardProject,
+ dir: project.path,
+ goals: ['feature-flags'],
+ });
+
+ await waitFor(() => {
+ expect(sut.lastFrame()).toContain('Start onboarding?');
+ });
+
+ sut.stdin.write(ENTER);
+
+ await waitFor(() => {
+ const frame = sut.lastFrame()!;
+ expect(frame).toContain('3–5 min.');
+ expect(frame).not.toContain('per feature');
+ });
+ });
+
+ it('shows per-feature time estimate for multiple goals', async () => {
+ using project = createProjectDir();
+ mockNextSpawn({ hang: true });
+
+ using sut = renderScreen(, {
+ screen: ScreenId.OnboardProject,
+ dir: project.path,
+ goals: ['feature-flags', 'session-recordings'],
+ });
+
+ await waitFor(() => {
+ expect(sut.lastFrame()).toContain('Start onboarding?');
+ });
+
+ sut.stdin.write(ENTER);
+
+ await waitFor(() => {
+ expect(sut.lastFrame()).toContain('3–5 min per feature');
+ });
+ });
+
it('shows choose-sdk prompt for empty project', async () => {
using project = createProjectDir('empty');
mockNextSpawn({ hang: true });
diff --git a/src/integrations/utils.ts b/src/integrations/utils.ts
index 43784ab..5b0e55c 100644
--- a/src/integrations/utils.ts
+++ b/src/integrations/utils.ts
@@ -10,8 +10,18 @@ export function normalizeStatusLine(line: StatusLine) {
return line.slice(STATUS_PREFIX.length);
}
+const MAX_REPORT_LINE_LENGTH = 60;
+
export function normalizeReportLine(line: string) {
- return isStatusLine(line) ? normalizeStatusLine(line) : line;
+ const stripped = isStatusLine(line) ? normalizeStatusLine(line) : line;
+ const clean = stripMarkdown(stripped);
+ return clean.length > MAX_REPORT_LINE_LENGTH
+ ? clean.slice(0, MAX_REPORT_LINE_LENGTH - 1) + '…'
+ : clean;
+}
+
+function stripMarkdown(text: string) {
+ return text.replace(/\*\*(.*?)\*\*/g, '$1').replace(/`([^`]+)`/g, '$1');
}
export function spawnErrorMessage(bin: string, err: NodeJS.ErrnoException): string {
diff --git a/src/ui/tui/screens/onboard-project/OnboardProjectScreen.tsx b/src/ui/tui/screens/onboard-project/OnboardProjectScreen.tsx
index 9701308..c45740c 100644
--- a/src/ui/tui/screens/onboard-project/OnboardProjectScreen.tsx
+++ b/src/ui/tui/screens/onboard-project/OnboardProjectScreen.tsx
@@ -90,6 +90,7 @@ export function OnboardProjectScreen() {
prompt={
void;
onConfirmStart: () => void;
onConfirmSkip: () => void;
@@ -15,6 +17,7 @@ type OnboardingBottomPromptProps = {
export function OnboardingBottomPrompt({
phase,
+ goals,
selectSdk,
onConfirmStart,
onConfirmSkip,
@@ -52,7 +55,13 @@ export function OnboardingBottomPrompt({
/>
);
case 'onboarding':
- return ;
+ return (
+ 1 ? ' per feature' : ''}.`}
+ onCancel={onCancel}
+ />
+ );
case 'error':
return (