From aef4db41172795e607b8777b4ec7723af1ea4b3c Mon Sep 17 00:00:00 2001 From: Guilhem Lemouel Date: Tue, 7 Jul 2026 09:40:56 +0200 Subject: [PATCH] docs: clarify raw-app index.tsx must mount App to avoid blank screen Co-Authored-By: Claude Opus 4.8 (1M context) --- cli/src/guidance/skills.gen.ts | 16 +++++++++++++++- .../src/lib/components/copilot/chat/app/core.ts | 10 ++++++++++ system_prompts/auto-generated/prompts.ts | 16 +++++++++++++++- .../auto-generated/skills/raw-app/SKILL.md | 16 +++++++++++++++- system_prompts/base/raw-app.md | 16 +++++++++++++++- 5 files changed, 70 insertions(+), 4 deletions(-) diff --git a/cli/src/guidance/skills.gen.ts b/cli/src/guidance/skills.gen.ts index 298db50ca4..d04affa93a 100644 --- a/cli/src/guidance/skills.gen.ts +++ b/cli/src/guidance/skills.gen.ts @@ -5585,7 +5585,21 @@ A raw app has three logical parts: ### Entrypoint -\`index.tsx\` is the bundling entrypoint. It typically renders a top-level \`App\` component. The bundler is esbuild. +\`index.tsx\` is the bundling **and mount** entrypoint (esbuild bundles it; the preview then executes it against an empty \`
\`). It MUST itself mount a top-level \`App\` component into \`#root\` — nothing is auto-rendered for you. Keep your UI in \`App.tsx\` (or \`App.svelte\` / \`App.vue\`) and keep \`index.tsx\` as the mount shim. + +React: + +\`\`\`tsx +import React from 'react' +import { createRoot } from 'react-dom/client' +import App from './App' + +createRoot(document.getElementById('root')!).render() +\`\`\` + +Svelte: \`mount(App, { target: document.getElementById('root')! })\`. Vue: \`createApp(App).mount('#root')\`. + +**Never turn \`index.tsx\` into a bare component** (e.g. \`export default function App() { ... }\` with no \`createRoot(...).render(...)\`). A component that is defined but never mounted renders **a blank screen with NO error thrown** — the JSX never executes, so nothing surfaces in the console or the error overlay. If the app is blank, the first thing to check is that \`index.tsx\` actually calls \`createRoot(document.getElementById('root')!).render()\`. **Always begin every React file (\`.tsx\`/\`.jsx\`) that uses JSX with \`import React from 'react'\`.** esbuild uses the classic JSX transform, so \`React\` must be in scope wherever JSX appears — a missing import compiles fine but throws \`React is not defined\` at runtime, leaving a blank screen. diff --git a/frontend/src/lib/components/copilot/chat/app/core.ts b/frontend/src/lib/components/copilot/chat/app/core.ts index 599a77e906..872957bd5a 100644 --- a/frontend/src/lib/components/copilot/chat/app/core.ts +++ b/frontend/src/lib/components/copilot/chat/app/core.ts @@ -925,6 +925,16 @@ export function prepareAppSystemMessage(customPrompt?: string): ChatCompletionSy ### Frontend - The frontend is bundled using esbuild with entrypoint \`index.tsx\` +- \`index.tsx\` is also the **mount** entrypoint: it MUST mount a top-level \`App\` into \`#root\` — nothing is auto-rendered. Keep your UI in \`App.tsx\` and keep \`index.tsx\` as the mount shim: + \`\`\`tsx + import React from 'react' + import { createRoot } from 'react-dom/client' + import App from './App' + + createRoot(document.getElementById('root')!).render() + \`\`\` + (Svelte: \`mount(App, { target: document.getElementById('root')! })\`; Vue: \`createApp(App).mount('#root')\`.) +- **Never replace \`index.tsx\` with a bare component.** A component that is defined but never mounted produces a **blank screen with NO error** (the JSX never runs, so nothing throws). If the app renders blank, first verify \`index.tsx\` calls \`createRoot(document.getElementById('root')!).render()\`. - Frontend files are managed separately from backend runnables - The \`wmill.d.ts\` file is generated automatically from the backend runnables shape - Begin every React file (\`.tsx\`/\`.jsx\`) that uses JSX with \`import React from 'react'\`. Raw apps bundle with the classic JSX transform, so \`React\` must be in scope wherever JSX is used — a missing import compiles fine but throws \`React is not defined\` at runtime. diff --git a/system_prompts/auto-generated/prompts.ts b/system_prompts/auto-generated/prompts.ts index 16ee0616bc..7cab373fcd 100644 --- a/system_prompts/auto-generated/prompts.ts +++ b/system_prompts/auto-generated/prompts.ts @@ -611,7 +611,21 @@ A raw app has three logical parts: ### Entrypoint -\`index.tsx\` is the bundling entrypoint. It typically renders a top-level \`App\` component. The bundler is esbuild. +\`index.tsx\` is the bundling **and mount** entrypoint (esbuild bundles it; the preview then executes it against an empty \`
\`). It MUST itself mount a top-level \`App\` component into \`#root\` — nothing is auto-rendered for you. Keep your UI in \`App.tsx\` (or \`App.svelte\` / \`App.vue\`) and keep \`index.tsx\` as the mount shim. + +React: + +\`\`\`tsx +import React from 'react' +import { createRoot } from 'react-dom/client' +import App from './App' + +createRoot(document.getElementById('root')!).render() +\`\`\` + +Svelte: \`mount(App, { target: document.getElementById('root')! })\`. Vue: \`createApp(App).mount('#root')\`. + +**Never turn \`index.tsx\` into a bare component** (e.g. \`export default function App() { ... }\` with no \`createRoot(...).render(...)\`). A component that is defined but never mounted renders **a blank screen with NO error thrown** — the JSX never executes, so nothing surfaces in the console or the error overlay. If the app is blank, the first thing to check is that \`index.tsx\` actually calls \`createRoot(document.getElementById('root')!).render()\`. **Always begin every React file (\`.tsx\`/\`.jsx\`) that uses JSX with \`import React from 'react'\`.** esbuild uses the classic JSX transform, so \`React\` must be in scope wherever JSX appears — a missing import compiles fine but throws \`React is not defined\` at runtime, leaving a blank screen. diff --git a/system_prompts/auto-generated/skills/raw-app/SKILL.md b/system_prompts/auto-generated/skills/raw-app/SKILL.md index 624fdc97ac..f95358a655 100644 --- a/system_prompts/auto-generated/skills/raw-app/SKILL.md +++ b/system_prompts/auto-generated/skills/raw-app/SKILL.md @@ -249,7 +249,21 @@ A raw app has three logical parts: ### Entrypoint -`index.tsx` is the bundling entrypoint. It typically renders a top-level `App` component. The bundler is esbuild. +`index.tsx` is the bundling **and mount** entrypoint (esbuild bundles it; the preview then executes it against an empty `
`). It MUST itself mount a top-level `App` component into `#root` — nothing is auto-rendered for you. Keep your UI in `App.tsx` (or `App.svelte` / `App.vue`) and keep `index.tsx` as the mount shim. + +React: + +```tsx +import React from 'react' +import { createRoot } from 'react-dom/client' +import App from './App' + +createRoot(document.getElementById('root')!).render() +``` + +Svelte: `mount(App, { target: document.getElementById('root')! })`. Vue: `createApp(App).mount('#root')`. + +**Never turn `index.tsx` into a bare component** (e.g. `export default function App() { ... }` with no `createRoot(...).render(...)`). A component that is defined but never mounted renders **a blank screen with NO error thrown** — the JSX never executes, so nothing surfaces in the console or the error overlay. If the app is blank, the first thing to check is that `index.tsx` actually calls `createRoot(document.getElementById('root')!).render()`. **Always begin every React file (`.tsx`/`.jsx`) that uses JSX with `import React from 'react'`.** esbuild uses the classic JSX transform, so `React` must be in scope wherever JSX appears — a missing import compiles fine but throws `React is not defined` at runtime, leaving a blank screen. diff --git a/system_prompts/base/raw-app.md b/system_prompts/base/raw-app.md index 004302cc74..a3c06538b3 100644 --- a/system_prompts/base/raw-app.md +++ b/system_prompts/base/raw-app.md @@ -14,7 +14,21 @@ A raw app has three logical parts: ### Entrypoint -`index.tsx` is the bundling entrypoint. It typically renders a top-level `App` component. The bundler is esbuild. +`index.tsx` is the bundling **and mount** entrypoint (esbuild bundles it; the preview then executes it against an empty `
`). It MUST itself mount a top-level `App` component into `#root` — nothing is auto-rendered for you. Keep your UI in `App.tsx` (or `App.svelte` / `App.vue`) and keep `index.tsx` as the mount shim. + +React: + +```tsx +import React from 'react' +import { createRoot } from 'react-dom/client' +import App from './App' + +createRoot(document.getElementById('root')!).render() +``` + +Svelte: `mount(App, { target: document.getElementById('root')! })`. Vue: `createApp(App).mount('#root')`. + +**Never turn `index.tsx` into a bare component** (e.g. `export default function App() { ... }` with no `createRoot(...).render(...)`). A component that is defined but never mounted renders **a blank screen with NO error thrown** — the JSX never executes, so nothing surfaces in the console or the error overlay. If the app is blank, the first thing to check is that `index.tsx` actually calls `createRoot(document.getElementById('root')!).render()`. **Always begin every React file (`.tsx`/`.jsx`) that uses JSX with `import React from 'react'`.** esbuild uses the classic JSX transform, so `React` must be in scope wherever JSX appears — a missing import compiles fine but throws `React is not defined` at runtime, leaving a blank screen.