create-interview-cockpit 0.6.0 → 0.8.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.
@@ -1,5 +1,7 @@
1
1
  import type { FrontendLabWorkspace } from "./types";
2
2
 
3
+ export type FrontendLabType = FrontendLabWorkspace["type"];
4
+
3
5
  // ── Default file contents ────────────────────────────────────────────────────
4
6
 
5
7
  const REACT_DEFAULT_FILES: Record<string, string> = {
@@ -207,6 +209,487 @@ export interface User {
207
209
  `,
208
210
  };
209
211
 
212
+ const MODULE_FEDERATION_DEFAULT_FILES: Record<string, string> = {
213
+ "README.md": `# Webpack Module Federation Lab
214
+
215
+ This lab uses real webpack 5 + webpack-dev-server + Module Federation.
216
+
217
+ ## What is here
218
+
219
+ - package.json runs three apps together: a host plus two remotes
220
+ - apps/host consumes federated modules from the remotes
221
+ - apps/profile exposes a profile widget
222
+ - apps/checkout exposes a checkout widget
223
+
224
+ ## Good experiments
225
+
226
+ 1. Break one remote URL in apps/host/webpack.config.js and see how the host fails.
227
+ 2. Rename an exposed module in a remote without updating the host import.
228
+ 3. Stop sharing React as a singleton and inspect the runtime behavior.
229
+ 4. Add a new remote by copying an existing app and wiring it into the host.
230
+
231
+ ## Notes
232
+
233
+ - Ports are injected by the lab runner through environment variables.
234
+ - If you change package.json, restart the webpack lab so dependencies/scripts are re-read.
235
+ `,
236
+ "package.json": `{
237
+ "name": "webpack-module-federation-lab",
238
+ "private": true,
239
+ "scripts": {
240
+ "dev": "concurrently -k -n host,profile,checkout -c cyan,magenta,yellow 'npm --prefix apps/host run dev' 'npm --prefix apps/profile run dev' 'npm --prefix apps/checkout run dev'",
241
+ "dev:host": "npm --prefix apps/host run dev",
242
+ "dev:profile": "npm --prefix apps/profile run dev",
243
+ "dev:checkout": "npm --prefix apps/checkout run dev",
244
+ "build": "npm --prefix apps/host run build && npm --prefix apps/profile run build && npm --prefix apps/checkout run build",
245
+ "build:host": "npm --prefix apps/host run build",
246
+ "build:profile": "npm --prefix apps/profile run build",
247
+ "build:checkout": "npm --prefix apps/checkout run build"
248
+ },
249
+ "dependencies": {
250
+ "react": "^19.0.0",
251
+ "react-dom": "^19.0.0"
252
+ },
253
+ "devDependencies": {
254
+ "concurrently": "^9.2.1",
255
+ "esbuild": "^0.28.0",
256
+ "esbuild-loader": "^4.4.3",
257
+ "html-webpack-plugin": "^5.6.7",
258
+ "webpack": "^5.106.2",
259
+ "webpack-cli": "^7.0.2",
260
+ "webpack-dev-server": "^5.2.3"
261
+ }
262
+ }
263
+ `,
264
+ "apps/host/public/index.html": `<!doctype html>
265
+ <html lang="en">
266
+ <head>
267
+ <meta charset="utf-8" />
268
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
269
+ <title>Host App</title>
270
+ </head>
271
+ <body>
272
+ <div id="root"></div>
273
+ </body>
274
+ </html>
275
+ `,
276
+ "apps/host/package.json": `{
277
+ "name": "@mf-lab/host",
278
+ "private": true,
279
+ "scripts": {
280
+ "dev": "webpack serve --config webpack.config.js",
281
+ "build": "webpack --config webpack.config.js"
282
+ }
283
+ }
284
+ `,
285
+ "apps/host/src/index.jsx": `import("./bootstrap");
286
+ `,
287
+ "apps/host/src/bootstrap.jsx": `import React from "react";
288
+ import { createRoot } from "react-dom/client";
289
+ import App from "./App";
290
+
291
+ const root = createRoot(document.getElementById("root"));
292
+
293
+ root.render(
294
+ <React.StrictMode>
295
+ <App />
296
+ </React.StrictMode>,
297
+ );
298
+ `,
299
+ "apps/host/src/App.jsx": `import React, { Suspense } from "react";
300
+
301
+ const ProfileCard = React.lazy(() => import("profile/ProfileCard"));
302
+ const CheckoutPanel = React.lazy(() => import("checkout/CheckoutPanel"));
303
+
304
+ function RemoteBoundary({ title, children }) {
305
+ return (
306
+ <div
307
+ style={{
308
+ border: "1px solid #cbd5e1",
309
+ borderRadius: "0.75rem",
310
+ padding: "1rem",
311
+ background: "#fff",
312
+ }}
313
+ >
314
+ <div style={{ fontSize: "0.8rem", color: "#64748b", marginBottom: "0.75rem" }}>
315
+ {title}
316
+ </div>
317
+ <Suspense fallback={<p style={{ color: "#64748b" }}>Loading remote...</p>}>
318
+ {children}
319
+ </Suspense>
320
+ </div>
321
+ );
322
+ }
323
+
324
+ export default function App() {
325
+ return (
326
+ <main
327
+ style={{
328
+ minHeight: "100vh",
329
+ margin: 0,
330
+ padding: "2rem",
331
+ background: "linear-gradient(135deg, #e0f2fe 0%, #f8fafc 50%, #fef3c7 100%)",
332
+ fontFamily: "ui-sans-serif, system-ui, sans-serif",
333
+ }}
334
+ >
335
+ <div style={{ maxWidth: "1100px", margin: "0 auto" }}>
336
+ <div style={{ marginBottom: "1.5rem" }}>
337
+ <p style={{ margin: 0, color: "#0369a1", fontSize: "0.8rem", letterSpacing: "0.08em", textTransform: "uppercase" }}>
338
+ Webpack 5 Host
339
+ </p>
340
+ <h1 style={{ margin: "0.35rem 0 0", fontSize: "2rem", color: "#0f172a" }}>
341
+ Module Federation Playground
342
+ </h1>
343
+ <p style={{ color: "#475569", maxWidth: "52rem" }}>
344
+ The host renders two independently built remotes. Change a remote expose, shared dependency,
345
+ or URL in the webpack configs to see the same failures you would hit in a real setup.
346
+ </p>
347
+ </div>
348
+
349
+ <section
350
+ style={{
351
+ display: "grid",
352
+ gridTemplateColumns: "repeat(auto-fit, minmax(280px, 1fr))",
353
+ gap: "1rem",
354
+ }}
355
+ >
356
+ <RemoteBoundary title="Remote: profile/ProfileCard">
357
+ <ProfileCard />
358
+ </RemoteBoundary>
359
+ <RemoteBoundary title="Remote: checkout/CheckoutPanel">
360
+ <CheckoutPanel />
361
+ </RemoteBoundary>
362
+ </section>
363
+ </div>
364
+ </main>
365
+ );
366
+ }
367
+ `,
368
+ "apps/host/webpack.config.js": `const path = require("path");
369
+ const HtmlWebpackPlugin = require("html-webpack-plugin");
370
+ const { ModuleFederationPlugin } = require("webpack").container;
371
+
372
+ const hostPort = Number(process.env.HOST_PORT || 3100);
373
+ const profilePort = Number(process.env.PROFILE_PORT || 3101);
374
+ const checkoutPort = Number(process.env.CHECKOUT_PORT || 3102);
375
+
376
+ module.exports = {
377
+ mode: "development",
378
+ entry: path.resolve(__dirname, "./src/index.jsx"),
379
+ output: {
380
+ path: path.resolve(__dirname, "./dist"),
381
+ publicPath: "http://localhost:" + hostPort + "/",
382
+ clean: true,
383
+ },
384
+ resolve: {
385
+ extensions: [".js", ".jsx"],
386
+ },
387
+ module: {
388
+ rules: [
389
+ {
390
+ test: /\\.(js|jsx)$/,
391
+ exclude: /node_modules/,
392
+ use: {
393
+ loader: "esbuild-loader",
394
+ options: {
395
+ loader: "jsx",
396
+ jsx: "automatic",
397
+ target: "es2020",
398
+ },
399
+ },
400
+ },
401
+ ],
402
+ },
403
+ devServer: {
404
+ port: hostPort,
405
+ historyApiFallback: true,
406
+ hot: true,
407
+ headers: {
408
+ "Access-Control-Allow-Origin": "*",
409
+ },
410
+ },
411
+ plugins: [
412
+ new ModuleFederationPlugin({
413
+ name: "host",
414
+ remotes: {
415
+ profile: "profile@http://localhost:" + profilePort + "/remoteEntry.js",
416
+ checkout: "checkout@http://localhost:" + checkoutPort + "/remoteEntry.js",
417
+ },
418
+ shared: {
419
+ react: { singleton: true, requiredVersion: false },
420
+ "react-dom": { singleton: true, requiredVersion: false },
421
+ },
422
+ }),
423
+ new HtmlWebpackPlugin({
424
+ template: path.resolve(__dirname, "./public/index.html"),
425
+ }),
426
+ ],
427
+ };
428
+ `,
429
+ "apps/profile/public/index.html": `<!doctype html>
430
+ <html lang="en">
431
+ <head>
432
+ <meta charset="utf-8" />
433
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
434
+ <title>Profile Remote</title>
435
+ </head>
436
+ <body>
437
+ <div id="root"></div>
438
+ </body>
439
+ </html>
440
+ `,
441
+ "apps/profile/package.json": `{
442
+ "name": "@mf-lab/profile",
443
+ "private": true,
444
+ "scripts": {
445
+ "dev": "webpack serve --config webpack.config.js",
446
+ "build": "webpack --config webpack.config.js"
447
+ }
448
+ }
449
+ `,
450
+ "apps/profile/src/index.jsx": `import("./bootstrap");
451
+ `,
452
+ "apps/profile/src/bootstrap.jsx": `import React from "react";
453
+ import { createRoot } from "react-dom/client";
454
+ import App from "./App";
455
+
456
+ const root = createRoot(document.getElementById("root"));
457
+
458
+ root.render(
459
+ <React.StrictMode>
460
+ <App />
461
+ </React.StrictMode>,
462
+ );
463
+ `,
464
+ "apps/profile/src/App.jsx": `import React from "react";
465
+ import ProfileCard from "./ProfileCard";
466
+
467
+ export default function App() {
468
+ return (
469
+ <main style={{ padding: "2rem", fontFamily: "ui-sans-serif, system-ui, sans-serif", background: "#f8fafc", minHeight: "100vh" }}>
470
+ <p style={{ margin: 0, color: "#7c3aed", fontSize: "0.8rem", letterSpacing: "0.08em", textTransform: "uppercase" }}>
471
+ Remote App
472
+ </p>
473
+ <h1 style={{ margin: "0.35rem 0 1rem", color: "#1e293b" }}>Profile</h1>
474
+ <ProfileCard />
475
+ </main>
476
+ );
477
+ }
478
+ `,
479
+ "apps/profile/src/ProfileCard.jsx": `import React from "react";
480
+
481
+ export default function ProfileCard() {
482
+ return (
483
+ <section>
484
+ <h2 style={{ marginTop: 0, color: "#1e293b" }}>Federated profile card</h2>
485
+ <p style={{ color: "#475569" }}>
486
+ This component is exposed from the profile remote and consumed by the host at runtime.
487
+ </p>
488
+ <dl style={{ display: "grid", gridTemplateColumns: "max-content 1fr", gap: "0.5rem 1rem", margin: 0 }}>
489
+ <dt style={{ color: "#64748b" }}>Owner</dt>
490
+ <dd style={{ margin: 0, color: "#0f172a" }}>Composable Platform Team</dd>
491
+ <dt style={{ color: "#64748b" }}>Build</dt>
492
+ <dd style={{ margin: 0, color: "#0f172a" }}>profile/ProfileCard</dd>
493
+ </dl>
494
+ </section>
495
+ );
496
+ }
497
+ `,
498
+ "apps/profile/webpack.config.js": `const path = require("path");
499
+ const HtmlWebpackPlugin = require("html-webpack-plugin");
500
+ const { ModuleFederationPlugin } = require("webpack").container;
501
+
502
+ const profilePort = Number(process.env.PROFILE_PORT || 3101);
503
+
504
+ module.exports = {
505
+ mode: "development",
506
+ entry: path.resolve(__dirname, "./src/index.jsx"),
507
+ output: {
508
+ path: path.resolve(__dirname, "./dist"),
509
+ publicPath: "http://localhost:" + profilePort + "/",
510
+ clean: true,
511
+ },
512
+ resolve: {
513
+ extensions: [".js", ".jsx"],
514
+ },
515
+ module: {
516
+ rules: [
517
+ {
518
+ test: /\\.(js|jsx)$/,
519
+ exclude: /node_modules/,
520
+ use: {
521
+ loader: "esbuild-loader",
522
+ options: {
523
+ loader: "jsx",
524
+ jsx: "automatic",
525
+ target: "es2020",
526
+ },
527
+ },
528
+ },
529
+ ],
530
+ },
531
+ devServer: {
532
+ port: profilePort,
533
+ hot: true,
534
+ headers: {
535
+ "Access-Control-Allow-Origin": "*",
536
+ },
537
+ },
538
+ plugins: [
539
+ new ModuleFederationPlugin({
540
+ name: "profile",
541
+ filename: "remoteEntry.js",
542
+ exposes: {
543
+ "./ProfileCard": path.resolve(__dirname, "./src/ProfileCard.jsx"),
544
+ },
545
+ shared: {
546
+ react: { singleton: true, requiredVersion: false },
547
+ "react-dom": { singleton: true, requiredVersion: false },
548
+ },
549
+ }),
550
+ new HtmlWebpackPlugin({
551
+ template: path.resolve(__dirname, "./public/index.html"),
552
+ }),
553
+ ],
554
+ };
555
+ `,
556
+ "apps/checkout/public/index.html": `<!doctype html>
557
+ <html lang="en">
558
+ <head>
559
+ <meta charset="utf-8" />
560
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
561
+ <title>Checkout Remote</title>
562
+ </head>
563
+ <body>
564
+ <div id="root"></div>
565
+ </body>
566
+ </html>
567
+ `,
568
+ "apps/checkout/package.json": `{
569
+ "name": "@mf-lab/checkout",
570
+ "private": true,
571
+ "scripts": {
572
+ "dev": "webpack serve --config webpack.config.js",
573
+ "build": "webpack --config webpack.config.js"
574
+ }
575
+ }
576
+ `,
577
+ "apps/checkout/src/index.jsx": `import("./bootstrap");
578
+ `,
579
+ "apps/checkout/src/bootstrap.jsx": `import React from "react";
580
+ import { createRoot } from "react-dom/client";
581
+ import App from "./App";
582
+
583
+ const root = createRoot(document.getElementById("root"));
584
+
585
+ root.render(
586
+ <React.StrictMode>
587
+ <App />
588
+ </React.StrictMode>,
589
+ );
590
+ `,
591
+ "apps/checkout/src/App.jsx": `import React from "react";
592
+ import CheckoutPanel from "./CheckoutPanel";
593
+
594
+ export default function App() {
595
+ return (
596
+ <main style={{ padding: "2rem", fontFamily: "ui-sans-serif, system-ui, sans-serif", background: "#fff7ed", minHeight: "100vh" }}>
597
+ <p style={{ margin: 0, color: "#ea580c", fontSize: "0.8rem", letterSpacing: "0.08em", textTransform: "uppercase" }}>
598
+ Remote App
599
+ </p>
600
+ <h1 style={{ margin: "0.35rem 0 1rem", color: "#7c2d12" }}>Checkout</h1>
601
+ <CheckoutPanel />
602
+ </main>
603
+ );
604
+ }
605
+ `,
606
+ "apps/checkout/src/CheckoutPanel.jsx": `import React from "react";
607
+
608
+ export default function CheckoutPanel() {
609
+ return (
610
+ <section>
611
+ <h2 style={{ marginTop: 0, color: "#7c2d12" }}>Federated checkout panel</h2>
612
+ <p style={{ color: "#9a3412" }}>
613
+ This remote can evolve independently from the host as long as the public module contract stays stable.
614
+ </p>
615
+ <button
616
+ type="button"
617
+ style={{
618
+ border: 0,
619
+ borderRadius: "999px",
620
+ background: "#fb923c",
621
+ color: "#431407",
622
+ padding: "0.65rem 1rem",
623
+ fontWeight: 700,
624
+ cursor: "pointer",
625
+ }}
626
+ >
627
+ Ship order
628
+ </button>
629
+ </section>
630
+ );
631
+ }
632
+ `,
633
+ "apps/checkout/webpack.config.js": `const path = require("path");
634
+ const HtmlWebpackPlugin = require("html-webpack-plugin");
635
+ const { ModuleFederationPlugin } = require("webpack").container;
636
+
637
+ const checkoutPort = Number(process.env.CHECKOUT_PORT || 3102);
638
+
639
+ module.exports = {
640
+ mode: "development",
641
+ entry: path.resolve(__dirname, "./src/index.jsx"),
642
+ output: {
643
+ path: path.resolve(__dirname, "./dist"),
644
+ publicPath: "http://localhost:" + checkoutPort + "/",
645
+ clean: true,
646
+ },
647
+ resolve: {
648
+ extensions: [".js", ".jsx"],
649
+ },
650
+ module: {
651
+ rules: [
652
+ {
653
+ test: /\\.(js|jsx)$/,
654
+ exclude: /node_modules/,
655
+ use: {
656
+ loader: "esbuild-loader",
657
+ options: {
658
+ loader: "jsx",
659
+ jsx: "automatic",
660
+ target: "es2020",
661
+ },
662
+ },
663
+ },
664
+ ],
665
+ },
666
+ devServer: {
667
+ port: checkoutPort,
668
+ hot: true,
669
+ headers: {
670
+ "Access-Control-Allow-Origin": "*",
671
+ },
672
+ },
673
+ plugins: [
674
+ new ModuleFederationPlugin({
675
+ name: "checkout",
676
+ filename: "remoteEntry.js",
677
+ exposes: {
678
+ "./CheckoutPanel": path.resolve(__dirname, "./src/CheckoutPanel.jsx"),
679
+ },
680
+ shared: {
681
+ react: { singleton: true, requiredVersion: false },
682
+ "react-dom": { singleton: true, requiredVersion: false },
683
+ },
684
+ }),
685
+ new HtmlWebpackPlugin({
686
+ template: path.resolve(__dirname, "./public/index.html"),
687
+ }),
688
+ ],
689
+ };
690
+ `,
691
+ };
692
+
210
693
  // ── Lab workspace constructors ────────────────────────────────────────────────
211
694
 
212
695
  export const DEFAULT_REACT_LAB: FrontendLabWorkspace = {
@@ -225,13 +708,23 @@ export const DEFAULT_NEXTJS_LAB: FrontendLabWorkspace = {
225
708
  files: NEXTJS_DEFAULT_FILES,
226
709
  };
227
710
 
228
- export function defaultForType(type: "react" | "nextjs"): FrontendLabWorkspace {
229
- return type === "nextjs" ? DEFAULT_NEXTJS_LAB : DEFAULT_REACT_LAB;
711
+ export const DEFAULT_MODULE_FEDERATION_LAB: FrontendLabWorkspace = {
712
+ version: 1,
713
+ label: "Webpack Module Federation Lab",
714
+ type: "module-federation",
715
+ activeFile: "apps/host/src/App.jsx",
716
+ files: MODULE_FEDERATION_DEFAULT_FILES,
717
+ };
718
+
719
+ export function defaultForType(type: FrontendLabType): FrontendLabWorkspace {
720
+ if (type === "nextjs") return DEFAULT_NEXTJS_LAB;
721
+ if (type === "module-federation") return DEFAULT_MODULE_FEDERATION_LAB;
722
+ return DEFAULT_REACT_LAB;
230
723
  }
231
724
 
232
725
  export function cloneFrontendLabWorkspace(
233
726
  workspace?: FrontendLabWorkspace | null,
234
- type?: "react" | "nextjs",
727
+ type?: FrontendLabType,
235
728
  ): FrontendLabWorkspace {
236
729
  const resolvedType = workspace?.type ?? type ?? "react";
237
730
  const defaults = defaultForType(resolvedType);
@@ -276,8 +769,12 @@ export function parseFrontendLabWorkspace(
276
769
  );
277
770
  if (Object.keys(files).length === 0) return null;
278
771
 
279
- const type: "react" | "nextjs" =
280
- parsed.type === "nextjs" ? "nextjs" : "react";
772
+ const type: FrontendLabType =
773
+ parsed.type === "nextjs"
774
+ ? "nextjs"
775
+ : parsed.type === "module-federation"
776
+ ? "module-federation"
777
+ : "react";
281
778
 
282
779
  return cloneFrontendLabWorkspace({
283
780
  version: 1,
@@ -304,6 +801,11 @@ export function getEntryFile(workspace: FrontendLabWorkspace): string {
304
801
  ? "app/page.tsx"
305
802
  : Object.keys(workspace.files)[0];
306
803
  }
804
+ if (workspace.type === "module-federation") {
805
+ return workspace.files["apps/host/src/App.jsx"]
806
+ ? "apps/host/src/App.jsx"
807
+ : Object.keys(workspace.files)[0];
808
+ }
307
809
  return workspace.files["App.tsx"]
308
810
  ? "App.tsx"
309
811
  : Object.keys(workspace.files)[0];
@@ -313,6 +815,17 @@ export function getEntryFile(workspace: FrontendLabWorkspace): string {
313
815
  export function getFrontendLabFileOrder(
314
816
  workspace: FrontendLabWorkspace,
315
817
  ): string[] {
818
+ if (workspace.type === "module-federation") {
819
+ const preferred = ["README.md", "package.json"];
820
+ const rest = Object.keys(workspace.files)
821
+ .filter((name) => !preferred.includes(name))
822
+ .sort((a, b) => {
823
+ const ad = a.split("/").length;
824
+ const bd = b.split("/").length;
825
+ return ad !== bd ? ad - bd : a.localeCompare(b);
826
+ });
827
+ return preferred.filter((name) => workspace.files[name]).concat(rest);
828
+ }
316
829
  const allFiles = Object.keys(workspace.files).sort((a, b) => {
317
830
  // Sort by folder depth first, then alphabetically
318
831
  const ad = a.split("/").length;
@@ -212,7 +212,14 @@ interface Store {
212
212
  code: string,
213
213
  language: string,
214
214
  label: string,
215
- origin: "user" | "ai" | "sandbox" | "infra" | "react" | "nextjs",
215
+ origin:
216
+ | "user"
217
+ | "ai"
218
+ | "sandbox"
219
+ | "infra"
220
+ | "react"
221
+ | "nextjs"
222
+ | "module-federation",
216
223
  ) => Promise<import("./types").ContextFile>;
217
224
  clearMessages: (questionId: string) => Promise<void>;
218
225
 
@@ -273,7 +280,7 @@ interface Store {
273
280
  clientLang: string;
274
281
  fileId?: string;
275
282
  /** If set, the client panel opens in React or Next.js preview mode instead of script mode */
276
- clientType?: "script" | "react" | "nextjs";
283
+ clientType?: "script" | "react" | "nextjs" | "module-federation";
277
284
  reactFiles?: Record<string, string> | null;
278
285
  reactActiveFile?: string | null;
279
286
  } | null;
@@ -287,7 +294,7 @@ interface Store {
287
294
  clientLang: string,
288
295
  fileId?: string,
289
296
  opts?: {
290
- clientType?: "script" | "react" | "nextjs";
297
+ clientType?: "script" | "react" | "nextjs" | "module-federation";
291
298
  reactFiles?: Record<string, string>;
292
299
  reactActiveFile?: string;
293
300
  },
@@ -311,7 +318,7 @@ interface Store {
311
318
  openInfraLab: (workspace?: InfraLabWorkspace, fileId?: string) => void;
312
319
  closeInfraLab: () => void;
313
320
 
314
- // ── Frontend Labs (React / Next.js) — open inside the sandbox ──
321
+ // ── Frontend Labs (React / Next.js / Module Federation) — open inside the sandbox ──
315
322
  openReactLab: (
316
323
  workspace?: FrontendLabWorkspace,
317
324
  fileId?: string,
@@ -324,6 +331,12 @@ interface Store {
324
331
  serverCode?: string,
325
332
  serverLang?: string,
326
333
  ) => void;
334
+ openModuleFederationLab: (
335
+ workspace?: FrontendLabWorkspace,
336
+ fileId?: string,
337
+ serverCode?: string,
338
+ serverLang?: string,
339
+ ) => void;
327
340
  }
328
341
 
329
342
  export const useStore = create<Store>((set, get) => ({
@@ -959,6 +972,24 @@ export const useStore = create<Store>((set, get) => ({
959
972
  },
960
973
  runnerInitialFileId: null,
961
974
  }),
975
+ openModuleFederationLab: (workspace?, fileId?, serverCode?, serverLang?) =>
976
+ set({
977
+ showCodeRunner: true,
978
+ showInfraLab: false,
979
+ runnerInitialCode: "",
980
+ runnerInitialLanguage: "typescript",
981
+ runnerInitialSandbox: {
982
+ serverCode: serverCode ?? DEFAULT_SERVER_CODE,
983
+ serverLang: serverLang ?? "typescript",
984
+ clientCode: "",
985
+ clientLang: "javascript",
986
+ fileId,
987
+ clientType: "module-federation",
988
+ reactFiles: workspace?.files ?? null,
989
+ reactActiveFile: workspace?.activeFile ?? null,
990
+ },
991
+ runnerInitialFileId: null,
992
+ }),
962
993
 
963
994
  overwriteContextFileContent: async (questionId, fileId, content) => {
964
995
  await api.overwriteContextFileContent(questionId, fileId, content);
@@ -5,7 +5,8 @@ export type ContextFileOrigin =
5
5
  | "sandbox"
6
6
  | "infra"
7
7
  | "react"
8
- | "nextjs";
8
+ | "nextjs"
9
+ | "module-federation";
9
10
 
10
11
  export interface ContextFile {
11
12
  id: string;
@@ -28,7 +29,7 @@ export interface FrontendLabWorkspace {
28
29
  version: 1;
29
30
  label: string;
30
31
  /** Determines defaults, entry file, and file-tree conventions. */
31
- type: "react" | "nextjs";
32
+ type: "react" | "nextjs" | "module-federation";
32
33
  activeFile: string;
33
34
  files: Record<string, string>;
34
35
  }
@@ -1 +1,30 @@
1
- {"root":["./src/app.tsx","./src/api.ts","./src/main.tsx","./src/store.ts","./src/types.ts","./src/vite-env.d.ts","./src/components/aisettingsmodal.tsx","./src/components/annotationdialog.tsx","./src/components/chatmessage.tsx","./src/components/chatview.tsx","./src/components/codecontextpanel.tsx","./src/components/codelineannotationpopup.tsx","./src/components/coderunnermodal.tsx","./src/components/docrefmodal.tsx","./src/components/fileattachments.tsx","./src/components/filepickermodal.tsx","./src/components/fileviewermodal.tsx","./src/components/linkedconvospicker.tsx","./src/components/markdownrenderer.tsx","./src/components/mermaiddiagram.tsx","./src/components/plotembed.tsx","./src/components/sidebar.tsx","./src/components/textannotator.tsx","./src/components/vizcraftembed.tsx","./src/components/workspaceswitcher.tsx"],"version":"5.9.3"}
1
+ {
2
+ "root": [
3
+ "./src/app.tsx",
4
+ "./src/api.ts",
5
+ "./src/main.tsx",
6
+ "./src/store.ts",
7
+ "./src/types.ts",
8
+ "./src/vite-env.d.ts",
9
+ "./src/components/aisettingsmodal.tsx",
10
+ "./src/components/annotationdialog.tsx",
11
+ "./src/components/chatmessage.tsx",
12
+ "./src/components/chatview.tsx",
13
+ "./src/components/codecontextpanel.tsx",
14
+ "./src/components/codelineannotationpopup.tsx",
15
+ "./src/components/coderunnermodal.tsx",
16
+ "./src/components/docrefmodal.tsx",
17
+ "./src/components/fileattachments.tsx",
18
+ "./src/components/filepickermodal.tsx",
19
+ "./src/components/fileviewermodal.tsx",
20
+ "./src/components/linkedconvospicker.tsx",
21
+ "./src/components/markdownrenderer.tsx",
22
+ "./src/components/mermaiddiagram.tsx",
23
+ "./src/components/plotembed.tsx",
24
+ "./src/components/sidebar.tsx",
25
+ "./src/components/textannotator.tsx",
26
+ "./src/components/vizcraftembed.tsx",
27
+ "./src/components/workspaceswitcher.tsx"
28
+ ],
29
+ "version": "5.9.3"
30
+ }