From 1df25c4943939e39c755c949074841e097f0ffc1 Mon Sep 17 00:00:00 2001 From: "posthog[bot]" <206114724+posthog[bot]@users.noreply.github.com> Date: Sat, 19 Sep 2026 02:00:36 +0000 Subject: [PATCH] fix(tui): pin the automatic JSX runtime in the build config Nothing under src/ imports React, so a classic-runtime transform emits unbound `React.*` references. tsdown had no JSX option and relied on tsconfig discovery. Pin `inputOptions.transform.jsx` to the automatic runtime, add a postbuild smoke check that the pin reaches every chunk, and turn the two module-scope subtitle fragments into components. Generated-By: PostHog Desktop Task-Id: 8fb56a6d-32ed-43b5-aa88-871a9f25117a --- scripts/smoke-test.sh | 21 ++++++++++++++----- src/ui/tui/screens/IntroScreenLayout.tsx | 4 ++-- .../screens/PostHogIntegrationIntroScreen.tsx | 6 +++--- tsdown.config.ts | 7 +++++++ 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/scripts/smoke-test.sh b/scripts/smoke-test.sh index 241bcab22..9edb609fe 100755 --- a/scripts/smoke-test.sh +++ b/scripts/smoke-test.sh @@ -3,11 +3,12 @@ # Postbuild smoke test for the compiled wizard binary. # # 1. Binary loads without crashing. -# 2. In production builds (WIZARD_BUILD_NODE_ENV != "ci"), --ci is rejected +# 2. Every .js chunk uses the automatic JSX runtime. +# 3. In production builds (WIZARD_BUILD_NODE_ENV != "ci"), --ci is rejected # with the tailored "CI mode is not currently supported" error and a # non-zero exit. Guards against a future change that re-enables --ci in # published builds without anyone noticing. -# 3. In production builds, the experimental headless flag IS accepted (the +# 4. In production builds, the experimental headless flag IS accepted (the # non-interactive published-build path) — it must not be rejected as an # unknown argument. It is intentionally undocumented; this check only keeps # the published binary from silently dropping the flag the cloud runs need. @@ -23,7 +24,17 @@ node --input-type=module -e "import '$DIST_BIN'" 2>&1 | head -5 | grep -q 'PostH exit 1 } -# ── 2. CI flag overrides physically absent from production builds ─────────── +# ── 2. Automatic JSX runtime ──────────────────────────────────────────────── +# Nothing under src/ imports React, so a classic-runtime transform emits `React.*` +# references that no module binds. A module-scope JSX value would then throw at +# import time and kill the run before the TUI draws. tsdown.config.ts pins the +# runtime; this asserts the pin still reaches the output. +if grep -q 'React\.createElement\|React\.Fragment' ./dist/*.js; then + echo 'Smoke test failed: a chunk was compiled with the classic JSX runtime' >&2 + exit 1 +fi + +# ── 3. CI flag overrides physically absent from production builds ─────────── # The override path (src/utils/ci-flag-overrides.ts) is dead code in published # builds and tsdown strips it; its env var name appearing in dist/*.js means # dead-code elimination regressed and a prod surface leaked. Sourcemaps keep @@ -58,7 +69,7 @@ else done fi -# ── 3. --ci rejected in production builds ──────────────────────────────────── +# ── 4. --ci rejected in production builds ──────────────────────────────────── # build:ci sets WIZARD_BUILD_NODE_ENV=ci → --ci stays enabled → skip the check. if [ "${WIZARD_BUILD_NODE_ENV:-production}" = "ci" ]; then exit 0 @@ -81,7 +92,7 @@ if ! echo "$output" | grep -qi 'CI mode is not currently supported'; then exit 1 fi -# ── 4. Experimental headless flag accepted in production builds ────────────── +# ── 5. Experimental headless flag accepted in production builds ────────────── # The non-interactive path for published builds (cloud / CI runs). yargs must # not reject the flag, and it must not fall through to the --ci rejection. With # no api-key the run exits fast on "Headless mode requires --api-key" — all this diff --git a/src/ui/tui/screens/IntroScreenLayout.tsx b/src/ui/tui/screens/IntroScreenLayout.tsx index 7e66cb37c..3016bfbd2 100644 --- a/src/ui/tui/screens/IntroScreenLayout.tsx +++ b/src/ui/tui/screens/IntroScreenLayout.tsx @@ -130,7 +130,7 @@ const WizardTitle = ({ title }: { title: string }) => ( ); /** Generic subtitle shown when a screen doesn't supply its own. */ -const DEFAULT_SUBTITLE = ( +const DefaultSubtitle = () => ( <> We'll use AI to analyze your project and complete work. @@ -251,7 +251,7 @@ export const IntroScreenLayout = ({ {showSubtitle && !showingPrivacy && ( - {subtitle ?? DEFAULT_SUBTITLE} + {subtitle ?? } )} diff --git a/src/ui/tui/screens/PostHogIntegrationIntroScreen.tsx b/src/ui/tui/screens/PostHogIntegrationIntroScreen.tsx index 3da96b496..c07d5160d 100644 --- a/src/ui/tui/screens/PostHogIntegrationIntroScreen.tsx +++ b/src/ui/tui/screens/PostHogIntegrationIntroScreen.tsx @@ -36,13 +36,13 @@ import { } from '@ui/tui/posthog-integration-intro'; /** - * Replaces IntroScreenLayout's DEFAULT_SUBTITLE for this screen only. The + * Replaces IntroScreenLayout's default subtitle for this screen only. The * shared default (".env* file contents will not leave your machine") is true * of values and false of variable names, which this screen reads and reports. * Two lines carry the fact and name the screen that holds the detail, so the * disclosure reaches people who never open it. */ -const SUBTITLE = ( +const Subtitle = () => ( <> We'll use AI to analyze your project and complete work. @@ -367,7 +367,7 @@ export const PostHogIntegrationIntroScreen = ({ installDir={session.installDir} title={title} showSubtitle={view === 'default'} - subtitle={SUBTITLE} + subtitle={} body={body} showDetection={showContinue} detectionRows={detectionRows} diff --git a/tsdown.config.ts b/tsdown.config.ts index 0d39ea907..dcd6e2dcc 100644 --- a/tsdown.config.ts +++ b/tsdown.config.ts @@ -23,6 +23,13 @@ export default defineConfig({ // Keep npm dependencies external — they're installed at runtime. skipNodeModulesBundle: true, + // Pin the automatic JSX runtime. Nothing under src/ imports React, so a + // classic-runtime transform would emit unbound `React.*` references. + // The object form is required — the `'react-jsx'` string form is ignored. + inputOptions: { + transform: { jsx: { runtime: 'automatic', importSource: 'react' } }, + }, + sourcemap: true, clean: true,