neural-loom 0.2.56 → 0.2.58

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.
Files changed (60) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +5 -5
  3. package/.next/cache/.previewinfo +1 -1
  4. package/.next/cache/.rscinfo +1 -1
  5. package/.next/cache/.tsbuildinfo +1 -1
  6. package/.next/diagnostics/route-bundle-stats.json +5 -5
  7. package/.next/fallback-build-manifest.json +3 -3
  8. package/.next/prerender-manifest.json +3 -3
  9. package/.next/server/app/_global-error/page/build-manifest.json +2 -2
  10. package/.next/server/app/_global-error.html +1 -1
  11. package/.next/server/app/_global-error.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  15. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  16. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  17. package/.next/server/app/_not-found/page/build-manifest.json +2 -2
  18. package/.next/server/app/_not-found.html +1 -1
  19. package/.next/server/app/_not-found.rsc +1 -1
  20. package/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  21. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  22. package/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  23. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  24. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  25. package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  26. package/.next/server/app/index.html +1 -1
  27. package/.next/server/app/index.rsc +2 -2
  28. package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  29. package/.next/server/app/index.segments/_full.segment.rsc +2 -2
  30. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  31. package/.next/server/app/index.segments/_index.segment.rsc +1 -1
  32. package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  33. package/.next/server/app/page/build-manifest.json +2 -2
  34. package/.next/server/app/page/react-loadable-manifest.json +2 -2
  35. package/.next/server/app/page_client-reference-manifest.js +1 -1
  36. package/.next/server/chunks/[root-of-the-server]__0_o7_jj._.js +1 -1
  37. package/.next/server/chunks/[root-of-the-server]__0_o7_jj._.js.map +1 -1
  38. package/.next/server/middleware-build-manifest.js +5 -5
  39. package/.next/server/pages/404.html +1 -1
  40. package/.next/server/pages/500.html +1 -1
  41. package/.next/server/server-reference-manifest.js +1 -1
  42. package/.next/server/server-reference-manifest.json +1 -1
  43. package/.next/static/chunks/08o_~ynkkb9ph.js +31 -0
  44. package/.next/static/chunks/{0eidfx-s54367.js → 0gy2rolwxzjl8.js} +1 -1
  45. package/.next/static/chunks/0obzqbxd9niy0.js +1 -0
  46. package/.next/static/chunks/12m0vh2h637z4.js +1 -0
  47. package/.next/static/chunks/15b-kgkhsdb0s.js +11 -0
  48. package/.next/static/chunks/{turbopack-0.g.4cakdth0h.js → turbopack-06zh3hgrjcphg.js} +1 -1
  49. package/.next/trace +1 -1
  50. package/.next/trace-build +1 -1
  51. package/package.json +2 -1
  52. package/public/manifest.json +6 -2
  53. package/src/app/api/git/route.ts +79 -0
  54. package/src/app/components/IdeLayout.tsx +426 -12
  55. package/.next/static/chunks/0ljspdd3cv1ft.js +0 -31
  56. package/.next/static/chunks/0st7_hghhyuv4.js +0 -1
  57. package/.next/static/chunks/162be8draqhn..js +0 -11
  58. /package/.next/static/{E5W0YoY_yEbo_xJeWaMEB → HoQA8gsbI6xeWQpFAPJZ9}/_buildManifest.js +0 -0
  59. /package/.next/static/{E5W0YoY_yEbo_xJeWaMEB → HoQA8gsbI6xeWQpFAPJZ9}/_clientMiddlewareManifest.js +0 -0
  60. /package/.next/static/{E5W0YoY_yEbo_xJeWaMEB → HoQA8gsbI6xeWQpFAPJZ9}/_ssgManifest.js +0 -0
@@ -96,6 +96,57 @@ export async function GET(request: Request) {
96
96
  }
97
97
  }
98
98
 
99
+ if (action === "branches") {
100
+ const { stdout } = await execPromise("git branch --format=\"%(refname:short)|%(HEAD)\"", { cwd: targetRoot });
101
+ const branches = stdout.split(/\r?\n/).filter(Boolean).map(line => {
102
+ const [name, active] = line.split("|");
103
+ return {
104
+ name: name.trim(),
105
+ isActive: active === "*"
106
+ };
107
+ });
108
+ return NextResponse.json({ success: true, branches });
109
+ }
110
+
111
+ if (action === "log") {
112
+ try {
113
+ const { stdout } = await execPromise("git log --graph --abbrev-commit --decorate --format=format:\"%h||%p||%s||%an||%ar\" --all -n 100", { cwd: targetRoot });
114
+ const lines = stdout.split(/\r?\n/);
115
+ const logItems = lines.map(line => {
116
+ const parts = line.split("||");
117
+ if (parts.length >= 5) {
118
+ const firstPart = parts[0];
119
+ const hashMatch = firstPart.match(/([0-9a-f]{7,40})$/);
120
+ const hash = hashMatch ? hashMatch[1] : "";
121
+ const graph = firstPart.substring(0, firstPart.length - hash.length);
122
+
123
+ return {
124
+ isCommit: true,
125
+ graph,
126
+ hash,
127
+ parents: parts[1].trim().split(/\s+/).filter(Boolean),
128
+ subject: parts[2],
129
+ author: parts[3],
130
+ date: parts[4]
131
+ };
132
+ } else {
133
+ return {
134
+ isCommit: false,
135
+ graph: line,
136
+ hash: "",
137
+ parents: [],
138
+ subject: "",
139
+ author: "",
140
+ date: ""
141
+ };
142
+ }
143
+ });
144
+ return NextResponse.json({ success: true, log: logItems });
145
+ } catch (err) {
146
+ return NextResponse.json({ success: true, log: [] });
147
+ }
148
+ }
149
+
99
150
  return NextResponse.json({ error: "Invalid action" }, { status: 400 });
100
151
  } catch (error) {
101
152
  const errorMessage = error instanceof Error ? error.message : "Unknown error";
@@ -189,6 +240,34 @@ export async function POST(request: Request) {
189
240
  return NextResponse.json({ success: true, output: stdout + stderr });
190
241
  }
191
242
 
243
+ if (action === "checkout") {
244
+ const { branchName } = body;
245
+ if (!branchName) {
246
+ return NextResponse.json({ error: "Missing branchName parameter" }, { status: 400 });
247
+ }
248
+ const { stdout, stderr } = await execPromise(`git checkout ${JSON.stringify(branchName)}`, { cwd: targetRoot });
249
+ return NextResponse.json({ success: true, output: stdout + stderr });
250
+ }
251
+
252
+ if (action === "create_branch") {
253
+ const { branchName } = body;
254
+ if (!branchName) {
255
+ return NextResponse.json({ error: "Missing branchName parameter" }, { status: 400 });
256
+ }
257
+ const { stdout, stderr } = await execPromise(`git checkout -b ${JSON.stringify(branchName)}`, { cwd: targetRoot });
258
+ return NextResponse.json({ success: true, output: stdout + stderr });
259
+ }
260
+
261
+ if (action === "delete_branch") {
262
+ const { branchName, force } = body;
263
+ if (!branchName) {
264
+ return NextResponse.json({ error: "Missing branchName parameter" }, { status: 400 });
265
+ }
266
+ const flag = force ? "-D" : "-d";
267
+ const { stdout, stderr } = await execPromise(`git branch ${flag} ${JSON.stringify(branchName)}`, { cwd: targetRoot });
268
+ return NextResponse.json({ success: true, output: stdout + stderr });
269
+ }
270
+
192
271
  return NextResponse.json({ error: "Invalid action" }, { status: 400 });
193
272
  } catch (error) {
194
273
  const errorMessage = error instanceof Error ? error.message : "Unknown error";
@@ -1,11 +1,85 @@
1
1
  "use client";
2
2
 
3
3
  import { useEffect, useState, useCallback } from "react";
4
+ import dynamic from "next/dynamic";
4
5
  import TerminalConsole from "./TerminalConsole";
5
6
  import DirectoryBrowserModal from "./DirectoryBrowserModal";
6
7
  import MarkdownPreview from "./MarkdownPreview";
7
8
  import DbExplorer from "./DbExplorer";
8
9
 
10
+ const MonacoEditor = dynamic(() => import("@monaco-editor/react"), { ssr: false });
11
+
12
+ const getEditorLanguage = (filePath: string): string => {
13
+ if (!filePath) return "plaintext";
14
+ const ext = filePath.split(".").pop()?.toLowerCase();
15
+ switch (ext) {
16
+ case "ts":
17
+ case "tsx":
18
+ return "typescript";
19
+ case "js":
20
+ case "jsx":
21
+ return "javascript";
22
+ case "json":
23
+ return "json";
24
+ case "css":
25
+ return "css";
26
+ case "md":
27
+ return "markdown";
28
+ case "html":
29
+ return "html";
30
+ case "sql":
31
+ return "sql";
32
+ case "py":
33
+ return "python";
34
+ case "sh":
35
+ case "bash":
36
+ return "shell";
37
+ case "yaml":
38
+ case "yml":
39
+ return "yaml";
40
+ default:
41
+ return "plaintext";
42
+ }
43
+ };
44
+
45
+ const handleEditorWillMount = (monaco: any) => {
46
+ monaco.editor.defineTheme("neural-loom-dark", {
47
+ base: "vs-dark",
48
+ inherit: true,
49
+ rules: [
50
+ { token: "", foreground: "d4d4d4" },
51
+ { token: "comment", foreground: "6a9955" },
52
+ { token: "keyword", foreground: "c586c0" },
53
+ { token: "string", foreground: "ce9178" },
54
+ { token: "number", foreground: "b5cea8" },
55
+ { token: "regexp", foreground: "d16969" },
56
+ { token: "type", foreground: "4ec9b0" },
57
+ { token: "class", foreground: "4ec9b0" },
58
+ { token: "function", foreground: "dcdcaa" },
59
+ ],
60
+ colors: {
61
+ "editor.background": "#05070a",
62
+ "editor.foreground": "#d4d4d4",
63
+ "editorLineNumber.foreground": "#5a5a5a",
64
+ "editorLineNumber.activeForeground": "#c084fc",
65
+ "editor.lineHighlightBackground": "#0f131a",
66
+ "editor.selectionBackground": "#264f78",
67
+ "editorCursor.foreground": "#c084fc",
68
+ },
69
+ });
70
+ };
71
+
72
+ const editorOptions = {
73
+ minimap: { enabled: true },
74
+ fontSize: 13,
75
+ lineNumbers: "on" as const,
76
+ wordWrap: "on" as const,
77
+ automaticLayout: true,
78
+ scrollBeyondLastLine: false,
79
+ tabSize: 2,
80
+ };
81
+
82
+
9
83
  const getModeIcon = (type: string) => {
10
84
  if (type === 'mock') return '🧪';
11
85
  if (type.includes('-docker')) return '🐳';
@@ -41,6 +115,16 @@ interface GitFile {
41
115
  worktreeStatus?: string;
42
116
  }
43
117
 
118
+ interface GitLogItem {
119
+ isCommit: boolean;
120
+ graph: string;
121
+ hash: string;
122
+ parents: string[];
123
+ subject: string;
124
+ author: string;
125
+ date: string;
126
+ }
127
+
44
128
  interface SessionStatus {
45
129
  type?: string;
46
130
  status?: string;
@@ -236,6 +320,12 @@ export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClos
236
320
  const [gitBranch, setGitBranch] = useState("");
237
321
  const [gitSync, setGitSync] = useState({ ahead: 0, behind: 0 });
238
322
  const [commitMessage, setCommitMessage] = useState("");
323
+ const [gitBranches, setGitBranches] = useState<{ name: string; isActive: boolean }[]>([]);
324
+ const [gitLog, setGitLog] = useState<GitLogItem[]>([]);
325
+ const [isBranchesExpanded, setIsBranchesExpanded] = useState(false);
326
+ const [isHistoryExpanded, setIsHistoryExpanded] = useState(false);
327
+ const [newBranchName, setNewBranchName] = useState("");
328
+ const [branchActionLoading, setBranchActionLoading] = useState(false);
239
329
 
240
330
  // Status & Telemetry
241
331
  const [sessionStatus, setSessionStatus] = useState<SessionStatus | null>(null);
@@ -266,6 +356,34 @@ export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClos
266
356
  setGitFiles(data.files || []);
267
357
  setGitBranch(data.branch || "");
268
358
  setGitSync({ ahead: data.ahead || 0, behind: data.behind || 0 });
359
+
360
+ if (data.isGit) {
361
+ // Fetch branches
362
+ try {
363
+ const branchesRes = await fetch(`/api/git?action=branches&root=${encodeURIComponent(workspaceRoot)}`);
364
+ if (branchesRes.ok) {
365
+ const branchesData = await branchesRes.json();
366
+ if (branchesData.success) {
367
+ setGitBranches(branchesData.branches || []);
368
+ }
369
+ }
370
+ } catch (err) {
371
+ console.error("Failed to load git branches:", err);
372
+ }
373
+
374
+ // Fetch log
375
+ try {
376
+ const logRes = await fetch(`/api/git?action=log&root=${encodeURIComponent(workspaceRoot)}`);
377
+ if (logRes.ok) {
378
+ const logData = await logRes.json();
379
+ if (logData.success) {
380
+ setGitLog(logData.log || []);
381
+ }
382
+ }
383
+ } catch (err) {
384
+ console.error("Failed to load git log:", err);
385
+ }
386
+ }
269
387
  }
270
388
  }
271
389
  } catch (e) {
@@ -436,6 +554,102 @@ export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClos
436
554
  }
437
555
  };
438
556
 
557
+ const handleGitCheckout = async (branchName: string) => {
558
+ setBranchActionLoading(true);
559
+ try {
560
+ const res = await fetch("/api/git", {
561
+ method: "POST",
562
+ headers: { "Content-Type": "application/json" },
563
+ body: JSON.stringify({
564
+ action: "checkout",
565
+ branchName,
566
+ rootPath: workspaceRoot,
567
+ }),
568
+ });
569
+ const data = await res.json();
570
+ if (res.ok && data.success) {
571
+ await loadGitStatus();
572
+ } else {
573
+ alert(data.error || `Failed to checkout branch ${branchName}`);
574
+ }
575
+ } catch (e) {
576
+ console.error(e);
577
+ alert("Failed to checkout branch due to a network error.");
578
+ } finally {
579
+ setBranchActionLoading(false);
580
+ }
581
+ };
582
+
583
+ const handleGitCreateBranch = async (e?: React.FormEvent) => {
584
+ if (e) e.preventDefault();
585
+ if (!newBranchName.trim()) return;
586
+ setBranchActionLoading(true);
587
+ try {
588
+ const res = await fetch("/api/git", {
589
+ method: "POST",
590
+ headers: { "Content-Type": "application/json" },
591
+ body: JSON.stringify({
592
+ action: "create_branch",
593
+ branchName: newBranchName.trim(),
594
+ rootPath: workspaceRoot,
595
+ }),
596
+ });
597
+ const data = await res.json();
598
+ if (res.ok && data.success) {
599
+ setNewBranchName("");
600
+ await loadGitStatus();
601
+ } else {
602
+ alert(data.error || `Failed to create branch ${newBranchName}`);
603
+ }
604
+ } catch (e) {
605
+ console.error(e);
606
+ alert("Failed to create branch due to a network error.");
607
+ } finally {
608
+ setBranchActionLoading(false);
609
+ }
610
+ };
611
+
612
+ const handleGitDeleteBranch = async (branchName: string, force = false) => {
613
+ if (branchName === gitBranch) {
614
+ alert("Cannot delete the currently active branch.");
615
+ return;
616
+ }
617
+ const message = force
618
+ ? `Are you sure you want to FORCE delete the branch "${branchName}"? All unmerged changes will be lost.`
619
+ : `Are you sure you want to delete the branch "${branchName}"?`;
620
+ if (!confirm(message)) return;
621
+
622
+ setBranchActionLoading(true);
623
+ try {
624
+ const res = await fetch("/api/git", {
625
+ method: "POST",
626
+ headers: { "Content-Type": "application/json" },
627
+ body: JSON.stringify({
628
+ action: "delete_branch",
629
+ branchName,
630
+ force,
631
+ rootPath: workspaceRoot,
632
+ }),
633
+ });
634
+ const data = await res.json();
635
+ if (res.ok && data.success) {
636
+ await loadGitStatus();
637
+ } else {
638
+ // If it failed because it's not merged, offer force delete
639
+ if (!force && data.error && (data.error.includes("not fully merged") || data.error.includes("not merged"))) {
640
+ handleGitDeleteBranch(branchName, true);
641
+ } else {
642
+ alert(data.error || `Failed to delete branch ${branchName}`);
643
+ }
644
+ }
645
+ } catch (e) {
646
+ console.error(e);
647
+ alert("Failed to delete branch due to a network error.");
648
+ } finally {
649
+ setBranchActionLoading(false);
650
+ }
651
+ };
652
+
439
653
  const handleSelectGitFile = async (file: GitFile) => {
440
654
  try {
441
655
  const res = await fetch(`/api/git?action=diff&file=${encodeURIComponent(file.relativePath)}&root=${encodeURIComponent(workspaceRoot)}`);
@@ -1316,6 +1530,194 @@ export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClos
1316
1530
  </div>
1317
1531
  )}
1318
1532
  </div>
1533
+
1534
+ {/* Branches Section */}
1535
+ <div style={{ borderTop: "1px solid var(--border)", paddingTop: "0.75rem" }}>
1536
+ <div
1537
+ onClick={() => setIsBranchesExpanded(!isBranchesExpanded)}
1538
+ style={{
1539
+ display: "flex",
1540
+ justifyContent: "space-between",
1541
+ alignItems: "center",
1542
+ cursor: "pointer",
1543
+ marginBottom: "0.5rem",
1544
+ userSelect: "none"
1545
+ }}
1546
+ >
1547
+ <span style={{ fontSize: "0.7rem", fontWeight: 700, textTransform: "uppercase", color: "var(--muted)", display: "flex", alignItems: "center", gap: "0.25rem" }}>
1548
+ {isBranchesExpanded ? "▼" : "▶"} Branches ({gitBranches.length})
1549
+ </span>
1550
+ </div>
1551
+
1552
+ {isBranchesExpanded && (
1553
+ <div style={{ display: "flex", flexDirection: "column", gap: "0.5rem" }}>
1554
+ {/* Create Branch Input */}
1555
+ <form onSubmit={handleGitCreateBranch} style={{ display: "flex", gap: "0.25rem" }}>
1556
+ <input
1557
+ type="text"
1558
+ placeholder="New branch name..."
1559
+ value={newBranchName}
1560
+ onChange={(e) => setNewBranchName(e.target.value)}
1561
+ disabled={branchActionLoading}
1562
+ style={{
1563
+ flex: 1,
1564
+ backgroundColor: "#05070a",
1565
+ border: "1px solid var(--border)",
1566
+ borderRadius: "4px",
1567
+ padding: "0.25rem 0.5rem",
1568
+ color: "#ffffff",
1569
+ fontSize: "0.7rem",
1570
+ outline: "none"
1571
+ }}
1572
+ />
1573
+ <button
1574
+ type="submit"
1575
+ disabled={branchActionLoading || !newBranchName.trim()}
1576
+ style={{
1577
+ backgroundColor: "rgba(59, 130, 246, 0.15)",
1578
+ border: "1px solid #3b82f6",
1579
+ color: "#3b82f6",
1580
+ padding: "0.25rem 0.5rem",
1581
+ borderRadius: "4px",
1582
+ fontSize: "0.7rem",
1583
+ fontWeight: 600,
1584
+ cursor: "pointer",
1585
+ opacity: (!newBranchName.trim() || branchActionLoading) ? 0.5 : 1
1586
+ }}
1587
+ >
1588
+ + Create
1589
+ </button>
1590
+ </form>
1591
+
1592
+ {/* Branch List */}
1593
+ <div style={{ display: "flex", flexDirection: "column", gap: "0.25rem", maxHeight: "150px", overflowY: "auto" }}>
1594
+ {gitBranches.map((br) => (
1595
+ <div
1596
+ key={br.name}
1597
+ style={{
1598
+ display: "flex",
1599
+ alignItems: "center",
1600
+ justifyContent: "space-between",
1601
+ padding: "0.35rem 0.5rem",
1602
+ borderRadius: "4px",
1603
+ backgroundColor: br.isActive ? "rgba(59, 130, 246, 0.08)" : "transparent",
1604
+ border: br.isActive ? "1px solid rgba(59, 130, 246, 0.2)" : "1px solid transparent",
1605
+ fontSize: "0.75rem",
1606
+ transition: "all 0.15s ease",
1607
+ }}
1608
+ >
1609
+ <div style={{ display: "flex", alignItems: "center", gap: "0.35rem", fontWeight: br.isActive ? 700 : 500, color: br.isActive ? "var(--primary)" : "#cbd5e1", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", flex: 1 }}>
1610
+ <span style={{ fontSize: "0.7rem" }}>{br.isActive ? "●" : "○"}</span>
1611
+ <span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }} title={br.name}>{br.name}</span>
1612
+ </div>
1613
+ <div style={{ display: "flex", gap: "0.25rem" }}>
1614
+ {!br.isActive && (
1615
+ <>
1616
+ <button
1617
+ type="button"
1618
+ onClick={() => handleGitCheckout(br.name)}
1619
+ disabled={branchActionLoading}
1620
+ title={`Switch to ${br.name}`}
1621
+ style={{
1622
+ background: "none",
1623
+ border: "none",
1624
+ cursor: "pointer",
1625
+ fontSize: "0.7rem",
1626
+ padding: "2px 4px",
1627
+ color: "var(--muted)"
1628
+ }}
1629
+ >
1630
+
1631
+ </button>
1632
+ <button
1633
+ type="button"
1634
+ onClick={() => handleGitDeleteBranch(br.name)}
1635
+ disabled={branchActionLoading}
1636
+ title={`Delete ${br.name}`}
1637
+ style={{
1638
+ background: "none",
1639
+ border: "none",
1640
+ cursor: "pointer",
1641
+ fontSize: "0.7rem",
1642
+ padding: "2px 4px",
1643
+ color: "var(--muted)"
1644
+ }}
1645
+ >
1646
+
1647
+ </button>
1648
+ </>
1649
+ )}
1650
+ </div>
1651
+ </div>
1652
+ ))}
1653
+ </div>
1654
+ </div>
1655
+ )}
1656
+ </div>
1657
+
1658
+ {/* Commit History Section */}
1659
+ <div style={{ borderTop: "1px solid var(--border)", paddingTop: "0.75rem" }}>
1660
+ <div
1661
+ onClick={() => setIsHistoryExpanded(!isHistoryExpanded)}
1662
+ style={{
1663
+ display: "flex",
1664
+ justifyContent: "space-between",
1665
+ alignItems: "center",
1666
+ cursor: "pointer",
1667
+ marginBottom: "0.5rem",
1668
+ userSelect: "none"
1669
+ }}
1670
+ >
1671
+ <span style={{ fontSize: "0.7rem", fontWeight: 700, textTransform: "uppercase", color: "var(--muted)", display: "flex", alignItems: "center", gap: "0.25rem" }}>
1672
+ {isHistoryExpanded ? "▼" : "▶"} Commit History
1673
+ </span>
1674
+ </div>
1675
+
1676
+ {isHistoryExpanded && (
1677
+ <div style={{
1678
+ backgroundColor: "#05070a",
1679
+ border: "1px solid var(--border)",
1680
+ borderRadius: "6px",
1681
+ padding: "0.5rem",
1682
+ maxHeight: "220px",
1683
+ overflowY: "auto",
1684
+ overflowX: "auto"
1685
+ }}>
1686
+ {gitLog.length === 0 ? (
1687
+ <div style={{ color: "var(--muted)", fontStyle: "italic", fontSize: "0.7rem", textAlign: "center", padding: "0.5rem" }}>
1688
+ No commit history found.
1689
+ </div>
1690
+ ) : (
1691
+ <div style={{ fontFamily: "var(--font-mono)", fontSize: "0.65rem", lineHeight: "1.4", whiteSpace: "pre" }}>
1692
+ {gitLog.map((item, idx) => (
1693
+ <div
1694
+ key={idx}
1695
+ style={{
1696
+ display: "flex",
1697
+ padding: "2px 0",
1698
+ borderBottom: "1px solid rgba(255,255,255,0.01)",
1699
+ alignItems: "center"
1700
+ }}
1701
+ >
1702
+ <span style={{ color: "#3b82f6", flexShrink: 0, fontWeight: 700 }}>{item.graph}</span>
1703
+ {item.isCommit && (
1704
+ <>
1705
+ <span style={{ color: "#eab308", marginRight: "6px", flexShrink: 0 }} title="Commit Hash">{item.hash}</span>
1706
+ <span style={{ flex: 1, textOverflow: "ellipsis", overflow: "hidden", whiteSpace: "nowrap", color: "#cbd5e1" }} title={item.subject}>
1707
+ {item.subject}
1708
+ </span>
1709
+ <span style={{ color: "var(--muted)", fontSize: "0.55rem", marginLeft: "6px", flexShrink: 0 }} title={`${item.author} (${item.date})`}>
1710
+ {item.date}
1711
+ </span>
1712
+ </>
1713
+ )}
1714
+ </div>
1715
+ ))}
1716
+ </div>
1717
+ )}
1718
+ </div>
1719
+ )}
1720
+ </div>
1319
1721
  </div>
1320
1722
  ) : (
1321
1723
  <div style={{ padding: "1rem", color: "var(--muted)", fontSize: "0.75rem", fontStyle: "italic" }}>
@@ -1826,11 +2228,15 @@ export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClos
1826
2228
  ) : currentMode === 'split' ? (
1827
2229
  <div style={{ display: "flex", width: "100%", height: "100%", overflow: "hidden" }}>
1828
2230
  <div style={{ flex: 1, height: "100%" }}>
1829
- <textarea
2231
+ <MonacoEditor
2232
+ height="100%"
2233
+ language="markdown"
2234
+ theme="neural-loom-dark"
1830
2235
  value={activeFile.content}
1831
- onChange={(e) => handleContentChange(e.target.value)}
1832
- style={textareaStyle}
1833
- spellCheck={false}
2236
+ onChange={(value) => handleContentChange(value || "")}
2237
+ beforeMount={handleEditorWillMount}
2238
+ options={editorOptions}
2239
+ loading={<div style={{ padding: "1rem", color: "var(--muted)" }}>Loading Editor...</div>}
1834
2240
  />
1835
2241
  </div>
1836
2242
  <div style={{ flex: 1, height: "100%", overflow: "hidden" }}>
@@ -1838,19 +2244,27 @@ export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClos
1838
2244
  </div>
1839
2245
  </div>
1840
2246
  ) : (
1841
- <textarea
2247
+ <MonacoEditor
2248
+ height="100%"
2249
+ language="markdown"
2250
+ theme="neural-loom-dark"
1842
2251
  value={activeFile.content}
1843
- onChange={(e) => handleContentChange(e.target.value)}
1844
- style={textareaStyle}
1845
- spellCheck={false}
2252
+ onChange={(value) => handleContentChange(value || "")}
2253
+ beforeMount={handleEditorWillMount}
2254
+ options={editorOptions}
2255
+ loading={<div style={{ padding: "1rem", color: "var(--muted)" }}>Loading Editor...</div>}
1846
2256
  />
1847
2257
  )
1848
2258
  ) : (
1849
- <textarea
2259
+ <MonacoEditor
2260
+ height="100%"
2261
+ language={getEditorLanguage(activeFile.path)}
2262
+ theme="neural-loom-dark"
1850
2263
  value={activeFile.content}
1851
- onChange={(e) => handleContentChange(e.target.value)}
1852
- style={textareaStyle}
1853
- spellCheck={false}
2264
+ onChange={(value) => handleContentChange(value || "")}
2265
+ beforeMount={handleEditorWillMount}
2266
+ options={editorOptions}
2267
+ loading={<div style={{ padding: "1rem", color: "var(--muted)" }}>Loading Editor...</div>}
1854
2268
  />
1855
2269
  )
1856
2270
  ) : (