create-interview-cockpit 0.15.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.
@@ -224,7 +224,8 @@ interface Store {
224
224
  | "infra"
225
225
  | "react"
226
226
  | "nextjs"
227
- | "module-federation",
227
+ | "module-federation"
228
+ | "canvas",
228
229
  ) => Promise<import("./types").ContextFile>;
229
230
  clearMessages: (questionId: string) => Promise<void>;
230
231
 
@@ -356,6 +357,13 @@ interface Store {
356
357
  serverCode?: string,
357
358
  serverLang?: string,
358
359
  ) => void;
360
+
361
+ // ── Canvas Lab ───────────────────────────────────────────────
362
+ showCanvasLab: boolean;
363
+ canvasLabInitialCode: string | null;
364
+ canvasLabInitialFileId: string | null;
365
+ openCanvasLab: (code?: string, fileId?: string) => void;
366
+ closeCanvasLab: () => void;
359
367
  }
360
368
 
361
369
  export const useStore = create<Store>((set, get) => ({
@@ -387,6 +395,9 @@ export const useStore = create<Store>((set, get) => ({
387
395
  showInfraLab: false,
388
396
  runnerInitialInfra: null,
389
397
  runnerInitialInfraFileId: null,
398
+ showCanvasLab: false,
399
+ canvasLabInitialCode: null,
400
+ canvasLabInitialFileId: null,
390
401
 
391
402
  // ── Workspaces ───────────────────────────────────────────────
392
403
  workspaces: [],
@@ -1076,6 +1087,18 @@ export const useStore = create<Store>((set, get) => ({
1076
1087
  openBrowserSecurityLab: () => set({ showBrowserSecurityLab: true }),
1077
1088
  closeBrowserSecurityLab: () => set({ showBrowserSecurityLab: false }),
1078
1089
  closeInfraLab: () => set({ showInfraLab: false }),
1090
+ openCanvasLab: (code?, fileId?) =>
1091
+ set({
1092
+ showCanvasLab: true,
1093
+ canvasLabInitialCode: code ?? null,
1094
+ canvasLabInitialFileId: fileId ?? null,
1095
+ }),
1096
+ closeCanvasLab: () =>
1097
+ set({
1098
+ showCanvasLab: false,
1099
+ canvasLabInitialCode: null,
1100
+ canvasLabInitialFileId: null,
1101
+ }),
1079
1102
 
1080
1103
  fetchAiSettings: async () => {
1081
1104
  const settings = await api.fetchAiSettings();
@@ -7,7 +7,8 @@ export type ContextFileOrigin =
7
7
  | "infra"
8
8
  | "react"
9
9
  | "nextjs"
10
- | "module-federation";
10
+ | "module-federation"
11
+ | "canvas";
11
12
 
12
13
  export interface ContextFile {
13
14
  id: string;
@@ -51,6 +52,7 @@ export interface WorkspaceMeta {
51
52
  id: string;
52
53
  name: string;
53
54
  type: "local" | "google_drive";
55
+ questionSortOrder?: "name" | "createdAt";
54
56
  driveConfig?: {
55
57
  folderId: string;
56
58
  folderName: string;
@@ -1,12 +1,19 @@
1
- import { defineConfig } from "vite";
1
+ import { defineConfig, loadEnv } from "vite";
2
2
  import react from "@vitejs/plugin-react";
3
3
 
4
- export default defineConfig({
5
- plugins: [react()],
6
- server: {
7
- port: 5173,
8
- proxy: {
9
- "/api": "http://localhost:3001",
4
+ export default defineConfig(({ mode }) => {
5
+ // Load the root .env (one level above client/)
6
+ const env = loadEnv(mode, "../", "");
7
+ const clientPort = parseInt(env.CLIENT_PORT || "5173", 10);
8
+ const serverPort = parseInt(env.PORT || "3001", 10);
9
+
10
+ return {
11
+ plugins: [react()],
12
+ server: {
13
+ port: clientPort,
14
+ proxy: {
15
+ "/api": `http://localhost:${serverPort}`,
16
+ },
10
17
  },
11
- },
18
+ };
12
19
  });
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "0.13.0"
2
+ "version": "0.14.0"
3
3
  }
@@ -330,6 +330,7 @@ export async function syncWorkspace(
330
330
  let codeContextFiles: string[] = [];
331
331
  let codeAnnotations: storage.Question["codeAnnotations"] = {};
332
332
  let parentTitle: string | null = null;
333
+ let restoredCreatedAt: string | null = null;
333
334
  const restoredContextFiles: storage.ContextFile[] = [];
334
335
 
335
336
  if (filename.endsWith(".json")) {
@@ -342,6 +343,7 @@ export async function syncWorkspace(
342
343
  codeContextFiles = parsed.codeContextFiles || [];
343
344
  codeAnnotations = parsed.codeAnnotations || {};
344
345
  parentTitle = parsed.parentTitle || null;
346
+ if (parsed.createdAt) restoredCreatedAt = parsed.createdAt;
345
347
 
346
348
  // Restore code snippet context files (user/ai origin)
347
349
  if (
@@ -399,7 +401,7 @@ export async function syncWorkspace(
399
401
  contextFiles: restoredContextFiles,
400
402
  messages,
401
403
  codeAnnotations,
402
- createdAt: new Date().toISOString(),
404
+ createdAt: restoredCreatedAt ?? new Date().toISOString(),
403
405
  };
404
406
  await storage.saveQuestion(q);
405
407
  result.filesImported++;
@@ -798,6 +800,7 @@ export async function exportWorkspace(
798
800
  codeContextFiles: q.codeContextFiles,
799
801
  codeAnnotations: q.codeAnnotations ?? {},
800
802
  codeSnippets: contextFilesWithContent,
803
+ createdAt: q.createdAt,
801
804
  },
802
805
  null,
803
806
  2,
@@ -743,11 +743,12 @@ app.post("/api/questions/:questionId/save-code-snippet", async (req, res) => {
743
743
  origin !== "infra" &&
744
744
  origin !== "react" &&
745
745
  origin !== "nextjs" &&
746
- origin !== "module-federation"
746
+ origin !== "module-federation" &&
747
+ origin !== "canvas"
747
748
  ) {
748
749
  return res.status(400).json({
749
750
  error:
750
- "origin must be 'user', 'ai', 'sandbox', 'browser-security', 'infra', 'react', 'nextjs', or 'module-federation'",
751
+ "origin must be 'user', 'ai', 'sandbox', 'browser-security', 'infra', 'react', 'nextjs', 'module-federation', or 'canvas'",
751
752
  });
752
753
  }
753
754
  try {
@@ -2972,7 +2973,7 @@ app.post("/api/module-federation/start", async (req, res) => {
2972
2973
 
2973
2974
  await runLoggedCommand(
2974
2975
  npmCommand(),
2975
- ["install", "--no-audit", "--no-fund", "--prefer-offline"],
2976
+ ["install", "--no-audit", "--no-fund", "--legacy-peer-deps"],
2976
2977
  {
2977
2978
  cwd: dir,
2978
2979
  env: {
@@ -2983,12 +2984,36 @@ app.post("/api/module-federation/start", async (req, res) => {
2983
2984
  logs,
2984
2985
  );
2985
2986
 
2987
+ // Detect new modern Next.js MFE template types.
2988
+ const isMultiZones = typeof files["apps/zone-b/package.json"] === "string";
2989
+ const isMfRuntimeApi =
2990
+ typeof files["apps/mf-remote/package.json"] === "string";
2991
+ const isRspackShell =
2992
+ typeof files["apps/rspack-shell/package.json"] === "string";
2993
+
2986
2994
  // Detect isolated 2-app pattern (host + mfe-auth, no profile/checkout).
2987
2995
  const isIsolated =
2988
2996
  typeof files["apps/mfe-auth/package.json"] === "string" &&
2989
2997
  typeof files["apps/checkout/package.json"] !== "string";
2990
2998
 
2991
- const ports = await getDistinctPorts(isIsolated ? 2 : 3);
2999
+ // Detect Next.js MF pattern (shell + remote, no mfe-auth/checkout/profile).
3000
+ // Excludes Multi-Zones (zone-b) and MF Runtime API (mf-remote) which also use apps/shell.
3001
+ const isNextjsMf =
3002
+ typeof files["apps/shell/package.json"] === "string" &&
3003
+ !isMultiZones &&
3004
+ !isMfRuntimeApi &&
3005
+ typeof files["apps/mfe-auth/package.json"] !== "string" &&
3006
+ typeof files["apps/checkout/package.json"] !== "string";
3007
+
3008
+ const ports = await getDistinctPorts(
3009
+ isIsolated ||
3010
+ isNextjsMf ||
3011
+ isMultiZones ||
3012
+ isMfRuntimeApi ||
3013
+ isRspackShell
3014
+ ? 2
3015
+ : 3,
3016
+ );
2992
3017
  const [hostPort] = ports;
2993
3018
 
2994
3019
  const appUrls: Record<string, string> = {
@@ -3001,7 +3026,32 @@ app.post("/api/module-federation/start", async (req, res) => {
3001
3026
  npm_config_update_notifier: "false",
3002
3027
  };
3003
3028
 
3004
- if (isIsolated) {
3029
+ if (isNextjsMf) {
3030
+ const [, remotePort] = ports;
3031
+ appUrls.remote = `http://localhost:${remotePort}`;
3032
+ spawnEnv.REMOTE_PORT = String(remotePort);
3033
+ // Next.js remotes put the entry at /_next/static/chunks/; webpack remotes put it at root.
3034
+ const isNextjsRemote =
3035
+ typeof files["apps/remote/next.config.js"] === "string";
3036
+ const remoteEntryPath = isNextjsRemote
3037
+ ? "/_next/static/chunks/remoteEntry.js"
3038
+ : "/remoteEntry.js";
3039
+ spawnEnv.NEXT_PUBLIC_REMOTE_URL = `http://localhost:${remotePort}${remoteEntryPath}`;
3040
+ } else if (isMultiZones) {
3041
+ const [, remotePort] = ports;
3042
+ // Zone B serves everything under /store (basePath), so point the preview there.
3043
+ appUrls.zoneB = `http://localhost:${remotePort}/store`;
3044
+ spawnEnv.REMOTE_PORT = String(remotePort);
3045
+ } else if (isMfRuntimeApi) {
3046
+ const [, remotePort] = ports;
3047
+ appUrls.mfRemote = `http://localhost:${remotePort}`;
3048
+ spawnEnv.REMOTE_PORT = String(remotePort);
3049
+ spawnEnv.NEXT_PUBLIC_REMOTE_URL = `http://localhost:${remotePort}/remoteEntry.js`;
3050
+ } else if (isRspackShell) {
3051
+ const [, remotePort] = ports;
3052
+ appUrls.remote = `http://localhost:${remotePort}`;
3053
+ spawnEnv.REMOTE_PORT = String(remotePort);
3054
+ } else if (isIsolated) {
3005
3055
  const [, mfeAuthPort] = ports;
3006
3056
  appUrls.mfeAuth = `http://localhost:${mfeAuthPort}`;
3007
3057
  spawnEnv.MFE_AUTH_PORT = String(mfeAuthPort);
@@ -3014,7 +3064,14 @@ app.post("/api/module-federation/start", async (req, res) => {
3014
3064
  }
3015
3065
 
3016
3066
  const readyPorts = new Set<string>();
3017
- const requiredPorts = isIsolated ? 2 : 3;
3067
+ const requiredPorts =
3068
+ isIsolated ||
3069
+ isNextjsMf ||
3070
+ isMultiZones ||
3071
+ isMfRuntimeApi ||
3072
+ isRspackShell
3073
+ ? 2
3074
+ : 3;
3018
3075
 
3019
3076
  const child = spawn(npmCommand(), ["run", "dev"], {
3020
3077
  cwd: dir,
@@ -154,6 +154,7 @@ export interface WorkspaceMeta {
154
154
  id: string;
155
155
  name: string;
156
156
  type: "local" | "google_drive";
157
+ questionSortOrder?: "name" | "createdAt";
157
158
  driveConfig?: DriveConfig;
158
159
  createdAt: string;
159
160
  }