create-interview-cockpit 0.14.0 → 0.16.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.
@@ -53,6 +53,7 @@ import {
53
53
  startNextjsSandbox,
54
54
  stopModuleFederationSandbox,
55
55
  streamModuleFederationCommand,
56
+ streamNextjsCommand,
56
57
  updateNextjsFiles,
57
58
  updateModuleFederationFiles,
58
59
  stopNextjsSandbox,
@@ -161,6 +162,93 @@ const rest = await fetch(SANDBOX_URL + '/ping');
161
162
  console.log(await rest.json());
162
163
  `;
163
164
 
165
+ function generateScriptPreviewHTML(
166
+ clientCode: string,
167
+ sandboxUrl?: string,
168
+ ): string {
169
+ const codeJSON = JSON.stringify(clientCode);
170
+ const sandboxJSON = JSON.stringify(sandboxUrl ?? "");
171
+
172
+ return `<!DOCTYPE html>
173
+ <html>
174
+ <head>
175
+ <meta charset="utf-8">
176
+ <meta name="viewport" content="width=device-width, initial-scale=1">
177
+ <style>
178
+ *{box-sizing:border-box}
179
+ body{margin:0;background:#020617;color:#e2e8f0;font-family:system-ui,sans-serif;min-height:100vh}
180
+ </style>
181
+ </head>
182
+ <body>
183
+ <script>
184
+ window.__SCRIPT_PREVIEW_CODE__ = ${codeJSON};
185
+ window.__SCRIPT_PREVIEW_SANDBOX_URL__ = ${sandboxJSON};
186
+ window.__scriptPreviewSerialize = function(value) {
187
+ try {
188
+ if (typeof value === 'string') return value;
189
+ return JSON.stringify(value, null, 2);
190
+ } catch {
191
+ return String(value);
192
+ }
193
+ };
194
+ window.__scriptPreviewPost = function(type, payload) {
195
+ try {
196
+ parent.postMessage({ type: type, ...payload }, '*');
197
+ } catch {}
198
+ };
199
+ ['log', 'warn', 'error'].forEach(function(kind) {
200
+ var original = console[kind] ? console[kind].bind(console) : null;
201
+ console[kind] = function() {
202
+ var args = Array.prototype.slice.call(arguments);
203
+ if (original) original.apply(console, args);
204
+ window.__scriptPreviewPost('script-preview-log', {
205
+ kind: kind,
206
+ text: args.map(window.__scriptPreviewSerialize).join(' '),
207
+ });
208
+ };
209
+ });
210
+ window.addEventListener('error', function(event) {
211
+ window.__scriptPreviewPost('script-preview-error', {
212
+ error:
213
+ event.error && event.error.stack
214
+ ? event.error.stack
215
+ : event.message || 'Unknown preview error',
216
+ });
217
+ });
218
+ window.addEventListener('unhandledrejection', function(event) {
219
+ var reason = event.reason;
220
+ window.__scriptPreviewPost('script-preview-error', {
221
+ error:
222
+ reason && reason.stack
223
+ ? reason.stack
224
+ : reason && reason.message
225
+ ? reason.message
226
+ : window.__scriptPreviewSerialize(reason),
227
+ });
228
+ });
229
+ </script>
230
+ <script type="module">
231
+ const SANDBOX_URL = window.__SCRIPT_PREVIEW_SANDBOX_URL__;
232
+ window.SANDBOX_URL = SANDBOX_URL;
233
+ const moduleSource =
234
+ 'const SANDBOX_URL = ' + JSON.stringify(SANDBOX_URL) + ';\\n' +
235
+ window.__SCRIPT_PREVIEW_CODE__;
236
+ const moduleBlob = new Blob([moduleSource], { type: 'text/javascript' });
237
+ const moduleUrl = URL.createObjectURL(moduleBlob);
238
+ try {
239
+ await import(moduleUrl);
240
+ } catch (error) {
241
+ window.__scriptPreviewPost('script-preview-error', {
242
+ error: error && error.stack ? error.stack : String(error),
243
+ });
244
+ } finally {
245
+ URL.revokeObjectURL(moduleUrl);
246
+ }
247
+ </script>
248
+ </body>
249
+ </html>`;
250
+ }
251
+
164
252
  // VS Code Dark+ token colours injected once for prismjs.
165
253
  // react-simple-code-editor uses Prism.highlight() which emits class-based spans;
166
254
  // these inline styles map those classes to the same palette used in the old theme.
@@ -633,6 +721,9 @@ export default function CodeRunnerModal() {
633
721
  const [mfConsoleCommand, setMfConsoleCommand] = useState("npm run build");
634
722
  const [mfConsoleCwd, setMfConsoleCwd] = useState("apps/host");
635
723
  const [mfConsoleRunning, setMfConsoleRunning] = useState(false);
724
+ const [nxConsoleCommand, setNxConsoleCommand] = useState("npm run build");
725
+ const [nxConsoleOutput, setNxConsoleOutput] = useState<OutputLine[]>([]);
726
+ const [nxConsoleRunning, setNxConsoleRunning] = useState(false);
636
727
  const [mfGeneratedFiles, setMfGeneratedFiles] = useState<string[]>([]);
637
728
  const [mfGeneratedFileContents, setMfGeneratedFileContents] = useState<
638
729
  Record<string, string>
@@ -684,6 +775,18 @@ export default function CodeRunnerModal() {
684
775
  // ── Sandbox save state (single combined save) ──────────────────
685
776
  const [sbxNaming, setSbxNaming] = useState(false);
686
777
  const [sbxName, setSbxName] = useState("My Sandbox");
778
+ const [sbxDefaultName, setSbxDefaultName] = useState(
779
+ runnerInitialSandbox?.label ?? "My Sandbox",
780
+ );
781
+ const [scriptSandboxOrigin, setScriptSandboxOrigin] = useState<
782
+ "sandbox" | "browser-security"
783
+ >(runnerInitialSandbox?.origin ?? "sandbox");
784
+ const [scriptClientTab, setScriptClientTab] = useState<"edit" | "preview">(
785
+ runnerInitialSandbox?.origin === "browser-security" ? "preview" : "edit",
786
+ );
787
+ const [scriptPreviewSrcDoc, setScriptPreviewSrcDoc] = useState<string | null>(
788
+ null,
789
+ );
687
790
  const [sbxSaved, setSbxSaved] = useState(false);
688
791
  const [sbxSaving, setSbxSaving] = useState(false);
689
792
  /** When non-null we are editing a previously saved sandbox — Save overwrites, Save As still creates new */
@@ -715,6 +818,15 @@ export default function CodeRunnerModal() {
715
818
  const sbxChatAbortRef = useRef<{ aborted: boolean }>({ aborted: false });
716
819
  const [sbxChatCopiedId, setSbxChatCopiedId] = useState<string | null>(null);
717
820
 
821
+ const openScriptPreview = useCallback(() => {
822
+ setScriptClientTab("preview");
823
+ if (!sandboxUrl) {
824
+ setScriptPreviewSrcDoc(null);
825
+ return;
826
+ }
827
+ setScriptPreviewSrcDoc(generateScriptPreviewHTML(clientCode, sandboxUrl));
828
+ }, [clientCode, sandboxUrl]);
829
+
718
830
  // Keep key ref fresh
719
831
  useEffect(() => {
720
832
  sbxChatKeyRef.current = sbxChatKey;
@@ -752,13 +864,15 @@ export default function CodeRunnerModal() {
752
864
  setSbxSaving(true);
753
865
  try {
754
866
  const origin =
755
- clientType === "react"
756
- ? "react"
757
- : clientType === "nextjs"
758
- ? "nextjs"
759
- : clientType === "module-federation"
760
- ? "module-federation"
761
- : "sandbox";
867
+ scriptSandboxOrigin === "browser-security"
868
+ ? "browser-security"
869
+ : clientType === "react"
870
+ ? "react"
871
+ : clientType === "nextjs"
872
+ ? "nextjs"
873
+ : clientType === "module-federation"
874
+ ? "module-federation"
875
+ : scriptSandboxOrigin;
762
876
  const payload = JSON.stringify(
763
877
  clientType === "script"
764
878
  ? { serverCode, serverLang, clientCode, clientLang }
@@ -865,6 +979,13 @@ export default function CodeRunnerModal() {
865
979
  setClientLang((runnerInitialSandbox.clientLang as Lang) ?? "javascript");
866
980
  setSandboxOutput([]);
867
981
  setActiveSandboxId(runnerInitialSandbox.fileId ?? null);
982
+ setScriptSandboxOrigin(runnerInitialSandbox.origin ?? "sandbox");
983
+ setScriptClientTab(
984
+ runnerInitialSandbox.origin === "browser-security" ? "preview" : "edit",
985
+ );
986
+ setScriptPreviewSrcDoc(null);
987
+ setSbxDefaultName(runnerInitialSandbox.label ?? "My Sandbox");
988
+ setSbxName(runnerInitialSandbox.label ?? "My Sandbox");
868
989
  // Restore client type and React/Next.js files
869
990
  const ct =
870
991
  (runnerInitialSandbox.clientType as FrontendClientType) ?? "script";
@@ -911,6 +1032,71 @@ export default function CodeRunnerModal() {
911
1032
  }
912
1033
  }, [runnerInitialSandbox]);
913
1034
 
1035
+ useEffect(() => {
1036
+ if (clientType !== "script" || scriptSandboxOrigin !== "browser-security") {
1037
+ return;
1038
+ }
1039
+ if (scriptClientTab !== "preview") return;
1040
+ if (!sandboxUrl) {
1041
+ setScriptPreviewSrcDoc(null);
1042
+ return;
1043
+ }
1044
+ setScriptPreviewSrcDoc(generateScriptPreviewHTML(clientCode, sandboxUrl));
1045
+ }, [
1046
+ clientCode,
1047
+ clientType,
1048
+ sandboxUrl,
1049
+ scriptClientTab,
1050
+ scriptSandboxOrigin,
1051
+ ]);
1052
+
1053
+ useEffect(() => {
1054
+ if (clientType !== "script" || scriptSandboxOrigin !== "browser-security") {
1055
+ return;
1056
+ }
1057
+
1058
+ const handler = (event: MessageEvent) => {
1059
+ if (
1060
+ event.data?.type === "script-preview-log" &&
1061
+ typeof event.data.text === "string"
1062
+ ) {
1063
+ const kind =
1064
+ event.data.kind === "error"
1065
+ ? "stderr"
1066
+ : event.data.kind === "warn"
1067
+ ? "warn"
1068
+ : "stdout";
1069
+
1070
+ setSandboxOutput((prev) => [
1071
+ ...prev,
1072
+ {
1073
+ kind,
1074
+ text: event.data.text,
1075
+ source: "client",
1076
+ },
1077
+ ]);
1078
+ }
1079
+
1080
+ if (
1081
+ event.data?.type === "script-preview-error" &&
1082
+ typeof event.data.error === "string"
1083
+ ) {
1084
+ setSandboxOutput((prev) => [
1085
+ ...prev,
1086
+ {
1087
+ kind: "stderr",
1088
+ text: `Preview error: ${event.data.error}`,
1089
+ source: "client",
1090
+ },
1091
+ ]);
1092
+ setSbxBottomTab("output");
1093
+ }
1094
+ };
1095
+
1096
+ window.addEventListener("message", handler);
1097
+ return () => window.removeEventListener("message", handler);
1098
+ }, [clientType, scriptSandboxOrigin]);
1099
+
914
1100
  // Auto-focus is handled inside SyntaxEditor via autoFocus prop.
915
1101
 
916
1102
  // Scroll output to bottom whenever it grows
@@ -1818,6 +2004,36 @@ export default function CodeRunnerModal() {
1818
2004
  refreshModuleFederationGeneratedFiles,
1819
2005
  ]);
1820
2006
 
2007
+ const runNextjsCommand = useCallback(async () => {
2008
+ if (!nxSandboxId || nxConsoleRunning) return;
2009
+ const command = nxConsoleCommand.trim();
2010
+ if (!command) return;
2011
+ setNxConsoleRunning(true);
2012
+ setSbxBottomTab("console");
2013
+ try {
2014
+ await streamNextjsCommand({ id: nxSandboxId, command }, (message) => {
2015
+ if (message.type === "output") {
2016
+ setNxConsoleOutput((prev) => [
2017
+ ...prev,
2018
+ { kind: message.kind, text: message.text, source: "server" },
2019
+ ]);
2020
+ } else if (message.type === "error") {
2021
+ setNxConsoleOutput((prev) => [
2022
+ ...prev,
2023
+ { kind: "stderr", text: message.error, source: "server" },
2024
+ ]);
2025
+ }
2026
+ });
2027
+ } catch (err: any) {
2028
+ setNxConsoleOutput((prev) => [
2029
+ ...prev,
2030
+ { kind: "stderr", text: err?.message ?? String(err), source: "server" },
2031
+ ]);
2032
+ } finally {
2033
+ setNxConsoleRunning(false);
2034
+ }
2035
+ }, [nxConsoleCommand, nxConsoleRunning, nxSandboxId]);
2036
+
1821
2037
  // Clean up Next.js server when the modal is closed or mode changes away from nextjs
1822
2038
  const prevClientTypeRef = useRef(clientType);
1823
2039
  useEffect(() => {
@@ -3133,7 +3349,7 @@ export default function CodeRunnerModal() {
3133
3349
  if (activeSandboxId) {
3134
3350
  await overwriteSandboxSnippet();
3135
3351
  } else {
3136
- setSbxName("My Sandbox");
3352
+ setSbxName(sbxDefaultName);
3137
3353
  setSbxNaming(true);
3138
3354
  setTimeout(() => sbxNameInputRef.current?.focus(), 30);
3139
3355
  }
@@ -3164,7 +3380,7 @@ export default function CodeRunnerModal() {
3164
3380
  type="button"
3165
3381
  onMouseDown={(e) => e.stopPropagation()}
3166
3382
  onClick={() => {
3167
- setSbxName("My Sandbox");
3383
+ setSbxName(sbxDefaultName);
3168
3384
  setSbxNaming(true);
3169
3385
  setTimeout(() => sbxNameInputRef.current?.focus(), 30);
3170
3386
  }}
@@ -3572,24 +3788,71 @@ export default function CodeRunnerModal() {
3572
3788
  </button>
3573
3789
  ))}
3574
3790
  </div>
3575
- <button
3576
- type="button"
3577
- onClick={() => void runClient()}
3578
- disabled={clientRunning || !serverRunning}
3579
- 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-40 transition-colors shrink-0"
3580
- title={
3581
- serverRunning
3582
- ? "Run client (Ctrl+Enter in editor)"
3583
- : "Start the server first"
3584
- }
3585
- >
3586
- {clientRunning ? (
3587
- <Loader2 className="w-3 h-3 animate-spin" />
3588
- ) : (
3589
- <Play className="w-3 h-3" />
3590
- )}
3591
- Run
3592
- </button>
3791
+ {scriptSandboxOrigin === "browser-security" ? (
3792
+ <>
3793
+ <div className="flex items-center rounded overflow-hidden border border-slate-700/50 text-[9px] shrink-0">
3794
+ <button
3795
+ type="button"
3796
+ onClick={() => setScriptClientTab("edit")}
3797
+ className={`flex items-center gap-0.5 px-1.5 py-0.5 transition-colors ${
3798
+ scriptClientTab === "edit"
3799
+ ? "bg-slate-700 text-slate-200"
3800
+ : "text-slate-500 hover:text-slate-400"
3801
+ }`}
3802
+ title="Edit client code"
3803
+ >
3804
+ <Code2 className="w-2.5 h-2.5" />
3805
+ </button>
3806
+ <button
3807
+ type="button"
3808
+ onClick={openScriptPreview}
3809
+ className={`flex items-center gap-0.5 px-1.5 py-0.5 transition-colors ${
3810
+ scriptClientTab === "preview"
3811
+ ? "bg-slate-700 text-slate-200"
3812
+ : "text-slate-500 hover:text-slate-400"
3813
+ }`}
3814
+ title="Browser preview"
3815
+ >
3816
+ <Eye className="w-2.5 h-2.5" />
3817
+ </button>
3818
+ </div>
3819
+ <button
3820
+ type="button"
3821
+ onClick={openScriptPreview}
3822
+ disabled={!serverRunning}
3823
+ 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-40 transition-colors shrink-0"
3824
+ title={
3825
+ serverRunning
3826
+ ? "Open the browser preview"
3827
+ : "Start the server first"
3828
+ }
3829
+ >
3830
+ <Globe className="w-3 h-3" />
3831
+ {scriptClientTab === "preview"
3832
+ ? "Refresh"
3833
+ : "Preview"}
3834
+ </button>
3835
+ </>
3836
+ ) : (
3837
+ <button
3838
+ type="button"
3839
+ onClick={() => void runClient()}
3840
+ disabled={clientRunning || !serverRunning}
3841
+ 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-40 transition-colors shrink-0"
3842
+ title={
3843
+ serverRunning
3844
+ ? "Run client (Ctrl+Enter in editor)"
3845
+ : "Start the server first"
3846
+ }
3847
+ >
3848
+ {clientRunning ? (
3849
+ <Loader2 className="w-3 h-3 animate-spin" />
3850
+ ) : (
3851
+ <Play className="w-3 h-3" />
3852
+ )}
3853
+ Run
3854
+ </button>
3855
+ )}
3593
3856
  </>
3594
3857
  )}
3595
3858
  {/* React/Next/Webpack mode controls */}
@@ -4046,19 +4309,75 @@ export default function CodeRunnerModal() {
4046
4309
  className={`${usesClientExplorer ? "flex-1 min-w-0 relative" : "absolute inset-0"}`}
4047
4310
  >
4048
4311
  {clientType === "script" ? (
4049
- <SyntaxEditor
4050
- value={clientCode}
4051
- onChange={setClientCode}
4052
- onCtrlEnter={() => {
4053
- if (serverRunning) void runClient();
4054
- }}
4055
- language={clientLang}
4056
- fontSize="12px"
4057
- focusRingClass="ring-cyan-500/30"
4058
- placeholder={
4059
- "// SANDBOX_URL is injected automatically\n// Start the server first, then Ctrl+Enter to run"
4060
- }
4061
- />
4312
+ scriptSandboxOrigin === "browser-security" &&
4313
+ scriptClientTab === "preview" ? (
4314
+ <div className="w-full h-full flex flex-col bg-slate-950">
4315
+ <div className="flex items-center gap-1 px-2 py-1 bg-slate-800 border-b border-slate-700 shrink-0">
4316
+ <button
4317
+ type="button"
4318
+ onClick={openScriptPreview}
4319
+ disabled={!serverRunning}
4320
+ className="p-0.5 rounded text-slate-500 hover:text-slate-200 disabled:opacity-30 disabled:cursor-not-allowed transition-colors shrink-0"
4321
+ title="Refresh preview"
4322
+ >
4323
+ <svg
4324
+ className="w-3 h-3"
4325
+ viewBox="0 0 16 16"
4326
+ fill="currentColor"
4327
+ >
4328
+ <path d="M13.65 2.35A8 8 0 1 0 15 8h-2a6 6 0 1 1-1.1-3.48L10 6h5V1l-1.35 1.35z" />
4329
+ </svg>
4330
+ </button>
4331
+ <span className="text-slate-600 text-[9px] font-mono select-none shrink-0">
4332
+ {sandboxUrl
4333
+ ? sandboxUrl.replace(/^https?:\/\//, "")
4334
+ : "Start server to preview"}
4335
+ </span>
4336
+ <div className="flex-1" />
4337
+ <span className="text-[9px] font-mono text-cyan-400 shrink-0">
4338
+ {serverRunning ? "● browser" : "server offline"}
4339
+ </span>
4340
+ </div>
4341
+ {sandboxUrl && scriptPreviewSrcDoc ? (
4342
+ <iframe
4343
+ srcDoc={scriptPreviewSrcDoc}
4344
+ className="flex-1 min-h-0 w-full border-0 bg-white"
4345
+ style={
4346
+ isDraggingResize
4347
+ ? { pointerEvents: "none" }
4348
+ : undefined
4349
+ }
4350
+ title="Browser Security Preview"
4351
+ />
4352
+ ) : (
4353
+ <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">
4354
+ <Globe className="w-8 h-8 text-cyan-400/70" />
4355
+ <p className="text-[12px]">
4356
+ Start the server to open the browser preview.
4357
+ </p>
4358
+ <p className="text-[10px] text-slate-600 max-w-md">
4359
+ Browser security labs render actual pages and
4360
+ iframes, so they use the preview pane instead of
4361
+ the Node client runner.
4362
+ </p>
4363
+ </div>
4364
+ )}
4365
+ </div>
4366
+ ) : (
4367
+ <SyntaxEditor
4368
+ value={clientCode}
4369
+ onChange={setClientCode}
4370
+ onCtrlEnter={() => {
4371
+ if (serverRunning) void runClient();
4372
+ }}
4373
+ language={clientLang}
4374
+ fontSize="12px"
4375
+ focusRingClass="ring-cyan-500/30"
4376
+ placeholder={
4377
+ "// SANDBOX_URL is injected automatically\n// Start the server first, then Ctrl+Enter to run"
4378
+ }
4379
+ />
4380
+ )
4062
4381
  ) : reactClientTab === "edit" ? (
4063
4382
  isActiveModuleFederationGeneratedFile ? (
4064
4383
  <div className="absolute inset-0 flex flex-col bg-slate-950">
@@ -4515,7 +4834,8 @@ export default function CodeRunnerModal() {
4515
4834
  Output
4516
4835
  </button>
4517
4836
  {(clientType === "module-federation" ||
4518
- clientType === "react") && (
4837
+ clientType === "react" ||
4838
+ clientType === "nextjs") && (
4519
4839
  <button
4520
4840
  type="button"
4521
4841
  onClick={() => setSbxBottomTab("console")}
@@ -4684,7 +5004,9 @@ export default function CodeRunnerModal() {
4684
5004
  ? "Start Next.js to launch the live preview"
4685
5005
  : clientType === "react"
4686
5006
  ? "Click Run Vite to start the dev server"
4687
- : "Start the server, then run the client"}
5007
+ : scriptSandboxOrigin === "browser-security"
5008
+ ? "Start the server to open the browser preview"
5009
+ : "Start the server, then run the client"}
4688
5010
  </span>
4689
5011
  )}
4690
5012
  {sandboxOutput.map((line, i) => (
@@ -6036,24 +6358,32 @@ export default function CodeRunnerModal() {
6036
6358
  value={
6037
6359
  clientType === "react"
6038
6360
  ? viteConsoleCommand
6039
- : mfConsoleCommand
6361
+ : clientType === "nextjs"
6362
+ ? nxConsoleCommand
6363
+ : mfConsoleCommand
6040
6364
  }
6041
6365
  onChange={(e) =>
6042
6366
  clientType === "react"
6043
6367
  ? setViteConsoleCommand(e.target.value)
6044
- : setMfConsoleCommand(e.target.value)
6368
+ : clientType === "nextjs"
6369
+ ? setNxConsoleCommand(e.target.value)
6370
+ : setMfConsoleCommand(e.target.value)
6045
6371
  }
6046
6372
  onKeyDown={(e) => {
6047
6373
  if (e.key === "Enter") {
6048
6374
  e.preventDefault();
6049
6375
  if (clientType === "react") void runViteCommand();
6376
+ else if (clientType === "nextjs")
6377
+ void runNextjsCommand();
6050
6378
  else void runModuleFederationCommand();
6051
6379
  }
6052
6380
  }}
6053
6381
  disabled={
6054
6382
  clientType === "react"
6055
6383
  ? !viteSandboxId || viteConsoleRunning
6056
- : !mfSandboxId || mfConsoleRunning
6384
+ : clientType === "nextjs"
6385
+ ? !nxSandboxId || nxConsoleRunning
6386
+ : !mfSandboxId || mfConsoleRunning
6057
6387
  }
6058
6388
  placeholder={
6059
6389
  clientType === "react"
@@ -6067,6 +6397,7 @@ export default function CodeRunnerModal() {
6067
6397
  type="button"
6068
6398
  onClick={() => {
6069
6399
  if (clientType === "react") void runViteCommand();
6400
+ else if (clientType === "nextjs") void runNextjsCommand();
6070
6401
  else void runModuleFederationCommand();
6071
6402
  }}
6072
6403
  disabled={
@@ -6074,16 +6405,22 @@ export default function CodeRunnerModal() {
6074
6405
  ? !viteSandboxId ||
6075
6406
  viteConsoleRunning ||
6076
6407
  !viteConsoleCommand.trim()
6077
- : !mfSandboxId ||
6078
- mfConsoleRunning ||
6079
- !mfConsoleCommand.trim()
6408
+ : clientType === "nextjs"
6409
+ ? !nxSandboxId ||
6410
+ nxConsoleRunning ||
6411
+ !nxConsoleCommand.trim()
6412
+ : !mfSandboxId ||
6413
+ mfConsoleRunning ||
6414
+ !mfConsoleCommand.trim()
6080
6415
  }
6081
6416
  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"
6082
6417
  >
6083
6418
  {(
6084
6419
  clientType === "react"
6085
6420
  ? viteConsoleRunning
6086
- : mfConsoleRunning
6421
+ : clientType === "nextjs"
6422
+ ? nxConsoleRunning
6423
+ : mfConsoleRunning
6087
6424
  ) ? (
6088
6425
  <Loader2 className="w-3 h-3 animate-spin" />
6089
6426
  ) : (
@@ -6107,30 +6444,44 @@ export default function CodeRunnerModal() {
6107
6444
  <div className="shrink-0 px-3 py-1.5 text-[10px] text-slate-500 border-b border-slate-800">
6108
6445
  {clientType === "react"
6109
6446
  ? "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."}
6447
+ : clientType === "nextjs"
6448
+ ? "Run npm commands in the Next.js lab — e.g. npm run build. Hot reload applies on file save."
6449
+ : "Run npm scripts in the selected webpack app. Generated dist files appear in the explorer as read-only artifacts."}
6111
6450
  </div>
6112
6451
  <div className="flex-1 overflow-y-auto px-3 py-2 font-mono text-[12px] leading-relaxed">
6113
6452
  {(() => {
6114
6453
  const out =
6115
6454
  clientType === "react"
6116
6455
  ? viteConsoleOutput
6117
- : mfConsoleOutput;
6456
+ : clientType === "nextjs"
6457
+ ? nxConsoleOutput
6458
+ : mfConsoleOutput;
6118
6459
  const running =
6119
6460
  clientType === "react"
6120
6461
  ? viteConsoleRunning
6121
- : mfConsoleRunning;
6462
+ : clientType === "nextjs"
6463
+ ? nxConsoleRunning
6464
+ : mfConsoleRunning;
6122
6465
  const sandboxActive =
6123
- clientType === "react" ? !!viteSandboxId : !!mfSandboxId;
6466
+ clientType === "react"
6467
+ ? !!viteSandboxId
6468
+ : clientType === "nextjs"
6469
+ ? !!nxSandboxId
6470
+ : !!mfSandboxId;
6124
6471
  if (out.length === 0 && !running) {
6125
6472
  return (
6126
6473
  <span className="text-slate-600">
6127
6474
  {sandboxActive
6128
6475
  ? clientType === "react"
6129
6476
  ? "Run npm install <pkg> to add a package."
6130
- : "Run npm run build in apps/host, apps/profile, or apps/checkout to inspect dist/."
6477
+ : clientType === "nextjs"
6478
+ ? "Run npm run build or any npm command here."
6479
+ : "Run npm run build in apps/host, apps/profile, or apps/checkout to inspect dist/."
6131
6480
  : clientType === "react"
6132
6481
  ? "Start Vite first, then run npm commands here."
6133
- : "Start webpack first, then run npm commands here."}
6482
+ : clientType === "nextjs"
6483
+ ? "Start Next.js first, then run npm commands here."
6484
+ : "Start webpack first, then run npm commands here."}
6134
6485
  </span>
6135
6486
  );
6136
6487
  }