create-interview-cockpit 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -56,6 +56,11 @@ import {
|
|
|
56
56
|
updateNextjsFiles,
|
|
57
57
|
updateModuleFederationFiles,
|
|
58
58
|
stopNextjsSandbox,
|
|
59
|
+
startReactLabSandbox,
|
|
60
|
+
updateReactLabFiles,
|
|
61
|
+
streamReactLabCommand,
|
|
62
|
+
stopReactLabSandbox,
|
|
63
|
+
readReactLabFile,
|
|
59
64
|
} from "../api";
|
|
60
65
|
import ReactMarkdown from "react-markdown";
|
|
61
66
|
import remarkGfm from "remark-gfm";
|
|
@@ -646,6 +651,15 @@ export default function CodeRunnerModal() {
|
|
|
646
651
|
const [reactNavInput, setReactNavInput] = useState("/");
|
|
647
652
|
const [reactNavHistory, setReactNavHistory] = useState<string[]>(["/"]);
|
|
648
653
|
const [reactNavIndex, setReactNavIndex] = useState(0);
|
|
654
|
+
// Real Vite dev-server state (React lab)
|
|
655
|
+
const [viteSandboxId, setViteSandboxId] = useState<string | null>(null);
|
|
656
|
+
const [viteSandboxUrl, setViteSandboxUrl] = useState<string | null>(null);
|
|
657
|
+
const [viteStarting, setViteStarting] = useState(false);
|
|
658
|
+
const [viteError, setViteError] = useState<string | null>(null);
|
|
659
|
+
const [viteConsoleCommand, setViteConsoleCommand] = useState("npm install");
|
|
660
|
+
const [viteConsoleOutput, setViteConsoleOutput] = useState<OutputLine[]>([]);
|
|
661
|
+
const [viteConsoleRunning, setViteConsoleRunning] = useState(false);
|
|
662
|
+
const viteIframeRef = useRef<HTMLIFrameElement>(null);
|
|
649
663
|
|
|
650
664
|
// ── Sandbox output tab ("output" | "console" | "chat") ─────────────
|
|
651
665
|
const [sbxBottomTab, setSbxBottomTab] = useState<SbxBottomTab>("output");
|
|
@@ -873,6 +887,13 @@ export default function CodeRunnerModal() {
|
|
|
873
887
|
}
|
|
874
888
|
setReactPreviewSrc(null);
|
|
875
889
|
setReactClientTab("edit");
|
|
890
|
+
if (ct === "react") {
|
|
891
|
+
setViteSandboxId(null);
|
|
892
|
+
setViteSandboxUrl(null);
|
|
893
|
+
setViteError(null);
|
|
894
|
+
setViteConsoleOutput([]);
|
|
895
|
+
setViteConsoleRunning(false);
|
|
896
|
+
}
|
|
876
897
|
if (ct === "module-federation") {
|
|
877
898
|
setServerCollapsed(true);
|
|
878
899
|
setClientCollapsed(false);
|
|
@@ -901,7 +922,13 @@ export default function CodeRunnerModal() {
|
|
|
901
922
|
if (sbxBottomTab === "console") {
|
|
902
923
|
mfConsoleEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
903
924
|
}
|
|
904
|
-
}, [
|
|
925
|
+
}, [
|
|
926
|
+
mfConsoleOutput,
|
|
927
|
+
viteConsoleOutput,
|
|
928
|
+
mfConsoleRunning,
|
|
929
|
+
viteConsoleRunning,
|
|
930
|
+
sbxBottomTab,
|
|
931
|
+
]);
|
|
905
932
|
|
|
906
933
|
useEffect(() => {
|
|
907
934
|
if (clientType !== "module-federation") return;
|
|
@@ -1278,7 +1305,11 @@ export default function CodeRunnerModal() {
|
|
|
1278
1305
|
return files["app/page.tsx"]
|
|
1279
1306
|
? "app/page.tsx"
|
|
1280
1307
|
: (Object.keys(files)[0] ?? "");
|
|
1281
|
-
return files["
|
|
1308
|
+
return files["main.tsx"]
|
|
1309
|
+
? "main.tsx"
|
|
1310
|
+
: files["App.tsx"]
|
|
1311
|
+
? "App.tsx"
|
|
1312
|
+
: (Object.keys(files)[0] ?? "");
|
|
1282
1313
|
};
|
|
1283
1314
|
|
|
1284
1315
|
const refreshPreview = useCallback(
|
|
@@ -1325,6 +1356,15 @@ export default function CodeRunnerModal() {
|
|
|
1325
1356
|
entry = resolved;
|
|
1326
1357
|
} else {
|
|
1327
1358
|
entry = getReactEntry(reactFiles, type);
|
|
1359
|
+
if (
|
|
1360
|
+
entry === "main.tsx" &&
|
|
1361
|
+
!/export\s+function\s+mount\s*\(/.test(reactFiles[entry] ?? "") &&
|
|
1362
|
+
reactFiles["App.tsx"]
|
|
1363
|
+
) {
|
|
1364
|
+
// Compatibility path for older React labs created before main.tsx
|
|
1365
|
+
// exported an explicit mount() entry contract.
|
|
1366
|
+
entry = "App.tsx";
|
|
1367
|
+
}
|
|
1328
1368
|
}
|
|
1329
1369
|
if (!entry) return;
|
|
1330
1370
|
const html = generatePreviewHTML(
|
|
@@ -1349,6 +1389,19 @@ export default function CodeRunnerModal() {
|
|
|
1349
1389
|
}
|
|
1350
1390
|
}, [reactFiles, reactClientTab, reactPreviewSrc, refreshPreview]);
|
|
1351
1391
|
|
|
1392
|
+
// Auto-sync file edits to the running Vite server (HMR picks them up)
|
|
1393
|
+
const viteFileSyncTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
1394
|
+
useEffect(() => {
|
|
1395
|
+
if (!viteSandboxId) return;
|
|
1396
|
+
if (viteFileSyncTimer.current) clearTimeout(viteFileSyncTimer.current);
|
|
1397
|
+
viteFileSyncTimer.current = setTimeout(() => {
|
|
1398
|
+
void updateReactLabFiles(viteSandboxId, reactFiles);
|
|
1399
|
+
}, 400);
|
|
1400
|
+
return () => {
|
|
1401
|
+
if (viteFileSyncTimer.current) clearTimeout(viteFileSyncTimer.current);
|
|
1402
|
+
};
|
|
1403
|
+
}, [reactFiles, viteSandboxId]);
|
|
1404
|
+
|
|
1352
1405
|
/** Navigate to a new path (updates URL bar + history + re-renders preview). */
|
|
1353
1406
|
const navigatePreview = useCallback(
|
|
1354
1407
|
(to: string) => {
|
|
@@ -1365,12 +1418,23 @@ export default function CodeRunnerModal() {
|
|
|
1365
1418
|
[reactNavIndex, refreshPreview],
|
|
1366
1419
|
);
|
|
1367
1420
|
|
|
1368
|
-
// Listen for rlab-nav messages from the preview iframe
|
|
1421
|
+
// Listen for rlab-nav and rlab-err messages from the preview iframe
|
|
1369
1422
|
useEffect(() => {
|
|
1370
1423
|
const handler = (e: MessageEvent) => {
|
|
1371
1424
|
if (e.data?.type === "rlab-nav" && typeof e.data.to === "string") {
|
|
1372
1425
|
navigatePreview(e.data.to);
|
|
1373
1426
|
}
|
|
1427
|
+
if (e.data?.type === "rlab-err" && typeof e.data.error === "string") {
|
|
1428
|
+
setOutput((prev) => [
|
|
1429
|
+
...prev,
|
|
1430
|
+
{
|
|
1431
|
+
kind: "stderr",
|
|
1432
|
+
text: `Preview error: ${e.data.error}`,
|
|
1433
|
+
source: "client",
|
|
1434
|
+
},
|
|
1435
|
+
]);
|
|
1436
|
+
setSbxBottomTab("output");
|
|
1437
|
+
}
|
|
1374
1438
|
};
|
|
1375
1439
|
window.addEventListener("message", handler);
|
|
1376
1440
|
return () => window.removeEventListener("message", handler);
|
|
@@ -1782,16 +1846,84 @@ export default function CodeRunnerModal() {
|
|
|
1782
1846
|
current === "console" || current === "inspector" ? "output" : current,
|
|
1783
1847
|
);
|
|
1784
1848
|
}
|
|
1785
|
-
|
|
1849
|
+
if (prev === "react" && clientType !== "react" && viteSandboxId) {
|
|
1850
|
+
void stopReactLabSandbox(viteSandboxId);
|
|
1851
|
+
setViteSandboxId(null);
|
|
1852
|
+
setViteSandboxUrl(null);
|
|
1853
|
+
setViteConsoleRunning(false);
|
|
1854
|
+
setSbxBottomTab((current) =>
|
|
1855
|
+
current === "console" ? "output" : current,
|
|
1856
|
+
);
|
|
1857
|
+
}
|
|
1858
|
+
}, [clientType, nxSandboxId, mfSandboxId, viteSandboxId]);
|
|
1786
1859
|
|
|
1787
1860
|
// Clean up on unmount
|
|
1788
1861
|
useEffect(() => {
|
|
1789
1862
|
return () => {
|
|
1790
1863
|
if (nxSandboxId) void stopNextjsSandbox(nxSandboxId);
|
|
1791
1864
|
if (mfSandboxId) void stopModuleFederationSandbox(mfSandboxId);
|
|
1865
|
+
if (viteSandboxId) void stopReactLabSandbox(viteSandboxId);
|
|
1792
1866
|
};
|
|
1793
1867
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1794
|
-
}, [nxSandboxId, mfSandboxId]);
|
|
1868
|
+
}, [nxSandboxId, mfSandboxId, viteSandboxId]);
|
|
1869
|
+
|
|
1870
|
+
const startViteServer = useCallback(async () => {
|
|
1871
|
+
if (viteStarting) return;
|
|
1872
|
+
setViteStarting(true);
|
|
1873
|
+
setViteError(null);
|
|
1874
|
+
setViteConsoleOutput([]);
|
|
1875
|
+
try {
|
|
1876
|
+
const info = await startReactLabSandbox(reactFiles);
|
|
1877
|
+
setViteSandboxId(info.id);
|
|
1878
|
+
setViteSandboxUrl(info.url);
|
|
1879
|
+
setReactClientTab("preview");
|
|
1880
|
+
} catch (err: any) {
|
|
1881
|
+
setViteError(err?.message ?? String(err));
|
|
1882
|
+
} finally {
|
|
1883
|
+
setViteStarting(false);
|
|
1884
|
+
}
|
|
1885
|
+
}, [viteStarting, reactFiles]);
|
|
1886
|
+
|
|
1887
|
+
const runViteCommand = useCallback(async () => {
|
|
1888
|
+
if (!viteSandboxId || viteConsoleRunning) return;
|
|
1889
|
+
const command = viteConsoleCommand.trim();
|
|
1890
|
+
if (!command) return;
|
|
1891
|
+
setViteError(null);
|
|
1892
|
+
setViteConsoleRunning(true);
|
|
1893
|
+
setSbxBottomTab("console");
|
|
1894
|
+
try {
|
|
1895
|
+
await streamReactLabCommand({ id: viteSandboxId, command }, (message) => {
|
|
1896
|
+
if (message.type === "output") {
|
|
1897
|
+
setViteConsoleOutput((prev) => [
|
|
1898
|
+
...prev,
|
|
1899
|
+
{ kind: message.kind, text: message.text, source: "server" },
|
|
1900
|
+
]);
|
|
1901
|
+
} else if (message.type === "error") {
|
|
1902
|
+
setViteConsoleOutput((prev) => [
|
|
1903
|
+
...prev,
|
|
1904
|
+
{ kind: "stderr", text: message.error, source: "server" },
|
|
1905
|
+
]);
|
|
1906
|
+
}
|
|
1907
|
+
});
|
|
1908
|
+
// After any npm command that mutates dependencies, read package.json back
|
|
1909
|
+
// from disk so the editor reflects what npm actually wrote.
|
|
1910
|
+
const tokens = command.trim().split(/\s+/);
|
|
1911
|
+
const sub = tokens[1];
|
|
1912
|
+
if (
|
|
1913
|
+
sub &&
|
|
1914
|
+
/^(install|i|uninstall|un|remove|rm|r|update|up|upgrade|add)$/.test(sub)
|
|
1915
|
+
) {
|
|
1916
|
+
const updated = await readReactLabFile(viteSandboxId, "package.json");
|
|
1917
|
+
if (updated !== null) {
|
|
1918
|
+
setReactFiles((prev) => ({ ...prev, "package.json": updated }));
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
} catch (err: any) {
|
|
1922
|
+
setViteError(err?.message ?? String(err));
|
|
1923
|
+
} finally {
|
|
1924
|
+
setViteConsoleRunning(false);
|
|
1925
|
+
}
|
|
1926
|
+
}, [viteConsoleCommand, viteConsoleRunning, viteSandboxId]);
|
|
1795
1927
|
|
|
1796
1928
|
const handleClientTypeChange = useCallback(
|
|
1797
1929
|
(ct: FrontendClientType) => {
|
|
@@ -1808,6 +1940,10 @@ export default function CodeRunnerModal() {
|
|
|
1808
1940
|
current === "console" || current === "inspector" ? "output" : current,
|
|
1809
1941
|
);
|
|
1810
1942
|
}
|
|
1943
|
+
if (ct !== "react") {
|
|
1944
|
+
setViteConsoleOutput([]);
|
|
1945
|
+
setViteConsoleRunning(false);
|
|
1946
|
+
}
|
|
1811
1947
|
if (ct !== "script") {
|
|
1812
1948
|
const defs = defaultForType(ct);
|
|
1813
1949
|
setReactFiles(defs.files);
|
|
@@ -2111,6 +2247,10 @@ export default function CodeRunnerModal() {
|
|
|
2111
2247
|
const isActiveModuleFederationGeneratedFile =
|
|
2112
2248
|
clientType === "module-federation" &&
|
|
2113
2249
|
moduleFederationGeneratedFileSet.has(reactActiveFile);
|
|
2250
|
+
const usesClientExplorer =
|
|
2251
|
+
clientType === "react" ||
|
|
2252
|
+
clientType === "nextjs" ||
|
|
2253
|
+
clientType === "module-federation";
|
|
2114
2254
|
const mfInspectorRuntimeCount = new Set(
|
|
2115
2255
|
mfInspectorEvents.map((event) => event.runtimeId),
|
|
2116
2256
|
).size;
|
|
@@ -3452,7 +3592,7 @@ export default function CodeRunnerModal() {
|
|
|
3452
3592
|
</button>
|
|
3453
3593
|
</>
|
|
3454
3594
|
)}
|
|
3455
|
-
{/* React/Next mode
|
|
3595
|
+
{/* React/Next/Webpack mode controls */}
|
|
3456
3596
|
{(clientType === "react" ||
|
|
3457
3597
|
clientType === "nextjs" ||
|
|
3458
3598
|
clientType === "module-federation") && (
|
|
@@ -3465,17 +3605,69 @@ export default function CodeRunnerModal() {
|
|
|
3465
3605
|
{sandboxUrl}
|
|
3466
3606
|
</span>
|
|
3467
3607
|
)}
|
|
3468
|
-
{/* React mode:
|
|
3608
|
+
{/* React mode: Run Vite OR edit/preview toggle */}
|
|
3469
3609
|
{clientType === "react" && (
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3610
|
+
<>
|
|
3611
|
+
{!viteSandboxUrl ? (
|
|
3612
|
+
<button
|
|
3613
|
+
type="button"
|
|
3614
|
+
onClick={() => void startViteServer()}
|
|
3615
|
+
disabled={viteStarting}
|
|
3616
|
+
className="flex items-center gap-1 px-2 py-0.5 rounded text-[10px] font-medium bg-cyan-600/20 hover:bg-cyan-600/40 text-cyan-400 disabled:opacity-50 transition-colors shrink-0"
|
|
3617
|
+
title="Install dependencies and start Vite dev server"
|
|
3618
|
+
>
|
|
3619
|
+
{viteStarting ? (
|
|
3620
|
+
<Loader2 className="w-3 h-3 animate-spin" />
|
|
3621
|
+
) : (
|
|
3622
|
+
<Play className="w-3 h-3" />
|
|
3623
|
+
)}
|
|
3624
|
+
{viteStarting ? "Starting…" : "Run Vite"}
|
|
3625
|
+
</button>
|
|
3626
|
+
) : (
|
|
3627
|
+
<>
|
|
3628
|
+
<div className="flex items-center rounded overflow-hidden border border-slate-700/50 text-[9px] shrink-0">
|
|
3629
|
+
<button
|
|
3630
|
+
type="button"
|
|
3631
|
+
onClick={() => setReactClientTab("edit")}
|
|
3632
|
+
className={`flex items-center gap-0.5 px-1.5 py-0.5 transition-colors ${
|
|
3633
|
+
reactClientTab === "edit"
|
|
3634
|
+
? "bg-slate-700 text-slate-200"
|
|
3635
|
+
: "text-slate-500 hover:text-slate-400"
|
|
3636
|
+
}`}
|
|
3637
|
+
title="Edit code"
|
|
3638
|
+
>
|
|
3639
|
+
<Code2 className="w-2.5 h-2.5" />
|
|
3640
|
+
</button>
|
|
3641
|
+
<button
|
|
3642
|
+
type="button"
|
|
3643
|
+
onClick={() => setReactClientTab("preview")}
|
|
3644
|
+
className={`flex items-center gap-0.5 px-1.5 py-0.5 transition-colors ${
|
|
3645
|
+
reactClientTab === "preview"
|
|
3646
|
+
? "bg-slate-700 text-slate-200"
|
|
3647
|
+
: "text-slate-500 hover:text-slate-400"
|
|
3648
|
+
}`}
|
|
3649
|
+
title="Live preview"
|
|
3650
|
+
>
|
|
3651
|
+
<Eye className="w-2.5 h-2.5" />
|
|
3652
|
+
</button>
|
|
3653
|
+
</div>
|
|
3654
|
+
<button
|
|
3655
|
+
type="button"
|
|
3656
|
+
onClick={() => {
|
|
3657
|
+
if (viteSandboxId)
|
|
3658
|
+
void stopReactLabSandbox(viteSandboxId);
|
|
3659
|
+
setViteSandboxId(null);
|
|
3660
|
+
setViteSandboxUrl(null);
|
|
3661
|
+
setReactClientTab("edit");
|
|
3662
|
+
}}
|
|
3663
|
+
className="p-0.5 rounded text-slate-600 hover:text-red-400 transition-colors shrink-0"
|
|
3664
|
+
title="Stop Vite lab"
|
|
3665
|
+
>
|
|
3666
|
+
<StopCircle className="w-3 h-3" />
|
|
3667
|
+
</button>
|
|
3668
|
+
</>
|
|
3669
|
+
)}
|
|
3670
|
+
</>
|
|
3479
3671
|
)}
|
|
3480
3672
|
{/* Next.js mode: start real server OR edit/preview toggle */}
|
|
3481
3673
|
{clientType === "nextjs" && (
|
|
@@ -3596,129 +3788,12 @@ export default function CodeRunnerModal() {
|
|
|
3596
3788
|
)}
|
|
3597
3789
|
</div>
|
|
3598
3790
|
|
|
3599
|
-
{/* File tabs row (React only — Next.js uses the tree sidebar) */}
|
|
3600
|
-
{clientType === "react" && (
|
|
3601
|
-
<div className="flex items-center gap-0.5 px-2 py-1 bg-slate-800/40 border-b border-slate-700 shrink-0 overflow-x-auto">
|
|
3602
|
-
{Object.keys(reactFiles).map((fname) => (
|
|
3603
|
-
<button
|
|
3604
|
-
key={fname}
|
|
3605
|
-
type="button"
|
|
3606
|
-
onClick={() => {
|
|
3607
|
-
setReactActiveFile(fname);
|
|
3608
|
-
setReactClientTab("edit");
|
|
3609
|
-
}}
|
|
3610
|
-
className={`flex items-center gap-1 px-2 py-0.5 rounded text-[10px] font-mono whitespace-nowrap transition-colors ${
|
|
3611
|
-
fname === reactActiveFile && reactClientTab === "edit"
|
|
3612
|
-
? "bg-slate-900 text-slate-200 border border-slate-600"
|
|
3613
|
-
: "text-slate-500 hover:text-slate-300 hover:bg-slate-800/50"
|
|
3614
|
-
}`}
|
|
3615
|
-
>
|
|
3616
|
-
{fname.includes("/") ? fname.split("/").pop() : fname}
|
|
3617
|
-
<span
|
|
3618
|
-
role="button"
|
|
3619
|
-
onClick={(e) => {
|
|
3620
|
-
e.stopPropagation();
|
|
3621
|
-
if (Object.keys(reactFiles).length <= 1) return;
|
|
3622
|
-
const remaining = Object.keys(reactFiles).filter(
|
|
3623
|
-
(f) => f !== fname,
|
|
3624
|
-
);
|
|
3625
|
-
setReactFiles((prev) => {
|
|
3626
|
-
const next = { ...prev };
|
|
3627
|
-
delete next[fname];
|
|
3628
|
-
return next;
|
|
3629
|
-
});
|
|
3630
|
-
if (reactActiveFile === fname)
|
|
3631
|
-
setReactActiveFile(remaining[0] ?? "");
|
|
3632
|
-
}}
|
|
3633
|
-
className="w-3 h-3 flex items-center justify-center text-slate-600 hover:text-red-400 rounded transition-colors"
|
|
3634
|
-
title="Delete file"
|
|
3635
|
-
>
|
|
3636
|
-
<X className="w-2.5 h-2.5" />
|
|
3637
|
-
</span>
|
|
3638
|
-
</button>
|
|
3639
|
-
))}
|
|
3640
|
-
{/* Add new file */}
|
|
3641
|
-
{reactAddingFile ? (
|
|
3642
|
-
<input
|
|
3643
|
-
autoFocus
|
|
3644
|
-
value={reactNewFileName}
|
|
3645
|
-
onChange={(e) => setReactNewFileName(e.target.value)}
|
|
3646
|
-
onBlur={() => {
|
|
3647
|
-
setReactAddingFile(false);
|
|
3648
|
-
setReactNewFileName("");
|
|
3649
|
-
}}
|
|
3650
|
-
onKeyDown={(e) => {
|
|
3651
|
-
if (e.key === "Enter") {
|
|
3652
|
-
e.preventDefault();
|
|
3653
|
-
const name = reactNewFileName.trim();
|
|
3654
|
-
if (name && !reactFiles[name]) {
|
|
3655
|
-
setReactFiles((prev) => ({
|
|
3656
|
-
...prev,
|
|
3657
|
-
[name]: newFileContent(name),
|
|
3658
|
-
}));
|
|
3659
|
-
setReactActiveFile(name);
|
|
3660
|
-
setReactClientTab("edit");
|
|
3661
|
-
}
|
|
3662
|
-
setReactAddingFile(false);
|
|
3663
|
-
setReactNewFileName("");
|
|
3664
|
-
} else if (e.key === "Escape") {
|
|
3665
|
-
setReactAddingFile(false);
|
|
3666
|
-
setReactNewFileName("");
|
|
3667
|
-
}
|
|
3668
|
-
}}
|
|
3669
|
-
placeholder="filename.tsx"
|
|
3670
|
-
className="w-28 bg-slate-900 border border-cyan-600/50 rounded px-1.5 py-0.5 text-[10px] font-mono text-slate-200 placeholder-slate-600 outline-none focus:border-cyan-500"
|
|
3671
|
-
/>
|
|
3672
|
-
) : (
|
|
3673
|
-
<button
|
|
3674
|
-
type="button"
|
|
3675
|
-
onClick={() => setReactAddingFile(true)}
|
|
3676
|
-
className="p-0.5 rounded text-slate-600 hover:text-cyan-400 transition-colors shrink-0"
|
|
3677
|
-
title="New file"
|
|
3678
|
-
>
|
|
3679
|
-
<FilePlus className="w-3 h-3" />
|
|
3680
|
-
</button>
|
|
3681
|
-
)}
|
|
3682
|
-
{/* Edit / Preview tab toggle */}
|
|
3683
|
-
<div className="ml-auto flex items-center rounded overflow-hidden border border-slate-700/50 text-[9px] shrink-0">
|
|
3684
|
-
<button
|
|
3685
|
-
type="button"
|
|
3686
|
-
onClick={() => setReactClientTab("edit")}
|
|
3687
|
-
className={`flex items-center gap-0.5 px-1.5 py-0.5 transition-colors ${
|
|
3688
|
-
reactClientTab === "edit"
|
|
3689
|
-
? "bg-slate-700 text-slate-200"
|
|
3690
|
-
: "text-slate-500 hover:text-slate-400"
|
|
3691
|
-
}`}
|
|
3692
|
-
title="Edit code"
|
|
3693
|
-
>
|
|
3694
|
-
<Code2 className="w-2.5 h-2.5" />
|
|
3695
|
-
</button>
|
|
3696
|
-
<button
|
|
3697
|
-
type="button"
|
|
3698
|
-
onClick={() => {
|
|
3699
|
-
if (!reactPreviewSrc) refreshPreview();
|
|
3700
|
-
else setReactClientTab("preview");
|
|
3701
|
-
}}
|
|
3702
|
-
className={`flex items-center gap-0.5 px-1.5 py-0.5 transition-colors ${
|
|
3703
|
-
reactClientTab === "preview"
|
|
3704
|
-
? "bg-slate-700 text-slate-200"
|
|
3705
|
-
: "text-slate-500 hover:text-slate-400"
|
|
3706
|
-
}`}
|
|
3707
|
-
title="Live preview"
|
|
3708
|
-
>
|
|
3709
|
-
<Eye className="w-2.5 h-2.5" />
|
|
3710
|
-
</button>
|
|
3711
|
-
</div>
|
|
3712
|
-
</div>
|
|
3713
|
-
)}
|
|
3714
|
-
|
|
3715
3791
|
{/* Client body */}
|
|
3716
3792
|
<div
|
|
3717
|
-
className={`flex-1 min-h-0 ${
|
|
3793
|
+
className={`flex-1 min-h-0 ${usesClientExplorer ? "flex flex-row" : "relative"}`}
|
|
3718
3794
|
>
|
|
3719
|
-
{/* ──
|
|
3720
|
-
{
|
|
3721
|
-
clientType === "module-federation") && (
|
|
3795
|
+
{/* ── VS Code-style file tree sidebar ── */}
|
|
3796
|
+
{usesClientExplorer && (
|
|
3722
3797
|
<div className="w-36 shrink-0 flex flex-col border-r border-slate-700 bg-slate-900/60 overflow-y-auto">
|
|
3723
3798
|
{/* Sidebar header */}
|
|
3724
3799
|
<div className="flex items-center justify-between px-2 py-1.5 border-b border-slate-700/60">
|
|
@@ -3759,7 +3834,9 @@ export default function CodeRunnerModal() {
|
|
|
3759
3834
|
placeholder={
|
|
3760
3835
|
clientType === "module-federation"
|
|
3761
3836
|
? "apps/orders/src/App.jsx"
|
|
3762
|
-
: "
|
|
3837
|
+
: clientType === "nextjs"
|
|
3838
|
+
? "app/new.tsx"
|
|
3839
|
+
: "components/NewWidget.tsx"
|
|
3763
3840
|
}
|
|
3764
3841
|
className="w-full bg-slate-800 border border-cyan-600/50 rounded px-1 py-0.5 text-[9px] font-mono text-slate-200 placeholder-slate-600 outline-none focus:border-cyan-500"
|
|
3765
3842
|
/>
|
|
@@ -3771,7 +3848,9 @@ export default function CodeRunnerModal() {
|
|
|
3771
3848
|
title={
|
|
3772
3849
|
clientType === "module-federation"
|
|
3773
3850
|
? "New file (use paths like apps/orders/src/App.jsx)"
|
|
3774
|
-
:
|
|
3851
|
+
: clientType === "nextjs"
|
|
3852
|
+
? "New file (use paths like app/dashboard/page.tsx)"
|
|
3853
|
+
: "New file (use paths like components/Button.tsx)"
|
|
3775
3854
|
}
|
|
3776
3855
|
>
|
|
3777
3856
|
<FilePlus className="w-3 h-3" />
|
|
@@ -3964,7 +4043,7 @@ export default function CodeRunnerModal() {
|
|
|
3964
4043
|
|
|
3965
4044
|
{/* ── Editor / Preview area ── */}
|
|
3966
4045
|
<div
|
|
3967
|
-
className={`${
|
|
4046
|
+
className={`${usesClientExplorer ? "flex-1 min-w-0 relative" : "absolute inset-0"}`}
|
|
3968
4047
|
>
|
|
3969
4048
|
{clientType === "script" ? (
|
|
3970
4049
|
<SyntaxEditor
|
|
@@ -4260,23 +4339,29 @@ export default function CodeRunnerModal() {
|
|
|
4260
4339
|
</div>
|
|
4261
4340
|
)}
|
|
4262
4341
|
{((clientType === "module-federation" && mfError) ||
|
|
4263
|
-
(clientType
|
|
4342
|
+
(clientType === "nextjs" && nxError) ||
|
|
4343
|
+
(clientType === "react" && viteError)) && (
|
|
4264
4344
|
<div className="text-[10px] text-red-400 bg-red-950/40 border-b border-red-800 px-3 py-1.5 shrink-0 font-mono">
|
|
4265
4345
|
{clientType === "module-federation"
|
|
4266
4346
|
? mfError
|
|
4267
|
-
:
|
|
4347
|
+
: clientType === "react"
|
|
4348
|
+
? viteError
|
|
4349
|
+
: nxError}
|
|
4268
4350
|
</div>
|
|
4269
4351
|
)}
|
|
4270
|
-
{(nxStarting || mfStarting) && (
|
|
4352
|
+
{(nxStarting || mfStarting || viteStarting) && (
|
|
4271
4353
|
<div className="flex-1 flex flex-col items-center justify-center gap-3 text-slate-400 text-sm bg-slate-950">
|
|
4272
4354
|
<Loader2 className="w-8 h-8 animate-spin text-cyan-400" />
|
|
4273
4355
|
<p className="text-[12px]">
|
|
4274
4356
|
{clientType === "module-federation"
|
|
4275
4357
|
? "Installing dependencies and starting webpack apps…"
|
|
4276
|
-
:
|
|
4358
|
+
: clientType === "react"
|
|
4359
|
+
? "Installing dependencies and starting Vite…"
|
|
4360
|
+
: "Starting Next.js dev server…"}
|
|
4277
4361
|
</p>
|
|
4278
4362
|
<p className="text-[10px] text-slate-600 max-w-md text-center px-4">
|
|
4279
|
-
{clientType === "module-federation"
|
|
4363
|
+
{clientType === "module-federation" ||
|
|
4364
|
+
clientType === "react"
|
|
4280
4365
|
? "The first run can take a little longer because npm install runs inside the lab sandbox."
|
|
4281
4366
|
: "This takes ~10 seconds on the first run"}
|
|
4282
4367
|
</p>
|
|
@@ -4342,14 +4427,47 @@ export default function CodeRunnerModal() {
|
|
|
4342
4427
|
</p>
|
|
4343
4428
|
</div>
|
|
4344
4429
|
)}
|
|
4345
|
-
{!
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4430
|
+
{!viteStarting &&
|
|
4431
|
+
clientType === "react" &&
|
|
4432
|
+
viteSandboxUrl && (
|
|
4433
|
+
<iframe
|
|
4434
|
+
ref={viteIframeRef}
|
|
4435
|
+
src={viteSandboxUrl}
|
|
4436
|
+
className="flex-1 min-h-0 w-full border-0 bg-white"
|
|
4437
|
+
style={
|
|
4438
|
+
isDraggingResize
|
|
4439
|
+
? { pointerEvents: "none" }
|
|
4440
|
+
: undefined
|
|
4441
|
+
}
|
|
4442
|
+
title="React Lab Preview"
|
|
4443
|
+
/>
|
|
4444
|
+
)}
|
|
4445
|
+
{!viteStarting &&
|
|
4446
|
+
clientType === "react" &&
|
|
4447
|
+
!viteSandboxUrl && (
|
|
4448
|
+
<div className="flex-1 flex flex-col items-center justify-center gap-3 text-slate-400 text-sm bg-slate-950 px-6 text-center">
|
|
4449
|
+
<Server className="w-8 h-8 text-cyan-400/70" />
|
|
4450
|
+
<p className="text-[12px]">
|
|
4451
|
+
Click{" "}
|
|
4452
|
+
<span className="text-cyan-400 font-medium">
|
|
4453
|
+
Run Vite
|
|
4454
|
+
</span>{" "}
|
|
4455
|
+
to install dependencies and start the dev
|
|
4456
|
+
server.
|
|
4457
|
+
</p>
|
|
4458
|
+
<p className="text-[10px] text-slate-600 max-w-md">
|
|
4459
|
+
Edit{" "}
|
|
4460
|
+
<code className="text-slate-400">
|
|
4461
|
+
package.json
|
|
4462
|
+
</code>{" "}
|
|
4463
|
+
to add packages, then use the Console tab to run{" "}
|
|
4464
|
+
<code className="text-slate-400">
|
|
4465
|
+
npm install
|
|
4466
|
+
</code>
|
|
4467
|
+
.
|
|
4468
|
+
</p>
|
|
4469
|
+
</div>
|
|
4470
|
+
)}
|
|
4353
4471
|
</div>
|
|
4354
4472
|
)}
|
|
4355
4473
|
</div>
|
|
@@ -4396,7 +4514,8 @@ export default function CodeRunnerModal() {
|
|
|
4396
4514
|
) : null}
|
|
4397
4515
|
Output
|
|
4398
4516
|
</button>
|
|
4399
|
-
{clientType === "module-federation"
|
|
4517
|
+
{(clientType === "module-federation" ||
|
|
4518
|
+
clientType === "react") && (
|
|
4400
4519
|
<button
|
|
4401
4520
|
type="button"
|
|
4402
4521
|
onClick={() => setSbxBottomTab("console")}
|
|
@@ -4444,9 +4563,10 @@ export default function CodeRunnerModal() {
|
|
|
4444
4563
|
(serverStarting || clientRunning) && (
|
|
4445
4564
|
<Loader2 className="w-3 h-3 text-emerald-400 animate-spin mr-1" />
|
|
4446
4565
|
)}
|
|
4447
|
-
{sbxBottomTab === "console" &&
|
|
4448
|
-
|
|
4449
|
-
|
|
4566
|
+
{sbxBottomTab === "console" &&
|
|
4567
|
+
(mfConsoleRunning || viteConsoleRunning) && (
|
|
4568
|
+
<Loader2 className="w-3 h-3 text-cyan-400 animate-spin mr-1" />
|
|
4569
|
+
)}
|
|
4450
4570
|
{sbxBottomTab === "inspector" && mfInspectorEvents.length > 0 && (
|
|
4451
4571
|
<div className="flex items-center gap-1 mr-1">
|
|
4452
4572
|
<button
|
|
@@ -4495,30 +4615,39 @@ export default function CodeRunnerModal() {
|
|
|
4495
4615
|
</button>
|
|
4496
4616
|
</div>
|
|
4497
4617
|
)}
|
|
4498
|
-
{sbxBottomTab === "console" &&
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
<
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4618
|
+
{sbxBottomTab === "console" &&
|
|
4619
|
+
(mfConsoleOutput.length > 0 ||
|
|
4620
|
+
viteConsoleOutput.length > 0) && (
|
|
4621
|
+
<div className="flex items-center gap-1 mr-1">
|
|
4622
|
+
<button
|
|
4623
|
+
type="button"
|
|
4624
|
+
onClick={() => {
|
|
4625
|
+
const out =
|
|
4626
|
+
clientType === "react"
|
|
4627
|
+
? viteConsoleOutput
|
|
4628
|
+
: mfConsoleOutput;
|
|
4629
|
+
navigator.clipboard.writeText(
|
|
4630
|
+
out.map((line) => line.text).join("\n"),
|
|
4631
|
+
);
|
|
4632
|
+
}}
|
|
4633
|
+
className="p-0.5 rounded text-slate-600 hover:text-slate-300 transition-colors"
|
|
4634
|
+
title="Copy console output"
|
|
4635
|
+
>
|
|
4636
|
+
<Copy className="w-3 h-3" />
|
|
4637
|
+
</button>
|
|
4638
|
+
<button
|
|
4639
|
+
type="button"
|
|
4640
|
+
onClick={() => {
|
|
4641
|
+
if (clientType === "react") setViteConsoleOutput([]);
|
|
4642
|
+
else setMfConsoleOutput([]);
|
|
4643
|
+
}}
|
|
4644
|
+
className="p-0.5 rounded text-slate-600 hover:text-slate-300 transition-colors"
|
|
4645
|
+
title="Clear console output"
|
|
4646
|
+
>
|
|
4647
|
+
<Trash2 className="w-3 h-3" />
|
|
4648
|
+
</button>
|
|
4649
|
+
</div>
|
|
4650
|
+
)}
|
|
4522
4651
|
{sbxBottomTab === "chat" && sbxChatMessages.length > 0 && (
|
|
4523
4652
|
<button
|
|
4524
4653
|
type="button"
|
|
@@ -4551,7 +4680,11 @@ export default function CodeRunnerModal() {
|
|
|
4551
4680
|
<span className="text-slate-600">
|
|
4552
4681
|
{clientType === "module-federation"
|
|
4553
4682
|
? "Run webpack to start the host and remotes"
|
|
4554
|
-
:
|
|
4683
|
+
: clientType === "nextjs"
|
|
4684
|
+
? "Start Next.js to launch the live preview"
|
|
4685
|
+
: clientType === "react"
|
|
4686
|
+
? "Click Run Vite to start the dev server"
|
|
4687
|
+
: "Start the server, then run the client"}
|
|
4555
4688
|
</span>
|
|
4556
4689
|
)}
|
|
4557
4690
|
{sandboxOutput.map((line, i) => (
|
|
@@ -5885,90 +6018,143 @@ export default function CodeRunnerModal() {
|
|
|
5885
6018
|
{sbxBottomTab === "console" && (
|
|
5886
6019
|
<div className="flex-1 min-h-0 flex flex-col">
|
|
5887
6020
|
<div className="shrink-0 border-b border-slate-800 bg-slate-900/70 px-3 py-2 flex items-center gap-2">
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
{root
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
6021
|
+
{clientType === "module-federation" && (
|
|
6022
|
+
<select
|
|
6023
|
+
value={mfConsoleCwd}
|
|
6024
|
+
onChange={(e) => setMfConsoleCwd(e.target.value)}
|
|
6025
|
+
disabled={!mfSandboxId || mfConsoleRunning}
|
|
6026
|
+
className="bg-slate-950 border border-slate-700 rounded px-2 py-1 text-[11px] font-mono text-slate-200 outline-none disabled:opacity-50"
|
|
6027
|
+
>
|
|
6028
|
+
{moduleFederationCommandRoots.map((root) => (
|
|
6029
|
+
<option key={root} value={root}>
|
|
6030
|
+
{root === "." ? "root" : root}
|
|
6031
|
+
</option>
|
|
6032
|
+
))}
|
|
6033
|
+
</select>
|
|
6034
|
+
)}
|
|
5900
6035
|
<input
|
|
5901
|
-
value={
|
|
5902
|
-
|
|
6036
|
+
value={
|
|
6037
|
+
clientType === "react"
|
|
6038
|
+
? viteConsoleCommand
|
|
6039
|
+
: mfConsoleCommand
|
|
6040
|
+
}
|
|
6041
|
+
onChange={(e) =>
|
|
6042
|
+
clientType === "react"
|
|
6043
|
+
? setViteConsoleCommand(e.target.value)
|
|
6044
|
+
: setMfConsoleCommand(e.target.value)
|
|
6045
|
+
}
|
|
5903
6046
|
onKeyDown={(e) => {
|
|
5904
6047
|
if (e.key === "Enter") {
|
|
5905
6048
|
e.preventDefault();
|
|
5906
|
-
void
|
|
6049
|
+
if (clientType === "react") void runViteCommand();
|
|
6050
|
+
else void runModuleFederationCommand();
|
|
5907
6051
|
}
|
|
5908
6052
|
}}
|
|
5909
|
-
disabled={
|
|
5910
|
-
|
|
6053
|
+
disabled={
|
|
6054
|
+
clientType === "react"
|
|
6055
|
+
? !viteSandboxId || viteConsoleRunning
|
|
6056
|
+
: !mfSandboxId || mfConsoleRunning
|
|
6057
|
+
}
|
|
6058
|
+
placeholder={
|
|
6059
|
+
clientType === "react"
|
|
6060
|
+
? "npm install <package>"
|
|
6061
|
+
: "npm run build"
|
|
6062
|
+
}
|
|
5911
6063
|
className="flex-1 bg-slate-950 border border-slate-700 rounded px-2 py-1 text-[11px] font-mono text-slate-200 placeholder-slate-600 outline-none disabled:opacity-50"
|
|
5912
6064
|
spellCheck={false}
|
|
5913
6065
|
/>
|
|
5914
6066
|
<button
|
|
5915
6067
|
type="button"
|
|
5916
|
-
onClick={() =>
|
|
6068
|
+
onClick={() => {
|
|
6069
|
+
if (clientType === "react") void runViteCommand();
|
|
6070
|
+
else void runModuleFederationCommand();
|
|
6071
|
+
}}
|
|
5917
6072
|
disabled={
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
6073
|
+
clientType === "react"
|
|
6074
|
+
? !viteSandboxId ||
|
|
6075
|
+
viteConsoleRunning ||
|
|
6076
|
+
!viteConsoleCommand.trim()
|
|
6077
|
+
: !mfSandboxId ||
|
|
6078
|
+
mfConsoleRunning ||
|
|
6079
|
+
!mfConsoleCommand.trim()
|
|
5921
6080
|
}
|
|
5922
6081
|
className="flex items-center gap-1 px-2.5 py-1 rounded text-[11px] font-medium bg-cyan-600/20 hover:bg-cyan-600/35 text-cyan-300 disabled:opacity-50 transition-colors shrink-0"
|
|
5923
6082
|
>
|
|
5924
|
-
{
|
|
6083
|
+
{(
|
|
6084
|
+
clientType === "react"
|
|
6085
|
+
? viteConsoleRunning
|
|
6086
|
+
: mfConsoleRunning
|
|
6087
|
+
) ? (
|
|
5925
6088
|
<Loader2 className="w-3 h-3 animate-spin" />
|
|
5926
6089
|
) : (
|
|
5927
6090
|
<Play className="w-3 h-3" />
|
|
5928
6091
|
)}
|
|
5929
6092
|
Run
|
|
5930
6093
|
</button>
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
6094
|
+
{clientType === "module-federation" && (
|
|
6095
|
+
<button
|
|
6096
|
+
type="button"
|
|
6097
|
+
onClick={() =>
|
|
6098
|
+
void refreshModuleFederationGeneratedFiles()
|
|
6099
|
+
}
|
|
6100
|
+
disabled={!mfSandboxId || mfConsoleRunning}
|
|
6101
|
+
className="px-2 py-1 rounded text-[10px] font-medium text-slate-400 hover:text-slate-200 hover:bg-slate-800 disabled:opacity-50 transition-colors shrink-0"
|
|
6102
|
+
>
|
|
6103
|
+
Refresh dist
|
|
6104
|
+
</button>
|
|
6105
|
+
)}
|
|
5939
6106
|
</div>
|
|
5940
6107
|
<div className="shrink-0 px-3 py-1.5 text-[10px] text-slate-500 border-b border-slate-800">
|
|
5941
|
-
|
|
5942
|
-
|
|
6108
|
+
{clientType === "react"
|
|
6109
|
+
? "Run npm commands in the React lab — e.g. npm install lodash. Vite HMR reloads automatically."
|
|
6110
|
+
: "Run npm scripts in the selected webpack app. Generated dist files appear in the explorer as read-only artifacts."}
|
|
5943
6111
|
</div>
|
|
5944
6112
|
<div className="flex-1 overflow-y-auto px-3 py-2 font-mono text-[12px] leading-relaxed">
|
|
5945
|
-
{
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
?
|
|
5949
|
-
:
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
className=
|
|
5959
|
-
|
|
5960
|
-
?
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
6113
|
+
{(() => {
|
|
6114
|
+
const out =
|
|
6115
|
+
clientType === "react"
|
|
6116
|
+
? viteConsoleOutput
|
|
6117
|
+
: mfConsoleOutput;
|
|
6118
|
+
const running =
|
|
6119
|
+
clientType === "react"
|
|
6120
|
+
? viteConsoleRunning
|
|
6121
|
+
: mfConsoleRunning;
|
|
6122
|
+
const sandboxActive =
|
|
6123
|
+
clientType === "react" ? !!viteSandboxId : !!mfSandboxId;
|
|
6124
|
+
if (out.length === 0 && !running) {
|
|
6125
|
+
return (
|
|
6126
|
+
<span className="text-slate-600">
|
|
6127
|
+
{sandboxActive
|
|
6128
|
+
? clientType === "react"
|
|
6129
|
+
? "Run npm install <pkg> to add a package."
|
|
6130
|
+
: "Run npm run build in apps/host, apps/profile, or apps/checkout to inspect dist/."
|
|
6131
|
+
: clientType === "react"
|
|
6132
|
+
? "Start Vite first, then run npm commands here."
|
|
6133
|
+
: "Start webpack first, then run npm commands here."}
|
|
6134
|
+
</span>
|
|
6135
|
+
);
|
|
6136
|
+
}
|
|
6137
|
+
return out.map((line, index) => (
|
|
6138
|
+
<div key={index} className="flex items-start gap-2">
|
|
6139
|
+
<span className="shrink-0 text-[9px] font-bold mt-0.5 w-7 text-right text-cyan-600">
|
|
6140
|
+
cmd
|
|
6141
|
+
</span>
|
|
6142
|
+
<span
|
|
6143
|
+
className={
|
|
6144
|
+
line.kind === "stderr"
|
|
6145
|
+
? "text-red-400 whitespace-pre-wrap"
|
|
6146
|
+
: line.kind === "warn"
|
|
6147
|
+
? "text-amber-400 whitespace-pre-wrap"
|
|
6148
|
+
: line.kind === "info"
|
|
6149
|
+
? "text-slate-500 italic whitespace-pre-wrap"
|
|
6150
|
+
: "text-slate-200 whitespace-pre-wrap"
|
|
6151
|
+
}
|
|
6152
|
+
>
|
|
6153
|
+
{line.text}
|
|
6154
|
+
</span>
|
|
6155
|
+
</div>
|
|
6156
|
+
));
|
|
6157
|
+
})()}
|
|
5972
6158
|
<div ref={mfConsoleEndRef} />
|
|
5973
6159
|
</div>
|
|
5974
6160
|
</div>
|