diff --git a/playground/src/app.tsx b/playground/src/app.tsx
index 73d6e8d0..2ee2be42 100644
--- a/playground/src/app.tsx
+++ b/playground/src/app.tsx
@@ -1,8 +1,9 @@
-import { useState } from 'react';
+import { useMemo, useState } from 'react';
import clsx from 'clsx';
import { Console } from './components/console/console';
-import { debouncedUpdateEsm, updateEsm } from './utils/esm';
+import { updateEsm } from './utils/esm';
+import { debounce } from './utils/debounce';
import { Editor } from './components/editor/editor';
import { FileBrowser } from './components/file-browser/file-browser';
import { getSearchParam } from './utils/search-params';
@@ -16,26 +17,39 @@ import { useLogs } from './hooks/use-logs';
import styles from './app.module.css';
+const PREVIEW_DEBOUNCE_MS = 500;
+
export function App() {
const [templatePickerOpen, setTemplatePickerOpen] = useState(false);
const [autoPlay, setAutoPlay] = useState(getSearchParam('initialAutoPlay'));
const [code, setCode] = useState(getSearchParam('initialCode'));
const [compiledCode, setCompiledCode] = useState('');
+ const [isCompiling, setIsCompiling] = useState(false);
const [tab, setTab] = useState(getSearchParam('initialTab'));
const [isLightMode, setIsLightMode] = useLightMode(getSearchParam('initialLightMode'));
const devTool = useDevTool();
const { logs } = useLogs();
+ // Unified run path: dispose any current engine, then re-import the compiled code.
+ // Safe on first press — disposeEngine is a guarded no-op when no engine exists.
+ const runCompiled = (text: string) => {
+ devTool.disposeEngine();
+ updateEsm(text);
+ };
+
+ // Debounce dispose + re-import together so auto-play never leaves a window where the engine is
+ // disposed but not yet recreated. Stable instance is safe: disposeEngine reads the engine via
+ // the hook's ref, so the first-render closure never goes stale.
+ const debouncedRunCompiled = useMemo(() => debounce(runCompiled, PREVIEW_DEBOUNCE_MS), []);
+
const onCompiledChange = (text: string) => {
setCompiledCode(text);
if (autoPlay) {
- debouncedUpdateEsm(text);
+ debouncedRunCompiled(text);
}
};
- const manualPlay = () => {
- updateEsm(compiledCode);
- };
+ const run = () => runCompiled(compiledCode);
return (
@@ -44,7 +58,7 @@ export function App() {
autoPlay={autoPlay}
code={code}
isLightMode={isLightMode}
- onPlay={manualPlay}
+ onPlay={run}
setAutoPlay={setAutoPlay}
setCode={setCode}
setIsLightMode={setIsLightMode}
@@ -61,7 +75,15 @@ export function App() {
{
label: 'Editor',
value: 'editor',
- content:
+ content: (
+
+ )
},
{
label: 'Assets',
@@ -79,7 +101,7 @@ export function App() {
diff --git a/playground/src/components/button/button.module.css b/playground/src/components/button/button.module.css
index 6d2772ef..91851ac0 100644
--- a/playground/src/components/button/button.module.css
+++ b/playground/src/components/button/button.module.css
@@ -47,3 +47,25 @@
opacity: 0.25;
}
}
+
+.icon {
+ --background-color: transparent;
+ --background-color-hover: var(--color-zinc-800);
+ --foreground-color: var(--color-zinc-100);
+ --border-color: var(--color-zinc-700);
+
+ align-items: center;
+ border-radius: 50%;
+ display: inline-flex;
+ font-size: 14px;
+ justify-content: center;
+ padding: 0;
+ width: 28px;
+
+ :global(html[data-theme='light']) & {
+ --background-color: transparent;
+ --background-color-hover: var(--color-zinc-200);
+ --foreground-color: var(--color-zinc-900);
+ --border-color: var(--color-zinc-600);
+ }
+}
diff --git a/playground/src/components/button/button.tsx b/playground/src/components/button/button.tsx
index 984ecdc6..218969d4 100644
--- a/playground/src/components/button/button.tsx
+++ b/playground/src/components/button/button.tsx
@@ -6,15 +6,16 @@ import styles from './button.module.css';
type Props = {
children: ReactNode;
onClick: () => void;
- variant: 'primary' | 'outline';
+ variant: 'primary' | 'outline' | 'icon';
disabled?: boolean;
+ label?: string;
};
export function Button(props: Props) {
- const { children, disabled, onClick, variant } = props;
+ const { children, disabled, label, onClick, variant } = props;
return (
-