neural-loom 0.2.40 → 0.2.42

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 (108) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +3 -3
  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 +2 -2
  7. package/.next/fallback-build-manifest.json +3 -3
  8. package/.next/prerender-manifest.json +3 -3
  9. package/.next/server/app/_global-error.html +1 -1
  10. package/.next/server/app/_global-error.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  15. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  16. package/.next/server/app/_not-found.html +1 -1
  17. package/.next/server/app/_not-found.rsc +2 -2
  18. package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  19. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  20. package/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  21. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  22. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  23. package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  24. package/.next/server/app/api/context/aider/route.js.nft.json +1 -1
  25. package/.next/server/app/api/context/claude/route.js.nft.json +1 -1
  26. package/.next/server/app/api/context/ssh/route.js.nft.json +1 -1
  27. package/.next/server/app/api/recents/route.js.nft.json +1 -1
  28. package/.next/server/app/api/sessions/inject/route.js +1 -1
  29. package/.next/server/app/api/sessions/inject/route.js.nft.json +1 -1
  30. package/.next/server/app/api/sessions/input/route.js +1 -1
  31. package/.next/server/app/api/sessions/input/route.js.nft.json +1 -1
  32. package/.next/server/app/api/sessions/launch/route.js +1 -1
  33. package/.next/server/app/api/sessions/launch/route.js.nft.json +1 -1
  34. package/.next/server/app/api/sessions/route.js +1 -1
  35. package/.next/server/app/api/sessions/route.js.nft.json +1 -1
  36. package/.next/server/app/api/sessions/scope/route.js +1 -1
  37. package/.next/server/app/api/sessions/scope/route.js.nft.json +1 -1
  38. package/.next/server/app/api/sessions/start/route.js +1 -1
  39. package/.next/server/app/api/sessions/start/route.js.nft.json +1 -1
  40. package/.next/server/app/api/sessions/stats/route.js +1 -1
  41. package/.next/server/app/api/sessions/stats/route.js.nft.json +1 -1
  42. package/.next/server/app/api/sessions/stop/route.js +1 -1
  43. package/.next/server/app/api/sessions/stop/route.js.nft.json +1 -1
  44. package/.next/server/app/index.html +1 -1
  45. package/.next/server/app/index.rsc +3 -3
  46. package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  47. package/.next/server/app/index.segments/_full.segment.rsc +3 -3
  48. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  49. package/.next/server/app/index.segments/_index.segment.rsc +1 -1
  50. package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  51. package/.next/server/app/page/react-loadable-manifest.json +3 -3
  52. package/.next/server/app/page_client-reference-manifest.js +1 -1
  53. package/.next/server/chunks/[root-of-the-server]__005xmeg._.js +15 -0
  54. package/.next/server/chunks/{[root-of-the-server]__0d9_-7g._.js.map → [root-of-the-server]__005xmeg._.js.map} +1 -1
  55. package/.next/server/chunks/[root-of-the-server]__0_~zjnf._.js +15 -0
  56. package/.next/server/chunks/{[root-of-the-server]__0fhv7.1._.js.map → [root-of-the-server]__0_~zjnf._.js.map} +1 -1
  57. package/.next/server/chunks/[root-of-the-server]__0d0pykb._.js +8 -8
  58. package/.next/server/chunks/[root-of-the-server]__0d0pykb._.js.map +1 -1
  59. package/.next/server/chunks/[root-of-the-server]__0hl8jxx._.js +15 -0
  60. package/.next/server/chunks/{[root-of-the-server]__0~zauvq._.js.map → [root-of-the-server]__0hl8jxx._.js.map} +1 -1
  61. package/.next/server/chunks/[root-of-the-server]__0ho7s6e._.js +15 -0
  62. package/.next/server/chunks/{[root-of-the-server]__0-vx6-s._.js.map → [root-of-the-server]__0ho7s6e._.js.map} +1 -1
  63. package/.next/server/chunks/[root-of-the-server]__0lh_jn-._.js +15 -0
  64. package/.next/server/chunks/{[root-of-the-server]__0xr~y1f._.js.map → [root-of-the-server]__0lh_jn-._.js.map} +1 -1
  65. package/.next/server/chunks/[root-of-the-server]__0mq3k0u._.js +15 -0
  66. package/.next/server/chunks/{[root-of-the-server]__0zkcq9q._.js.map → [root-of-the-server]__0mq3k0u._.js.map} +1 -1
  67. package/.next/server/chunks/[root-of-the-server]__0rr36fm._.js +15 -0
  68. package/.next/server/chunks/{[root-of-the-server]__0qqnqov._.js.map → [root-of-the-server]__0rr36fm._.js.map} +1 -1
  69. package/.next/server/chunks/[root-of-the-server]__0xcrc-1._.js +15 -0
  70. package/.next/server/chunks/{[root-of-the-server]__0t_~on0._.js.map → [root-of-the-server]__0xcrc-1._.js.map} +1 -1
  71. package/.next/server/chunks/ssr/[root-of-the-server]__0v73tbn._.js +1 -1
  72. package/.next/server/chunks/ssr/[root-of-the-server]__0v73tbn._.js.map +1 -1
  73. package/.next/server/chunks/ssr/_0t7oqy6._.js +1 -1
  74. package/.next/server/chunks/ssr/_0t7oqy6._.js.map +1 -1
  75. package/.next/server/middleware-build-manifest.js +3 -3
  76. package/.next/server/pages/404.html +1 -1
  77. package/.next/server/pages/500.html +1 -1
  78. package/.next/server/server-reference-manifest.js +1 -1
  79. package/.next/server/server-reference-manifest.json +1 -1
  80. package/.next/static/chunks/{0x__nd4avi0fg.js → 070hz362~_h8n.js} +1 -1
  81. package/.next/static/chunks/0bclk7hf35ac4.js +1 -0
  82. package/.next/static/chunks/17jifiroq0_q..js +1 -0
  83. package/.next/trace +1 -1
  84. package/.next/trace-build +1 -1
  85. package/package.json +1 -1
  86. package/src/app/components/IdeLayout.tsx +500 -25
  87. package/src/app/components/TerminalConsole.tsx +2 -2
  88. package/src/app/layout.tsx +0 -1
  89. package/src/app/page.tsx +12 -4
  90. package/src/lib/agents/AiderRunner.ts +8 -8
  91. package/src/lib/agents/ClaudeRunner.ts +43 -18
  92. package/src/lib/agents/DockerRunner.ts +1 -3
  93. package/src/lib/agents/SSHRunner.ts +1 -3
  94. package/src/lib/agents/ShellRunner.ts +1 -3
  95. package/src/lib/agents/WebSocketServer.ts +20 -13
  96. package/.next/server/chunks/[root-of-the-server]__0-vx6-s._.js +0 -15
  97. package/.next/server/chunks/[root-of-the-server]__0d9_-7g._.js +0 -15
  98. package/.next/server/chunks/[root-of-the-server]__0fhv7.1._.js +0 -15
  99. package/.next/server/chunks/[root-of-the-server]__0qqnqov._.js +0 -15
  100. package/.next/server/chunks/[root-of-the-server]__0t_~on0._.js +0 -15
  101. package/.next/server/chunks/[root-of-the-server]__0xr~y1f._.js +0 -15
  102. package/.next/server/chunks/[root-of-the-server]__0zkcq9q._.js +0 -15
  103. package/.next/server/chunks/[root-of-the-server]__0~zauvq._.js +0 -15
  104. package/.next/static/chunks/07-tub5vqy.fk.js +0 -1
  105. package/.next/static/chunks/0wq3_34inbzj6.js +0 -1
  106. /package/.next/static/{5uGIuH8oXybqivkeHIrqb → KK9yy3Qq3Z7N_cZVRK4cc}/_buildManifest.js +0 -0
  107. /package/.next/static/{5uGIuH8oXybqivkeHIrqb → KK9yy3Qq3Z7N_cZVRK4cc}/_clientMiddlewareManifest.js +0 -0
  108. /package/.next/static/{5uGIuH8oXybqivkeHIrqb → KK9yy3Qq3Z7N_cZVRK4cc}/_ssgManifest.js +0 -0
@@ -156,7 +156,8 @@ export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClos
156
156
 
157
157
  const [editorMode, setEditorMode] = useState<'edit' | 'split' | 'preview'>('edit');
158
158
  const [explorerVisible, setExplorerVisible] = useState(true);
159
- const [activeTab, setActiveTab] = useState<"explorer" | "git" | "analytics" | "auditor">("explorer");
159
+ const [activeTab, setActiveTab] = useState<"explorer" | "git" | "analytics" | "auditor" | "browser">("explorer");
160
+ const [browserUrlInput, setBrowserUrlInput] = useState("http://localhost:3000");
160
161
  const [isRefreshing, setIsRefreshing] = useState(false);
161
162
 
162
163
  // Layout sizing states
@@ -806,6 +807,25 @@ export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClos
806
807
  }
807
808
  };
808
809
 
810
+ const handleOpenBrowserTab = (url: string) => {
811
+ const formattedUrl = url.trim();
812
+ if (!formattedUrl) return;
813
+ const browserPath = `browser:${formattedUrl}`;
814
+ const alreadyOpen = openFiles.find(f => f.path === browserPath);
815
+ if (!alreadyOpen) {
816
+ setOpenFiles(prev => [
817
+ ...prev,
818
+ {
819
+ name: `Browser: ${formattedUrl.replace(/^https?:\/\//, "")}`,
820
+ path: browserPath,
821
+ content: formattedUrl,
822
+ isModified: false,
823
+ }
824
+ ]);
825
+ }
826
+ setActiveFilePath(browserPath);
827
+ };
828
+
809
829
  const activeFile = openFiles.find((f) => f.path === activeFilePath);
810
830
  const isMarkdown = activeFile ? activeFile.path.toLowerCase().endsWith(".md") : false;
811
831
  const currentMode = isMarkdown ? editorMode : "edit";
@@ -821,7 +841,7 @@ export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClos
821
841
  };
822
842
 
823
843
  const handleSaveFile = useCallback(async () => {
824
- if (!activeFile) return;
844
+ if (!activeFile || activeFile.path.startsWith("diff:") || activeFile.path.startsWith("browser:")) return;
825
845
  try {
826
846
  const res = await fetch("/api/files", {
827
847
  method: "POST",
@@ -968,6 +988,26 @@ export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClos
968
988
  >
969
989
  🔍
970
990
  </div>
991
+
992
+ {/* Web Browser Tab */}
993
+ <div
994
+ onClick={() => {
995
+ if (activeTab === "browser") {
996
+ setExplorerVisible(!explorerVisible);
997
+ } else {
998
+ setActiveTab("browser");
999
+ setExplorerVisible(true);
1000
+ }
1001
+ }}
1002
+ style={{
1003
+ ...sidebarIconStyle,
1004
+ backgroundColor: activeTab === "browser" && explorerVisible ? "rgba(255,255,255,0.08)" : "transparent",
1005
+ borderLeft: activeTab === "browser" && explorerVisible ? "2px solid var(--primary)" : "2px solid transparent",
1006
+ }}
1007
+ title="Local Web Browser Test Preview"
1008
+ >
1009
+ 🌐
1010
+ </div>
971
1011
  </div>
972
1012
 
973
1013
  <div
@@ -989,6 +1029,7 @@ export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClos
989
1029
  {activeTab === "git" && "Source Control"}
990
1030
  {activeTab === "analytics" && "Telemetry & Cost"}
991
1031
  {activeTab === "auditor" && "Session Auditor"}
1032
+ {activeTab === "browser" && "Browser Preview"}
992
1033
  </span>
993
1034
  {activeTab === "explorer" && (
994
1035
  <div style={{ display: "flex", gap: "0.25rem" }}>
@@ -1254,6 +1295,105 @@ export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClos
1254
1295
  </div>
1255
1296
  )}
1256
1297
 
1298
+ {activeTab === "browser" && (
1299
+ <div style={{ padding: "1rem", display: "flex", flexDirection: "column", gap: "1rem" }}>
1300
+ <div>
1301
+ <label style={{ fontSize: "0.7rem", fontWeight: 700, textTransform: "uppercase", color: "var(--muted)", display: "block", marginBottom: "0.4rem" }}>
1302
+ Url Address
1303
+ </label>
1304
+ <div style={{ display: "flex", gap: "0.25rem" }}>
1305
+ <input
1306
+ type="text"
1307
+ value={browserUrlInput}
1308
+ onChange={(e) => setBrowserUrlInput(e.target.value)}
1309
+ placeholder="http://localhost:3000"
1310
+ style={{
1311
+ flex: 1,
1312
+ backgroundColor: "#05070a",
1313
+ border: "1px solid var(--border)",
1314
+ borderRadius: "4px",
1315
+ padding: "0.35rem 0.5rem",
1316
+ color: "#ffffff",
1317
+ fontSize: "0.75rem",
1318
+ outline: "none"
1319
+ }}
1320
+ onKeyDown={(e) => {
1321
+ if (e.key === "Enter") {
1322
+ handleOpenBrowserTab(browserUrlInput);
1323
+ }
1324
+ }}
1325
+ />
1326
+ <button
1327
+ onClick={() => handleOpenBrowserTab(browserUrlInput)}
1328
+ style={{
1329
+ backgroundColor: "var(--primary)",
1330
+ border: "none",
1331
+ color: "#ffffff",
1332
+ padding: "0.35rem 0.75rem",
1333
+ borderRadius: "4px",
1334
+ fontSize: "0.75rem",
1335
+ fontWeight: 600,
1336
+ cursor: "pointer"
1337
+ }}
1338
+ >
1339
+ Go
1340
+ </button>
1341
+ </div>
1342
+ </div>
1343
+
1344
+ <div>
1345
+ <label style={{ fontSize: "0.7rem", fontWeight: 700, textTransform: "uppercase", color: "var(--muted)", display: "block", marginBottom: "0.5rem" }}>
1346
+ Quick Launch Ports
1347
+ </label>
1348
+ <div style={{ display: "flex", flexDirection: "column", gap: "0.35rem" }}>
1349
+ {[
1350
+ { label: "Next.js App (3000)", url: "http://localhost:3000" },
1351
+ { label: "WebSocket Gateway (3001)", url: "http://localhost:3001" },
1352
+ { label: "Vite Server (5173)", url: "http://localhost:5173" }
1353
+ ].map((item, idx) => (
1354
+ <div
1355
+ key={idx}
1356
+ onClick={() => {
1357
+ setBrowserUrlInput(item.url);
1358
+ handleOpenBrowserTab(item.url);
1359
+ }}
1360
+ style={{
1361
+ display: "flex",
1362
+ alignItems: "center",
1363
+ justifyContent: "space-between",
1364
+ padding: "0.5rem 0.75rem",
1365
+ borderRadius: "4px",
1366
+ backgroundColor: "rgba(255,255,255,0.02)",
1367
+ border: "1px solid var(--border)",
1368
+ cursor: "pointer",
1369
+ fontSize: "0.75rem",
1370
+ transition: "all 0.15s ease"
1371
+ }}
1372
+ onMouseEnter={(e) => e.currentTarget.style.backgroundColor = "rgba(255,255,255,0.06)"}
1373
+ onMouseLeave={(e) => e.currentTarget.style.backgroundColor = "rgba(255,255,255,0.02)"}
1374
+ >
1375
+ <span style={{ color: "var(--foreground)" }}>{item.label}</span>
1376
+ <span style={{ color: "var(--primary)", fontSize: "0.8rem" }}>🌐➔</span>
1377
+ </div>
1378
+ ))}
1379
+ </div>
1380
+ </div>
1381
+
1382
+ <div style={{
1383
+ marginTop: "auto",
1384
+ padding: "0.5rem",
1385
+ borderRadius: "6px",
1386
+ backgroundColor: "rgba(234, 179, 8, 0.05)",
1387
+ border: "1px solid rgba(234, 179, 8, 0.15)",
1388
+ fontSize: "0.65rem",
1389
+ color: "var(--muted)",
1390
+ lineHeight: "1.4"
1391
+ }}>
1392
+ <span style={{ color: "#eab308", fontWeight: 700 }}>⚠️ Iframe CSP Note</span>: Some public websites (e.g. Google, GitHub) block loading inside frames via security headers. Use this tab primarily to preview and debug local development webservers.
1393
+ </div>
1394
+ </div>
1395
+ )}
1396
+
1257
1397
  {activeTab === "analytics" && (
1258
1398
  <div style={{ padding: "0.5rem 1rem", display: "flex", flexDirection: "column", gap: "1.25rem" }}>
1259
1399
  <div>
@@ -1552,7 +1692,7 @@ export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClos
1552
1692
  ))}
1553
1693
  </div>
1554
1694
 
1555
- {activeFile && !activeFile.path.startsWith("diff:") && (
1695
+ {activeFile && !activeFile.path.startsWith("diff:") && !activeFile.path.startsWith("browser:") && (
1556
1696
  <div style={{ display: "flex", alignItems: "center", gap: "0.75rem" }}>
1557
1697
  {isMarkdown && (
1558
1698
  <div style={modeToggleContainerStyle}>
@@ -1612,28 +1752,10 @@ export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClos
1612
1752
  {/* Editor contents */}
1613
1753
  <div style={editorWorkspaceStyle}>
1614
1754
  {activeFile ? (
1615
- activeFile.path.startsWith("diff:") ? (
1616
- <div style={{ fontFamily: "var(--font-mono)", fontSize: "0.8rem", padding: "1rem", overflow: "auto", height: "100%", backgroundColor: "#0b101b", lineHeight: 1.5 }}>
1617
- {activeFile.content.split("\n").map((line, i) => {
1618
- let color = "#e2e8f0";
1619
- let bg = "transparent";
1620
- if (line.startsWith("+")) {
1621
- color = "#4ade80";
1622
- bg = "rgba(74, 222, 128, 0.08)";
1623
- } else if (line.startsWith("-")) {
1624
- color = "#f87171";
1625
- bg = "rgba(248, 113, 113, 0.08)";
1626
- } else if (line.startsWith("@@")) {
1627
- color = "#60a5fa";
1628
- bg = "rgba(96, 165, 250, 0.05)";
1629
- }
1630
- return (
1631
- <div key={i} style={{ color, backgroundColor: bg, whiteSpace: "pre-wrap", padding: "0.1rem 0.5rem" }}>
1632
- {line}
1633
- </div>
1634
- );
1635
- })}
1636
- </div>
1755
+ activeFile.path.startsWith("browser:") ? (
1756
+ <BrowserView url={activeFile.content} />
1757
+ ) : activeFile.path.startsWith("diff:") ? (
1758
+ <DiffView content={activeFile.content} filePath={activeFile.path.substring(5)} />
1637
1759
  ) : isMarkdown ? (
1638
1760
  currentMode === 'preview' ? (
1639
1761
  <div style={{ height: "100%", overflow: "hidden" }}>
@@ -2785,6 +2907,359 @@ function GitFileRow({ file, isStaged, onAction, onSelect, onIgnore, onDiscard }:
2785
2907
  );
2786
2908
  }
2787
2909
 
2910
+ interface BrowserViewProps {
2911
+ url: string;
2912
+ }
2913
+
2914
+ function BrowserView({ url }: BrowserViewProps) {
2915
+ const [currentUrl, setCurrentUrl] = useState(url);
2916
+ const [iframeUrl, setIframeUrl] = useState(url);
2917
+ const [refreshKey, setRefreshKey] = useState(0);
2918
+
2919
+ useEffect(() => {
2920
+ setCurrentUrl(url);
2921
+ setIframeUrl(url);
2922
+ }, [url]);
2923
+
2924
+ const handleGo = () => {
2925
+ let targetUrl = currentUrl.trim();
2926
+ if (targetUrl && !/^https?:\/\//i.test(targetUrl)) {
2927
+ targetUrl = "http://" + targetUrl;
2928
+ }
2929
+ setCurrentUrl(targetUrl);
2930
+ setIframeUrl(targetUrl);
2931
+ };
2932
+
2933
+ const handleReload = () => {
2934
+ setRefreshKey(prev => prev + 1);
2935
+ };
2936
+
2937
+ const handleOpenExternal = () => {
2938
+ window.open(iframeUrl, "_blank");
2939
+ };
2940
+
2941
+ return (
2942
+ <div style={{ display: "flex", flexDirection: "column", width: "100%", height: "100%", backgroundColor: "#0b101b" }}>
2943
+ {/* Navigation Toolbar */}
2944
+ <div style={{
2945
+ display: "flex",
2946
+ alignItems: "center",
2947
+ gap: "0.5rem",
2948
+ padding: "0.5rem",
2949
+ backgroundColor: "#05070a",
2950
+ borderBottom: "1px solid var(--border)",
2951
+ }}>
2952
+ <button
2953
+ onClick={handleReload}
2954
+ title="Hard Reload Frame"
2955
+ style={{
2956
+ background: "none",
2957
+ border: "1px solid var(--border)",
2958
+ borderRadius: "4px",
2959
+ color: "var(--muted)",
2960
+ cursor: "pointer",
2961
+ fontSize: "0.85rem",
2962
+ padding: "0.25rem 0.5rem",
2963
+ display: "flex",
2964
+ alignItems: "center",
2965
+ backgroundColor: "rgba(255,255,255,0.02)"
2966
+ }}
2967
+ onMouseEnter={(e) => e.currentTarget.style.color = "var(--foreground)"}
2968
+ onMouseLeave={(e) => e.currentTarget.style.color = "var(--muted)"}
2969
+ >
2970
+ 🔄
2971
+ </button>
2972
+
2973
+ <input
2974
+ type="text"
2975
+ value={currentUrl}
2976
+ onChange={(e) => setCurrentUrl(e.target.value)}
2977
+ onKeyDown={(e) => {
2978
+ if (e.key === "Enter") {
2979
+ handleGo();
2980
+ }
2981
+ }}
2982
+ style={{
2983
+ flex: 1,
2984
+ backgroundColor: "#080c14",
2985
+ border: "1px solid var(--border)",
2986
+ borderRadius: "4px",
2987
+ padding: "0.25rem 0.75rem",
2988
+ color: "#ffffff",
2989
+ fontSize: "0.8rem",
2990
+ outline: "none",
2991
+ fontFamily: "var(--font-mono), monospace"
2992
+ }}
2993
+ />
2994
+
2995
+ <button
2996
+ onClick={handleGo}
2997
+ title="Go to URL"
2998
+ style={{
2999
+ background: "none",
3000
+ border: "1px solid var(--border)",
3001
+ borderRadius: "4px",
3002
+ color: "var(--primary)",
3003
+ cursor: "pointer",
3004
+ fontSize: "0.8rem",
3005
+ padding: "0.25rem 0.5rem",
3006
+ display: "flex",
3007
+ alignItems: "center",
3008
+ backgroundColor: "rgba(255,255,255,0.02)"
3009
+ }}
3010
+ >
3011
+
3012
+ </button>
3013
+
3014
+ <button
3015
+ onClick={handleOpenExternal}
3016
+ title="Open in External Browser"
3017
+ style={{
3018
+ background: "none",
3019
+ border: "1px solid var(--border)",
3020
+ borderRadius: "4px",
3021
+ color: "var(--muted)",
3022
+ cursor: "pointer",
3023
+ fontSize: "0.85rem",
3024
+ padding: "0.25rem 0.5rem",
3025
+ display: "flex",
3026
+ alignItems: "center",
3027
+ backgroundColor: "rgba(255,255,255,0.02)"
3028
+ }}
3029
+ onMouseEnter={(e) => e.currentTarget.style.color = "var(--foreground)"}
3030
+ onMouseLeave={(e) => e.currentTarget.style.color = "var(--muted)"}
3031
+ >
3032
+ 🌐
3033
+ </button>
3034
+ </div>
3035
+
3036
+ {/* Frame Wrapper */}
3037
+ <div style={{ flex: 1, position: "relative", width: "100%", height: "100%", backgroundColor: "#ffffff" }}>
3038
+ <iframe
3039
+ key={refreshKey}
3040
+ src={iframeUrl}
3041
+ style={{
3042
+ width: "100%",
3043
+ height: "100%",
3044
+ border: "none",
3045
+ backgroundColor: "#ffffff",
3046
+ }}
3047
+ sandbox="allow-scripts allow-same-origin allow-popups allow-forms"
3048
+ />
3049
+ </div>
3050
+ </div>
3051
+ );
3052
+ }
3053
+
3054
+ interface DiffViewProps {
3055
+ content: string;
3056
+ filePath: string;
3057
+ }
3058
+
3059
+ function DiffView({ content, filePath }: DiffViewProps) {
3060
+ const lines = content.split(/\r?\n/);
3061
+
3062
+ let oldLineNum = 0;
3063
+ let newLineNum = 0;
3064
+
3065
+ let additions = 0;
3066
+ let deletions = 0;
3067
+
3068
+ const parsedLines = lines.map((line) => {
3069
+ let type: 'meta' | 'header' | 'hunk' | 'addition' | 'deletion' | 'normal' = 'normal';
3070
+ let lineContent = line;
3071
+ let oldNum: number | null = null;
3072
+ let newNum: number | null = null;
3073
+
3074
+ if (line.startsWith("diff --git") || line.startsWith("index ") || line.startsWith("--- ") || line.startsWith("+++ ")) {
3075
+ type = 'header';
3076
+ } else if (line.startsWith("@@")) {
3077
+ type = 'hunk';
3078
+ const match = line.match(/^@@\s+-(\d+)(?:,\d+)?\s+\+(\d+)(?:,\d+)?\s+@@/);
3079
+ if (match) {
3080
+ oldLineNum = parseInt(match[1], 10);
3081
+ newLineNum = parseInt(match[2], 10);
3082
+ }
3083
+ } else if (line.startsWith("+")) {
3084
+ type = 'addition';
3085
+ lineContent = line.substring(1);
3086
+ newNum = newLineNum++;
3087
+ additions++;
3088
+ } else if (line.startsWith("-")) {
3089
+ type = 'deletion';
3090
+ lineContent = line.substring(1);
3091
+ oldNum = oldLineNum++;
3092
+ deletions++;
3093
+ } else {
3094
+ type = 'normal';
3095
+ if (line.startsWith(" ")) {
3096
+ lineContent = line.substring(1);
3097
+ }
3098
+ if (oldLineNum > 0) {
3099
+ oldNum = oldLineNum++;
3100
+ }
3101
+ if (newLineNum > 0) {
3102
+ newNum = newLineNum++;
3103
+ }
3104
+ }
3105
+
3106
+ return {
3107
+ type,
3108
+ content: lineContent,
3109
+ oldNum,
3110
+ newNum,
3111
+ raw: line
3112
+ };
3113
+ });
3114
+
3115
+ return (
3116
+ <div style={{ display: "flex", flexDirection: "column", height: "100%", backgroundColor: "#0b101b" }}>
3117
+ <div style={{
3118
+ display: "flex",
3119
+ alignItems: "center",
3120
+ justifyContent: "space-between",
3121
+ padding: "0.5rem 1rem",
3122
+ backgroundColor: "#05070a",
3123
+ borderBottom: "1px solid var(--border)",
3124
+ }}>
3125
+ <div style={{ display: "flex", alignItems: "center", gap: "0.5rem" }}>
3126
+ <span style={{ fontSize: "1rem" }}>📄</span>
3127
+ <span style={{ fontWeight: 600, fontSize: "0.85rem", color: "var(--foreground)", fontFamily: "var(--font-mono)" }}>
3128
+ {filePath}
3129
+ </span>
3130
+ </div>
3131
+ <div style={{ display: "flex", alignItems: "center", gap: "0.75rem", fontSize: "0.75rem" }}>
3132
+ {additions > 0 && (
3133
+ <span style={{ color: "#4ade80", backgroundColor: "rgba(74, 222, 128, 0.1)", padding: "0.15rem 0.4rem", borderRadius: "4px", fontWeight: 600 }}>
3134
+ +{additions} lines
3135
+ </span>
3136
+ )}
3137
+ {deletions > 0 && (
3138
+ <span style={{ color: "#f87171", backgroundColor: "rgba(248, 113, 113, 0.1)", padding: "0.15rem 0.4rem", borderRadius: "4px", fontWeight: 600 }}>
3139
+ -{deletions} lines
3140
+ </span>
3141
+ )}
3142
+ <span style={{ color: "var(--muted)" }}>
3143
+ {parsedLines.length} lines in diff
3144
+ </span>
3145
+ </div>
3146
+ </div>
3147
+
3148
+ <div style={{ flex: 1, overflow: "auto", padding: "0.5rem 0" }}>
3149
+ <table style={{ width: "100%", borderCollapse: "collapse", fontFamily: "var(--font-mono), monospace", fontSize: "0.8rem", lineHeight: 1.5 }}>
3150
+ <tbody>
3151
+ {parsedLines.map((lineObj, idx) => {
3152
+ if (lineObj.type === 'header') {
3153
+ return (
3154
+ <tr key={idx} style={{ backgroundColor: "rgba(255,255,255,0.02)" }}>
3155
+ <td colSpan={4} style={{ color: "var(--muted)", padding: "0.25rem 1rem", fontSize: "0.75rem", borderBottom: "1px solid rgba(255,255,255,0.03)" }}>
3156
+ {lineObj.raw}
3157
+ </td>
3158
+ </tr>
3159
+ );
3160
+ }
3161
+ if (lineObj.type === 'hunk') {
3162
+ return (
3163
+ <tr key={idx} style={{ backgroundColor: "rgba(96, 165, 250, 0.05)" }}>
3164
+ <td colSpan={2} style={{
3165
+ width: "80px",
3166
+ textAlign: "center",
3167
+ color: "rgba(96, 165, 250, 0.6)",
3168
+ borderRight: "1px solid rgba(96, 165, 250, 0.15)",
3169
+ userSelect: "none",
3170
+ padding: "0.25rem 0",
3171
+ fontSize: "0.7rem",
3172
+ }}>
3173
+ @@
3174
+ </td>
3175
+ <td style={{
3176
+ width: "25px",
3177
+ borderRight: "1px solid rgba(255, 255, 255, 0.05)",
3178
+ }}></td>
3179
+ <td style={{ color: "#60a5fa", padding: "0.25rem 1rem", fontStyle: "italic", fontSize: "0.75rem" }}>
3180
+ {lineObj.raw}
3181
+ </td>
3182
+ </tr>
3183
+ );
3184
+ }
3185
+
3186
+ let rowBg = "transparent";
3187
+ let codeColor = "var(--foreground)";
3188
+ let sign = " ";
3189
+ let signColor = "transparent";
3190
+
3191
+ if (lineObj.type === 'addition') {
3192
+ rowBg = "rgba(74, 222, 128, 0.08)";
3193
+ codeColor = "#4ade80";
3194
+ sign = "+";
3195
+ signColor = "rgba(74, 222, 128, 0.4)";
3196
+ } else if (lineObj.type === 'deletion') {
3197
+ rowBg = "rgba(248, 113, 113, 0.08)";
3198
+ codeColor = "#f87171";
3199
+ sign = "-";
3200
+ signColor = "rgba(248, 113, 113, 0.4)";
3201
+ }
3202
+
3203
+ return (
3204
+ <tr key={idx} style={{ backgroundColor: rowBg }}>
3205
+ {/* Old line number */}
3206
+ <td style={{
3207
+ width: "40px",
3208
+ textAlign: "right",
3209
+ paddingRight: "0.75rem",
3210
+ color: "var(--muted)",
3211
+ opacity: lineObj.oldNum ? 0.6 : 0.2,
3212
+ userSelect: "none",
3213
+ borderRight: "1px solid rgba(255, 255, 255, 0.05)",
3214
+ fontSize: "0.75rem"
3215
+ }}>
3216
+ {lineObj.oldNum || ""}
3217
+ </td>
3218
+ {/* New line number */}
3219
+ <td style={{
3220
+ width: "40px",
3221
+ textAlign: "right",
3222
+ paddingRight: "0.75rem",
3223
+ color: "var(--muted)",
3224
+ opacity: lineObj.newNum ? 0.6 : 0.2,
3225
+ userSelect: "none",
3226
+ borderRight: "1px solid rgba(255, 255, 255, 0.05)",
3227
+ fontSize: "0.75rem"
3228
+ }}>
3229
+ {lineObj.newNum || ""}
3230
+ </td>
3231
+ {/* Sign (+ / -) */}
3232
+ <td style={{
3233
+ width: "25px",
3234
+ textAlign: "center",
3235
+ color: signColor,
3236
+ userSelect: "none",
3237
+ fontWeight: "bold",
3238
+ fontSize: "0.85rem",
3239
+ borderRight: "1px solid rgba(255, 255, 255, 0.05)",
3240
+ }}>
3241
+ {sign}
3242
+ </td>
3243
+ {/* Code line content */}
3244
+ <td style={{
3245
+ paddingLeft: "0.75rem",
3246
+ color: codeColor,
3247
+ whiteSpace: "pre-wrap",
3248
+ wordBreak: "break-all"
3249
+ }}>
3250
+ {lineObj.content}
3251
+ </td>
3252
+ </tr>
3253
+ );
3254
+ })}
3255
+ </tbody>
3256
+ </table>
3257
+ </div>
3258
+ </div>
3259
+ );
3260
+ }
3261
+
3262
+
2788
3263
  const commitTextareaStyle: React.CSSProperties = {
2789
3264
  width: "100%",
2790
3265
  minHeight: "60px",
@@ -25,9 +25,9 @@ export default function TerminalConsole({ sessionId, height = "400px", wsPort =
25
25
  // Initialize xterm
26
26
  const term = new Terminal({
27
27
  cursorBlink: true,
28
- fontFamily: "var(--font-mono), monospace",
28
+ fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
29
29
  fontSize: 13,
30
- lineHeight: 1.4,
30
+ lineHeight: 1.2,
31
31
  scrollback: 5000,
32
32
  theme: {
33
33
  background: "#05070a",
@@ -20,7 +20,6 @@ export const metadata: Metadata = {
20
20
  shortcut: "/logo.png",
21
21
  apple: "/logo.png",
22
22
  },
23
- manifest: "/manifest.json",
24
23
  };
25
24
 
26
25
  export default function RootLayout({
package/src/app/page.tsx CHANGED
@@ -145,11 +145,19 @@ export default function Home() {
145
145
  };
146
146
  initFetch();
147
147
 
148
- // Register PWA Service Worker for standalone taskbar icon installation
148
+ // Unregister any active Service Workers to disable PWA interceptions
149
149
  if ('serviceWorker' in navigator) {
150
- navigator.serviceWorker.register('/sw.js')
151
- .then((reg) => console.log('Service Worker registered:', reg.scope))
152
- .catch((err) => console.error('Service Worker registration failed:', err));
150
+ navigator.serviceWorker.getRegistrations().then((registrations) => {
151
+ for (const registration of registrations) {
152
+ registration.unregister()
153
+ .then((success) => {
154
+ if (success) {
155
+ console.log('Service Worker unregistered successfully.');
156
+ }
157
+ })
158
+ .catch((err) => console.error('Failed to unregister Service Worker:', err));
159
+ }
160
+ });
153
161
  }
154
162
 
155
163
  const interval = setInterval(fetchSessions, 4000);
@@ -6,9 +6,7 @@ import { AgentRunner, RunnerStatus } from "./AgentRunner";
6
6
  import { SessionPersister } from "./SessionPersister";
7
7
 
8
8
  function isWindows(): boolean {
9
- if (typeof process === "undefined") return false;
10
- const key = Object.keys(process).find(k => k.startsWith("plat") && k.endsWith("form"));
11
- return !!key && (process as unknown as Record<string, unknown>)[key] === "win32";
9
+ return typeof process !== "undefined" && process.platform === "win32";
12
10
  }
13
11
 
14
12
  function findGitBash(): string | null {
@@ -106,12 +104,14 @@ export class AiderRunner extends AgentRunner {
106
104
  this.appendLog(`\x1b[1;36m NEURALLOOM AIDER AGENT ROUTE (${this.type.toUpperCase()}) \x1b[0m\r\n`);
107
105
  this.appendLog("\x1b[1;35m==================================================\x1b[0m\r\n");
108
106
 
109
- const bashPath = findGitBash();
110
- if (!bashPath) {
107
+ const useCmd = isWindows();
108
+ const shellPath = useCmd ? (process.env.COMSPEC || "cmd.exe") : findGitBash();
109
+ if (!shellPath) {
111
110
  this.status = 'stopped';
112
- this.appendLog("\x1b[1;31m[System Error] Git Bash not found. Cannot spawn terminal.\x1b[0m\r\n");
111
+ this.appendLog("\x1b[1;31m[System Error] Shell not found. Cannot spawn terminal.\x1b[0m\r\n");
113
112
  return;
114
113
  }
114
+ const shellArgs = useCmd ? ["/c"] : ["-c"];
115
115
 
116
116
  try {
117
117
  if (!this.ptyModule) {
@@ -214,7 +214,7 @@ export class AiderRunner extends AgentRunner {
214
214
 
215
215
  const dockerCmd = `docker run -it --name neural-loom-session-${this.id} --rm ${mountsStr} -w /workspace ${envFlagsStr} paulgauthier/aider ${modelFlag} ${extraFlags} --no-fancy-input ${loadAllFilesFlag}`.trim();
216
216
 
217
- this.ptyProcess = this.ptyModule!.spawn(bashPath, ["-c", dockerCmd], {
217
+ this.ptyProcess = this.ptyModule!.spawn(shellPath, [...shellArgs, dockerCmd], {
218
218
  name: "xterm-256color",
219
219
  cols: 120,
220
220
  rows: 30,
@@ -245,7 +245,7 @@ export class AiderRunner extends AgentRunner {
245
245
  this.appendLog("[System] Spawning local Aider CLI...\r\n");
246
246
  const localCmd = `aider ${modelFlag} ${extraFlags} --no-fancy-input ${loadAllFilesFlag}`.trim();
247
247
 
248
- this.ptyProcess = this.ptyModule!.spawn(bashPath, ["-c", localCmd], {
248
+ this.ptyProcess = this.ptyModule!.spawn(shellPath, [...shellArgs, localCmd], {
249
249
  name: "xterm-256color",
250
250
  cols: 120,
251
251
  rows: 30,