create-interview-cockpit 0.19.0 → 0.21.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.
@@ -19,7 +19,12 @@ import {
19
19
  X,
20
20
  } from "lucide-react";
21
21
  import MonacoEditorLib from "@monaco-editor/react";
22
- import type { BeforeMount, Monaco, OnChange } from "@monaco-editor/react";
22
+ import type {
23
+ BeforeMount,
24
+ Monaco,
25
+ OnChange,
26
+ OnMount,
27
+ } from "@monaco-editor/react";
23
28
  import { useStore } from "../store";
24
29
  import {
25
30
  cloneGhaLabWorkspace,
@@ -42,8 +47,11 @@ const DEFAULT_W = Math.min(1280, window.innerWidth - 48);
42
47
  const DEFAULT_H = Math.min(820, window.innerHeight - 48);
43
48
  const EDITOR_FONT =
44
49
  'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace';
50
+ const GHA_LAB_MODEL_ROOT = "file:///gha-lab/";
45
51
  type ResizeDir = "e" | "s" | "se" | "sw" | "w" | "ne" | "nw" | "n";
46
52
 
53
+ let ghaLabMonacoTypeLibsInjected = false;
54
+
47
55
  const EVENTS = [
48
56
  "push",
49
57
  "pull_request",
@@ -61,6 +69,8 @@ function baseName(filePath: string): string {
61
69
  function getEditorLanguage(filePath: string): string {
62
70
  const lower = filePath.toLowerCase();
63
71
  if (lower.endsWith(".yml") || lower.endsWith(".yaml")) return "yaml";
72
+ if (lower.endsWith(".html")) return "html";
73
+ if (lower.endsWith(".css")) return "css";
64
74
  if (lower.endsWith(".json")) return "json";
65
75
  if (lower.endsWith(".md") || lower.endsWith(".markdown")) return "markdown";
66
76
  if (lower.endsWith(".js") || lower.endsWith(".mjs")) return "javascript";
@@ -70,6 +80,13 @@ function getEditorLanguage(filePath: string): string {
70
80
  return "plaintext";
71
81
  }
72
82
 
83
+ function getGhaLabModelPath(filePath: string): string {
84
+ return `${GHA_LAB_MODEL_ROOT}${filePath
85
+ .split("/")
86
+ .map(encodeURIComponent)
87
+ .join("/")}`;
88
+ }
89
+
73
90
  // Tiny grouped-by-folder list to keep the modal lean.
74
91
  function groupByFolder(paths: string[]): { folder: string; files: string[] }[] {
75
92
  const map = new Map<string, string[]>();
@@ -155,6 +172,8 @@ export default function GithubActionsLabModal() {
155
172
  const [rightCollapsed, setRightCollapsed] = useState(false);
156
173
  const abortRef = useRef<AbortController | null>(null);
157
174
  const consoleEndRef = useRef<HTMLDivElement | null>(null);
175
+ const monacoRef = useRef<Monaco | null>(null);
176
+ const monacoModelUrisRef = useRef<Set<string>>(new Set());
158
177
 
159
178
  useEffect(() => {
160
179
  // Keep workflow selection valid when files change
@@ -521,6 +540,50 @@ export default function GithubActionsLabModal() {
521
540
 
522
541
  const clearConsole = () => setConsoleLines([]);
523
542
 
543
+ const syncMonacoWorkspaceModels = useCallback(
544
+ (monaco: Monaco, files: Record<string, string>) => {
545
+ const nextUris = new Set<string>();
546
+
547
+ for (const [filePath, content] of Object.entries(files)) {
548
+ const uri = monaco.Uri.parse(getGhaLabModelPath(filePath));
549
+ const uriText = uri.toString();
550
+ nextUris.add(uriText);
551
+
552
+ const existing = monaco.editor.getModel(uri);
553
+ if (existing) {
554
+ if (existing.getValue() !== content) existing.setValue(content);
555
+ } else {
556
+ monaco.editor.createModel(content, getEditorLanguage(filePath), uri);
557
+ }
558
+ }
559
+
560
+ for (const uriText of monacoModelUrisRef.current) {
561
+ if (nextUris.has(uriText)) continue;
562
+ monaco.editor.getModel(monaco.Uri.parse(uriText))?.dispose();
563
+ }
564
+
565
+ monacoModelUrisRef.current = nextUris;
566
+ },
567
+ [],
568
+ );
569
+
570
+ useEffect(() => {
571
+ const monaco = monacoRef.current;
572
+ if (!monaco) return;
573
+ syncMonacoWorkspaceModels(monaco, workspace.files);
574
+ }, [syncMonacoWorkspaceModels, workspace.files]);
575
+
576
+ useEffect(() => {
577
+ return () => {
578
+ const monaco = monacoRef.current;
579
+ if (!monaco) return;
580
+ for (const uriText of monacoModelUrisRef.current) {
581
+ monaco.editor.getModel(monaco.Uri.parse(uriText))?.dispose();
582
+ }
583
+ monacoModelUrisRef.current.clear();
584
+ };
585
+ }, []);
586
+
524
587
  // ── Console input ─────────────────────────────────────────────────
525
588
  const [consoleInput, setConsoleInput] = useState("");
526
589
  const handleConsoleSubmit = (e: React.FormEvent) => {
@@ -533,6 +596,139 @@ export default function GithubActionsLabModal() {
533
596
 
534
597
  // ── Monaco config ─────────────────────────────────────────────────
535
598
  const handleBeforeMount = useCallback<BeforeMount>((monaco: Monaco) => {
599
+ monacoRef.current = monaco;
600
+
601
+ // Monaco does not read the template's tsconfig.json or package.json, so
602
+ // configure its in-browser TypeScript worker like a Vite React TS project.
603
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
604
+ const tsLang = monaco.languages.typescript as any;
605
+ const compilerOptions = {
606
+ target: tsLang.ScriptTarget.ESNext,
607
+ module: tsLang.ModuleKind.ESNext,
608
+ moduleResolution:
609
+ tsLang.ModuleResolutionKind.Bundler ??
610
+ tsLang.ModuleResolutionKind.NodeJs,
611
+ jsx: tsLang.JsxEmit.ReactJSX ?? tsLang.JsxEmit.Preserve,
612
+ jsxImportSource: "react",
613
+ allowJs: true,
614
+ strict: true,
615
+ esModuleInterop: true,
616
+ allowSyntheticDefaultImports: true,
617
+ allowNonTsExtensions: true,
618
+ resolveJsonModule: true,
619
+ };
620
+ tsLang.typescriptDefaults.setCompilerOptions(compilerOptions);
621
+ tsLang.javascriptDefaults.setCompilerOptions(compilerOptions);
622
+ tsLang.typescriptDefaults.setDiagnosticsOptions({
623
+ noSemanticValidation: false,
624
+ noSyntaxValidation: false,
625
+ diagnosticCodesToIgnore: [2688],
626
+ });
627
+ tsLang.javascriptDefaults.setDiagnosticsOptions({
628
+ noSemanticValidation: false,
629
+ noSyntaxValidation: false,
630
+ diagnosticCodesToIgnore: [2688],
631
+ });
632
+ tsLang.typescriptDefaults.setEagerModelSync(true);
633
+ tsLang.javascriptDefaults.setEagerModelSync(true);
634
+
635
+ if (!ghaLabMonacoTypeLibsInjected) {
636
+ const reactViteTypes = `
637
+ declare module "react" {
638
+ export type ReactNode = any;
639
+ export type ReactElement = any;
640
+ export type CSSProperties = Record<string, string | number>;
641
+ export interface FC<P = {}> {
642
+ (props: P): ReactElement | null;
643
+ }
644
+ export const StrictMode: FC<{ children?: ReactNode }>;
645
+ const React: {
646
+ createElement: (...args: any[]) => ReactElement;
647
+ Fragment: any;
648
+ StrictMode: FC<{ children?: ReactNode }>;
649
+ };
650
+ export default React;
651
+ }
652
+
653
+ declare module "react/jsx-runtime" {
654
+ export namespace JSX {
655
+ interface Element {}
656
+ interface IntrinsicElements {
657
+ [elemName: string]: any;
658
+ }
659
+ }
660
+ export const Fragment: any;
661
+ export function jsx(type: any, props: any, key?: any): any;
662
+ export function jsxs(type: any, props: any, key?: any): any;
663
+ export function jsxDEV(
664
+ type: any,
665
+ props: any,
666
+ key: any,
667
+ isStaticChildren: boolean,
668
+ source: any,
669
+ self: any,
670
+ ): any;
671
+ }
672
+
673
+ declare namespace JSX {
674
+ interface Element {}
675
+ interface IntrinsicElements {
676
+ [elemName: string]: any;
677
+ }
678
+ }
679
+
680
+ declare module "react-dom/client" {
681
+ export function createRoot(container: Element | DocumentFragment): {
682
+ render(children: any): void;
683
+ unmount(): void;
684
+ };
685
+ }
686
+
687
+ declare module "vite" {
688
+ export function defineConfig(config: any): any;
689
+ }
690
+
691
+ declare module "@vitejs/plugin-react" {
692
+ export default function react(options?: any): any;
693
+ }
694
+
695
+ declare module "*.css" {
696
+ const classes: Record<string, string>;
697
+ export default classes;
698
+ }
699
+
700
+ interface ImportMetaEnv {
701
+ readonly BASE_URL: string;
702
+ readonly DEV: boolean;
703
+ readonly MODE: string;
704
+ readonly PROD: boolean;
705
+ readonly SSR: boolean;
706
+ readonly [key: string]: any;
707
+ }
708
+
709
+ interface ImportMeta {
710
+ readonly env: ImportMetaEnv;
711
+ }
712
+ `;
713
+ tsLang.typescriptDefaults.addExtraLib(
714
+ reactViteTypes,
715
+ "file:///__gha-lab-types/react-vite.d.ts",
716
+ );
717
+ tsLang.javascriptDefaults.addExtraLib(
718
+ reactViteTypes,
719
+ "file:///__gha-lab-types/react-vite.d.ts",
720
+ );
721
+ tsLang.typescriptDefaults.addExtraLib(
722
+ reactViteTypes,
723
+ "file:///node_modules/vite/client.d.ts",
724
+ );
725
+ tsLang.javascriptDefaults.addExtraLib(
726
+ reactViteTypes,
727
+ "file:///node_modules/vite/client.d.ts",
728
+ );
729
+ ghaLabMonacoTypeLibsInjected = true;
730
+ }
731
+
536
732
  monaco.editor.defineTheme("gha-lab-dark", {
537
733
  base: "vs-dark",
538
734
  inherit: true,
@@ -554,6 +750,14 @@ export default function GithubActionsLabModal() {
554
750
  });
555
751
  }, []);
556
752
 
753
+ const handleMount = useCallback<OnMount>(
754
+ (_editor, monaco) => {
755
+ monacoRef.current = monaco;
756
+ syncMonacoWorkspaceModels(monaco, workspace.files);
757
+ },
758
+ [syncMonacoWorkspaceModels, workspace.files],
759
+ );
760
+
557
761
  const handleEditorChange = useCallback<OnChange>(
558
762
  (next) => {
559
763
  updateFile(activeFile, next ?? "");
@@ -873,9 +1077,10 @@ export default function GithubActionsLabModal() {
873
1077
  width="100%"
874
1078
  language={getEditorLanguage(activeFile)}
875
1079
  theme="gha-lab-dark"
876
- path={`file:///gha-lab/${activeFile}`}
1080
+ path={getGhaLabModelPath(activeFile)}
877
1081
  value={workspace.files[activeFile]}
878
1082
  beforeMount={handleBeforeMount}
1083
+ onMount={handleMount}
879
1084
  onChange={handleEditorChange}
880
1085
  options={{
881
1086
  fontFamily: EDITOR_FONT,
@@ -1,7 +1,11 @@
1
1
  import { useState } from "react";
2
2
  import { useStore } from "../store";
3
3
  import { DOCKER_DEEP_DIVE_LAB, parseInfraLabWorkspace } from "../infraLab";
4
- import { DEFAULT_GHA_LAB, parseGhaLabWorkspace } from "../githubActionsLab";
4
+ import {
5
+ DEFAULT_GHA_LAB,
6
+ parseGhaLabWorkspace,
7
+ REACT_VITE_TYPESCRIPT_GHA_LAB,
8
+ } from "../githubActionsLab";
5
9
  import { ENTERPRISE_LOCAL_AUTH_LAB } from "../enterpriseLocalLab";
6
10
  import {
7
11
  parseFrontendLabWorkspace,
@@ -656,6 +660,12 @@ export default function LabsPanel() {
656
660
  origin="github-actions"
657
661
  emptyText="Save a GitHub Actions lab to reopen it here"
658
662
  newLabMenu={[
663
+ {
664
+ label: "React Vite TypeScript Starter",
665
+ description:
666
+ "Scaffolded React app with an empty .github/workflows/ci.yml",
667
+ onClick: () => openGhaLab(REACT_VITE_TYPESCRIPT_GHA_LAB),
668
+ },
659
669
  {
660
670
  label: "Workflows + Composite Action",
661
671
  description:
@@ -164,6 +164,223 @@ require("fs").readdirSync(".").forEach((f) => console.log(" -", f));
164
164
  `,
165
165
  };
166
166
 
167
+ const REACT_VITE_TYPESCRIPT_FILES: Record<string, string> = {
168
+ "README.md": `# React Vite TypeScript GitHub Actions Lab
169
+
170
+ This template starts with a small React + Vite + TypeScript application and an intentionally empty workflow file.
171
+
172
+ ## App files
173
+
174
+ - \`package.json\` defines the Vite scripts and React dependencies.
175
+ - \`index.html\` mounts the app.
176
+ - \`src/main.tsx\` boots React.
177
+ - \`src/App.tsx\` contains the starter UI.
178
+ - \`.github/workflows/ci.yml\` is blank on purpose so you can write the CI workflow from scratch.
179
+
180
+ ## Local app commands
181
+
182
+ npm install
183
+ npm run dev
184
+ npm run build
185
+
186
+ ## GitHub Actions task
187
+
188
+ Open \`.github/workflows/ci.yml\`, add your workflow, then run it with the lab controls.
189
+ `,
190
+
191
+ ".github/workflows/ci.yml": "",
192
+
193
+ ".gitignore": `node_modules
194
+ dist
195
+ .DS_Store
196
+ *.local
197
+ `,
198
+
199
+ "package.json": `{
200
+ "name": "gha-react-vite-typescript-app",
201
+ "private": true,
202
+ "version": "0.0.0",
203
+ "type": "module",
204
+ "scripts": {
205
+ "dev": "vite --host 0.0.0.0",
206
+ "build": "tsc --noEmit && vite build",
207
+ "preview": "vite preview --host 0.0.0.0"
208
+ },
209
+ "dependencies": {
210
+ "react": "^19.0.0",
211
+ "react-dom": "^19.0.0"
212
+ },
213
+ "devDependencies": {
214
+ "@types/react": "^19.0.0",
215
+ "@types/react-dom": "^19.0.0",
216
+ "@vitejs/plugin-react": "^4.3.0",
217
+ "typescript": "^5.9.3",
218
+ "vite": "^6.0.0"
219
+ }
220
+ }
221
+ `,
222
+
223
+ "index.html": `<!doctype html>
224
+ <html lang="en">
225
+ <head>
226
+ <meta charset="UTF-8" />
227
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
228
+ <title>React Vite TypeScript App</title>
229
+ </head>
230
+ <body>
231
+ <div id="root"></div>
232
+ <script type="module" src="/src/main.tsx"></script>
233
+ </body>
234
+ </html>
235
+ `,
236
+
237
+ "tsconfig.json": `{
238
+ "compilerOptions": {
239
+ "target": "ES2020",
240
+ "useDefineForClassFields": true,
241
+ "lib": ["DOM", "DOM.Iterable", "ES2020"],
242
+ "allowJs": false,
243
+ "skipLibCheck": true,
244
+ "esModuleInterop": true,
245
+ "allowSyntheticDefaultImports": true,
246
+ "strict": true,
247
+ "forceConsistentCasingInFileNames": true,
248
+ "module": "ESNext",
249
+ "moduleResolution": "Node",
250
+ "resolveJsonModule": true,
251
+ "isolatedModules": true,
252
+ "noEmit": true,
253
+ "jsx": "react-jsx"
254
+ },
255
+ "include": ["src"]
256
+ }
257
+ `,
258
+
259
+ "vite.config.ts": `import { defineConfig } from "vite";
260
+ import react from "@vitejs/plugin-react";
261
+
262
+ export default defineConfig({
263
+ plugins: [react()],
264
+ });
265
+ `,
266
+
267
+ "src/main.tsx": `import { StrictMode } from "react";
268
+ import { createRoot } from "react-dom/client";
269
+ import App from "./App";
270
+ import "./index.css";
271
+
272
+ createRoot(document.getElementById("root")!).render(
273
+ <StrictMode>
274
+ <App />
275
+ </StrictMode>,
276
+ );
277
+ `,
278
+
279
+ "src/App.tsx": `const checks = [
280
+ "Edit .github/workflows/ci.yml from a clean slate",
281
+ "Install dependencies with npm install",
282
+ "Run npm run build in your workflow",
283
+ ];
284
+
285
+ export default function App() {
286
+ return (
287
+ <main className="app-shell">
288
+ <section className="hero-card">
289
+ <p className="eyebrow">GitHub Actions Practice</p>
290
+ <h1>React + Vite + TypeScript</h1>
291
+ <p className="lead">
292
+ The application is ready. Your CI workflow starts empty so you can
293
+ build it one line at a time.
294
+ </p>
295
+ <ul>
296
+ {checks.map((check) => (
297
+ <li key={check}>{check}</li>
298
+ ))}
299
+ </ul>
300
+ </section>
301
+ </main>
302
+ );
303
+ }
304
+ `,
305
+
306
+ "src/index.css": `:root {
307
+ color: #e5eefb;
308
+ background: #0f172a;
309
+ font-family:
310
+ Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
311
+ sans-serif;
312
+ }
313
+
314
+ * {
315
+ box-sizing: border-box;
316
+ }
317
+
318
+ body {
319
+ margin: 0;
320
+ min-width: 320px;
321
+ min-height: 100vh;
322
+ }
323
+
324
+ .app-shell {
325
+ display: grid;
326
+ min-height: 100vh;
327
+ place-items: center;
328
+ padding: 2rem;
329
+ background:
330
+ radial-gradient(circle at top left, rgba(59, 130, 246, 0.35), transparent 32rem),
331
+ linear-gradient(135deg, #0f172a 0%, #111827 100%);
332
+ }
333
+
334
+ .hero-card {
335
+ width: min(100%, 720px);
336
+ padding: 2rem;
337
+ border: 1px solid rgba(148, 163, 184, 0.2);
338
+ border-radius: 24px;
339
+ background: rgba(15, 23, 42, 0.78);
340
+ box-shadow: 0 24px 80px rgba(2, 6, 23, 0.45);
341
+ }
342
+
343
+ .eyebrow {
344
+ margin: 0 0 0.75rem;
345
+ color: #fbbf24;
346
+ font-size: 0.78rem;
347
+ font-weight: 700;
348
+ letter-spacing: 0.16em;
349
+ text-transform: uppercase;
350
+ }
351
+
352
+ h1 {
353
+ margin: 0;
354
+ font-size: clamp(2.5rem, 8vw, 5rem);
355
+ line-height: 0.95;
356
+ }
357
+
358
+ .lead {
359
+ color: #cbd5e1;
360
+ font-size: 1.1rem;
361
+ line-height: 1.7;
362
+ }
363
+
364
+ ul {
365
+ display: grid;
366
+ gap: 0.75rem;
367
+ margin: 1.5rem 0 0;
368
+ padding: 0;
369
+ list-style: none;
370
+ }
371
+
372
+ li {
373
+ padding: 0.85rem 1rem;
374
+ border: 1px solid rgba(148, 163, 184, 0.16);
375
+ border-radius: 14px;
376
+ background: rgba(30, 41, 59, 0.72);
377
+ }
378
+ `,
379
+
380
+ "src/vite-env.d.ts": `/// <reference types="vite/client" />
381
+ `,
382
+ };
383
+
167
384
  export const DEFAULT_GHA_LAB: GithubActionsLabWorkspace = {
168
385
  version: 1,
169
386
  label: "GitHub Actions Playground",
@@ -173,6 +390,15 @@ export const DEFAULT_GHA_LAB: GithubActionsLabWorkspace = {
173
390
  files: DEFAULT_FILES,
174
391
  };
175
392
 
393
+ export const REACT_VITE_TYPESCRIPT_GHA_LAB: GithubActionsLabWorkspace = {
394
+ version: 1,
395
+ label: "React Vite TypeScript Starter",
396
+ activeFile: ".github/workflows/ci.yml",
397
+ defaultEvent: "push",
398
+ defaultWorkflow: ".github/workflows/ci.yml",
399
+ files: REACT_VITE_TYPESCRIPT_FILES,
400
+ };
401
+
176
402
  // ─── Helpers (mirror infraLab.ts API surface) ────────────────────────────
177
403
 
178
404
  export function cloneGhaLabWorkspace(
@@ -183,7 +409,10 @@ export function cloneGhaLabWorkspace(
183
409
  source.files && Object.keys(source.files).length > 0
184
410
  ? { ...source.files }
185
411
  : { ...DEFAULT_FILES };
186
- const activeFile = sourceFiles[source.activeFile]
412
+ const activeFile = Object.prototype.hasOwnProperty.call(
413
+ sourceFiles,
414
+ source.activeFile,
415
+ )
187
416
  ? source.activeFile
188
417
  : (Object.keys(sourceFiles)[0] ?? ".github/workflows/ci.yml");
189
418
 
@@ -193,7 +422,8 @@ export function cloneGhaLabWorkspace(
193
422
  activeFile,
194
423
  defaultEvent: source.defaultEvent || "push",
195
424
  defaultWorkflow:
196
- source.defaultWorkflow && sourceFiles[source.defaultWorkflow]
425
+ source.defaultWorkflow &&
426
+ Object.prototype.hasOwnProperty.call(sourceFiles, source.defaultWorkflow)
197
427
  ? source.defaultWorkflow
198
428
  : Object.keys(sourceFiles).find((f) =>
199
429
  f.startsWith(".github/workflows/"),
@@ -200,6 +200,10 @@ interface Store {
200
200
  toggleSidebar: () => void;
201
201
  fetchAvailableFiles: () => Promise<void>;
202
202
  updateCodeContext: (questionId: string, files: string[]) => Promise<void>;
203
+ updateGitDiffContext: (
204
+ questionId: string,
205
+ ctx: import("./types").GitDiffContext | null,
206
+ ) => Promise<void>;
203
207
  refreshCurrentQuestion: () => Promise<void>;
204
208
  uploadTopicFiles: (
205
209
  topicId: string,
@@ -830,6 +834,16 @@ export const useStore = create<Store>((set, get) => ({
830
834
  }));
831
835
  },
832
836
 
837
+ updateGitDiffContext: async (questionId, ctx) => {
838
+ // Send null explicitly so the server can clear the field; PATCH ignores undefined.
839
+ await api.updateQuestion(questionId, { gitDiffContext: ctx } as any);
840
+ set((s) => ({
841
+ currentQuestion: s.currentQuestion
842
+ ? { ...s.currentQuestion, gitDiffContext: ctx ?? undefined }
843
+ : null,
844
+ }));
845
+ },
846
+
833
847
  refreshCurrentQuestion: async () => {
834
848
  const { selectedQuestionId } = get();
835
849
  if (selectedQuestionId) {
@@ -151,6 +151,8 @@ export interface Question {
151
151
  title: string;
152
152
  systemContext: string;
153
153
  codeContextFiles: string[];
154
+ /** Optional git diff selection that the chat LLM can lazy-fetch via readFile. */
155
+ gitDiffContext?: GitDiffContext;
154
156
  contextFiles: ContextFile[];
155
157
  messages: Message[];
156
158
  annotations?: Annotation[];
@@ -161,3 +163,24 @@ export interface Question {
161
163
  linkedConversationIds?: string[];
162
164
  createdAt: string;
163
165
  }
166
+
167
+ export type GitDiffMode = "two-dot" | "three-dot" | "working-tree";
168
+
169
+ export interface GitDiffContext {
170
+ baseRef: string;
171
+ /** Branch / ref / tag. Empty when mode is working-tree. */
172
+ headRef: string;
173
+ mode: GitDiffMode;
174
+ selectedFiles: string[];
175
+ }
176
+
177
+ export type GitDiffStatus = "A" | "M" | "D" | "R" | "C" | "T" | "U" | "?";
178
+
179
+ export interface GitDiffFileEntry {
180
+ path: string;
181
+ oldPath?: string;
182
+ status: GitDiffStatus;
183
+ additions: number;
184
+ deletions: number;
185
+ binary: boolean;
186
+ }
@@ -1 +1 @@
1
- {"root":["./src/app.tsx","./src/api.ts","./src/browsersecuritytemplates.ts","./src/enterpriselocallab.ts","./src/githubactionslab.ts","./src/infralab.ts","./src/main.tsx","./src/reactlab.ts","./src/store.ts","./src/types.ts","./src/vite-env.d.ts","./src/components/aisettingsmodal.tsx","./src/components/annotationdialog.tsx","./src/components/browsersecuritylabmodal.tsx","./src/components/canvaslabmodal.tsx","./src/components/chatmessage.tsx","./src/components/chatview.tsx","./src/components/codecontextpanel.tsx","./src/components/codelineannotationpopup.tsx","./src/components/coderunnermodal.tsx","./src/components/deploymentlabmodal.tsx","./src/components/docrefmodal.tsx","./src/components/fileattachments.tsx","./src/components/filepickermodal.tsx","./src/components/fileviewermodal.tsx","./src/components/ghahistorypanel.tsx","./src/components/ghajobspanel.tsx","./src/components/githubactionslabmodal.tsx","./src/components/infralabmodal.tsx","./src/components/labspanel.tsx","./src/components/linkedconvospicker.tsx","./src/components/markdownrenderer.tsx","./src/components/mermaiddiagram.tsx","./src/components/notesmodal.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
+ {"root":["./src/app.tsx","./src/api.ts","./src/browsersecuritytemplates.ts","./src/enterpriselocallab.ts","./src/githubactionslab.ts","./src/infralab.ts","./src/main.tsx","./src/reactlab.ts","./src/store.ts","./src/types.ts","./src/vite-env.d.ts","./src/components/aisettingsmodal.tsx","./src/components/annotationdialog.tsx","./src/components/browsersecuritylabmodal.tsx","./src/components/canvaslabmodal.tsx","./src/components/chatmessage.tsx","./src/components/chatview.tsx","./src/components/codecontextpanel.tsx","./src/components/codelineannotationpopup.tsx","./src/components/coderunnermodal.tsx","./src/components/deploymentlabmodal.tsx","./src/components/docrefmodal.tsx","./src/components/fileattachments.tsx","./src/components/filepickermodal.tsx","./src/components/fileviewermodal.tsx","./src/components/ghahistorypanel.tsx","./src/components/ghajobspanel.tsx","./src/components/gitdiffpanel.tsx","./src/components/gitdiffviewermodal.tsx","./src/components/githubactionslabmodal.tsx","./src/components/infralabmodal.tsx","./src/components/labspanel.tsx","./src/components/linkedconvospicker.tsx","./src/components/markdownrenderer.tsx","./src/components/mermaiddiagram.tsx","./src/components/notesmodal.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,3 +1,3 @@
1
1
  {
2
- "version": "0.17.3"
2
+ "version": "0.19.0"
3
3
  }
@@ -180,7 +180,8 @@ function parseWorkspace(input: unknown): GithubActionsLabWorkspace {
180
180
  ? candidate.label.trim()
181
181
  : "GitHub Actions Lab",
182
182
  activeFile:
183
- typeof candidate.activeFile === "string" && files[candidate.activeFile]
183
+ typeof candidate.activeFile === "string" &&
184
+ Object.prototype.hasOwnProperty.call(files, candidate.activeFile)
184
185
  ? candidate.activeFile
185
186
  : fileNames[0],
186
187
  defaultEvent: