From a63255e56bb1199ff9b0106abda6ccce5d6d6d60 Mon Sep 17 00:00:00 2001 From: Alex Bespoyasov Date: Tue, 8 Sep 2026 16:14:59 +0200 Subject: [PATCH 1/2] fix: normalize report lines Strip Markdown, prevent text blow out. --- src/integrations/utils.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) 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 { From 6e2dece6264e68bf269a2af423301e9160cf040b Mon Sep 17 00:00:00 2001 From: Alex Bespoyasov Date: Tue, 8 Sep 2026 16:19:46 +0200 Subject: [PATCH 2/2] chore: manage expectations about multi-goal onboarding duration --- __tests__/e2e/select-goal.e2e.ts | 35 +++++++++++++++ __tests__/ui/screens/OnboardingFlow.test.tsx | 44 +++++++++++++++++++ .../onboard-project/OnboardProjectScreen.tsx | 1 + .../components/OnboardingBottomPrompt.tsx | 11 ++++- 4 files changed, 90 insertions(+), 1 deletion(-) 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/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 (