@cryptiklemur/lattice 1.2.0 → 1.4.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.
Files changed (95) hide show
  1. package/.serena/project.yml +138 -0
  2. package/bun.lock +705 -2
  3. package/client/index.html +1 -13
  4. package/client/package.json +6 -1
  5. package/client/src/App.tsx +2 -0
  6. package/client/src/commands.ts +36 -0
  7. package/client/src/components/chat/AttachmentChips.tsx +116 -0
  8. package/client/src/components/chat/ChatInput.tsx +250 -73
  9. package/client/src/components/chat/ChatView.tsx +242 -10
  10. package/client/src/components/chat/CommandPalette.tsx +162 -0
  11. package/client/src/components/chat/Message.tsx +23 -2
  12. package/client/src/components/chat/PromptQuestion.tsx +271 -0
  13. package/client/src/components/chat/TodoCard.tsx +57 -0
  14. package/client/src/components/chat/ToolResultRenderer.tsx +2 -1
  15. package/client/src/components/chat/VoiceRecorder.tsx +85 -0
  16. package/client/src/components/project-settings/ProjectClaude.tsx +14 -0
  17. package/client/src/components/project-settings/ProjectMemory.tsx +12 -2
  18. package/client/src/components/project-settings/ProjectNotifications.tsx +48 -0
  19. package/client/src/components/project-settings/ProjectRules.tsx +10 -1
  20. package/client/src/components/project-settings/ProjectSettingsView.tsx +6 -0
  21. package/client/src/components/settings/Appearance.tsx +1 -0
  22. package/client/src/components/settings/ClaudeSettings.tsx +24 -0
  23. package/client/src/components/settings/Editor.tsx +123 -0
  24. package/client/src/components/settings/GlobalMcp.tsx +10 -1
  25. package/client/src/components/settings/GlobalMemory.tsx +19 -0
  26. package/client/src/components/settings/GlobalRules.tsx +149 -0
  27. package/client/src/components/settings/GlobalSkills.tsx +10 -0
  28. package/client/src/components/settings/Notifications.tsx +88 -0
  29. package/client/src/components/settings/SettingsView.tsx +12 -0
  30. package/client/src/components/settings/skill-shared.tsx +2 -1
  31. package/client/src/components/setup/SetupWizard.tsx +1 -1
  32. package/client/src/components/sidebar/AddProjectModal.tsx +3 -2
  33. package/client/src/components/sidebar/NodeSettingsModal.tsx +23 -1
  34. package/client/src/components/sidebar/ProjectDropdown.tsx +176 -27
  35. package/client/src/components/sidebar/SettingsSidebar.tsx +11 -1
  36. package/client/src/components/sidebar/Sidebar.tsx +35 -2
  37. package/client/src/components/sidebar/UserIsland.tsx +18 -7
  38. package/client/src/components/ui/UpdatePrompt.tsx +47 -0
  39. package/client/src/components/workspace/FileBrowser.tsx +174 -0
  40. package/client/src/components/workspace/FileTree.tsx +129 -0
  41. package/client/src/components/workspace/FileViewer.tsx +211 -0
  42. package/client/src/components/workspace/NoteCard.tsx +119 -0
  43. package/client/src/components/workspace/NotesView.tsx +102 -0
  44. package/client/src/components/workspace/ScheduledTasksView.tsx +117 -0
  45. package/client/src/components/workspace/SplitPane.tsx +81 -0
  46. package/client/src/components/workspace/TabBar.tsx +185 -0
  47. package/client/src/components/workspace/TaskCard.tsx +158 -0
  48. package/client/src/components/workspace/TaskEditModal.tsx +114 -0
  49. package/client/src/components/{panels/Terminal.tsx → workspace/TerminalInstance.tsx} +50 -7
  50. package/client/src/components/workspace/TerminalView.tsx +110 -0
  51. package/client/src/components/workspace/WorkspaceView.tsx +116 -0
  52. package/client/src/hooks/useAttachments.ts +280 -0
  53. package/client/src/hooks/useEditorConfig.ts +28 -0
  54. package/client/src/hooks/useIdleDetection.ts +44 -0
  55. package/client/src/hooks/useInstallPrompt.ts +53 -0
  56. package/client/src/hooks/useNotifications.ts +54 -0
  57. package/client/src/hooks/useOnline.ts +6 -0
  58. package/client/src/hooks/useSession.ts +110 -4
  59. package/client/src/hooks/useSpinnerVerb.ts +36 -0
  60. package/client/src/hooks/useSwipeDrawer.ts +275 -0
  61. package/client/src/hooks/useVoiceRecorder.ts +123 -0
  62. package/client/src/hooks/useWorkspace.ts +48 -0
  63. package/client/src/providers/WebSocketProvider.tsx +18 -0
  64. package/client/src/router.tsx +48 -20
  65. package/client/src/stores/session.ts +136 -0
  66. package/client/src/stores/sidebar.ts +3 -2
  67. package/client/src/stores/workspace.ts +254 -0
  68. package/client/src/styles/global.css +131 -0
  69. package/client/src/utils/editorUrl.ts +62 -0
  70. package/client/vite.config.ts +53 -1
  71. package/package.json +1 -1
  72. package/server/src/daemon.ts +11 -1
  73. package/server/src/features/scheduler.ts +23 -0
  74. package/server/src/features/sticky-notes.ts +5 -3
  75. package/server/src/handlers/attachment.ts +172 -0
  76. package/server/src/handlers/chat.ts +43 -2
  77. package/server/src/handlers/editor.ts +40 -0
  78. package/server/src/handlers/fs.ts +10 -2
  79. package/server/src/handlers/memory.ts +3 -0
  80. package/server/src/handlers/notes.ts +4 -2
  81. package/server/src/handlers/scheduler.ts +18 -1
  82. package/server/src/handlers/session.ts +14 -8
  83. package/server/src/handlers/settings.ts +37 -2
  84. package/server/src/handlers/terminal.ts +13 -6
  85. package/server/src/project/pty-worker.cjs +83 -0
  86. package/server/src/project/sdk-bridge.ts +266 -11
  87. package/server/src/project/terminal.ts +78 -34
  88. package/shared/src/messages.ts +145 -4
  89. package/shared/src/models.ts +27 -1
  90. package/shared/src/project-settings.ts +1 -1
  91. package/tp.js +19 -0
  92. package/client/public/manifest.json +0 -24
  93. package/client/public/sw.js +0 -61
  94. package/client/src/components/panels/FileBrowser.tsx +0 -241
  95. package/client/src/components/panels/StickyNotes.tsx +0 -187
@@ -0,0 +1,123 @@
1
+ import { useEffect, useRef, useState } from "react";
2
+ import { useWebSocket } from "../../hooks/useWebSocket";
3
+ import { useSaveState } from "../../hooks/useSaveState";
4
+ import { SaveFooter } from "../ui/SaveFooter";
5
+ import type { ServerMessage, SettingsDataMessage, SettingsUpdateMessage } from "@lattice/shared";
6
+
7
+ var IDE_OPTIONS = [
8
+ { id: "vscode", label: "VS Code" },
9
+ { id: "vscode-insiders", label: "VS Code Insiders" },
10
+ { id: "cursor", label: "Cursor" },
11
+ { id: "webstorm", label: "WebStorm" },
12
+ { id: "intellij", label: "IntelliJ IDEA" },
13
+ { id: "pycharm", label: "PyCharm" },
14
+ { id: "goland", label: "GoLand" },
15
+ { id: "notepad++", label: "Notepad++" },
16
+ { id: "sublime", label: "Sublime Text" },
17
+ { id: "custom", label: "Custom" },
18
+ ];
19
+
20
+ export function Editor() {
21
+ var { send, subscribe, unsubscribe } = useWebSocket();
22
+ var [ideType, setIdeType] = useState("vscode");
23
+ var [customCommand, setCustomCommand] = useState("");
24
+ var save = useSaveState();
25
+ var saveRef = useRef(save);
26
+ saveRef.current = save;
27
+
28
+ useEffect(function () {
29
+ function handleMessage(msg: ServerMessage) {
30
+ if (msg.type !== "settings:data") return;
31
+ var data = msg as SettingsDataMessage;
32
+ var cfg = data.config as unknown as Record<string, unknown>;
33
+ var editor = cfg.editor as { type?: string; customCommand?: string } | undefined;
34
+
35
+ var newType = editor?.type ?? "vscode";
36
+ var newCustomCommand = editor?.customCommand ?? "";
37
+
38
+ if (saveRef.current.saving) {
39
+ saveRef.current.confirmSave();
40
+ } else {
41
+ setIdeType(newType);
42
+ setCustomCommand(newCustomCommand);
43
+ saveRef.current.resetFromServer();
44
+ }
45
+ }
46
+
47
+ subscribe("settings:data", handleMessage);
48
+ send({ type: "settings:get" });
49
+
50
+ return function () {
51
+ unsubscribe("settings:data", handleMessage);
52
+ };
53
+ }, []);
54
+
55
+ function handleTypeChange(value: string) {
56
+ setIdeType(value);
57
+ save.markDirty();
58
+ }
59
+
60
+ function handleCustomCommandChange(value: string) {
61
+ setCustomCommand(value);
62
+ save.markDirty();
63
+ }
64
+
65
+ function handleSave() {
66
+ save.startSave();
67
+ var editorSettings: { type: string; customCommand?: string } = { type: ideType };
68
+ if (ideType === "custom") {
69
+ editorSettings.customCommand = customCommand;
70
+ }
71
+ var updateMsg: SettingsUpdateMessage = {
72
+ type: "settings:update",
73
+ settings: { editor: editorSettings } as SettingsUpdateMessage["settings"],
74
+ };
75
+ send(updateMsg);
76
+ }
77
+
78
+ return (
79
+ <div className="py-2">
80
+ <div className="mb-5">
81
+ <label htmlFor="editor-ide-type" className="block text-[12px] font-semibold text-base-content/40 mb-1">IDE</label>
82
+ <p className="text-[11px] text-base-content/30 mb-2">Choose your preferred editor for opening files from Lattice.</p>
83
+ <p className="text-[11px] text-base-content/20 mb-2">Files open via your editor's URL scheme handler.</p>
84
+ <select
85
+ id="editor-ide-type"
86
+ value={ideType}
87
+ onChange={function (e) { handleTypeChange(e.target.value); }}
88
+ className="w-full h-9 px-3 bg-base-300 border border-base-content/15 rounded-xl text-base-content text-[13px] focus:border-primary focus-visible:outline-none transition-colors duration-[120ms]"
89
+ >
90
+ {IDE_OPTIONS.map(function (opt) {
91
+ return (
92
+ <option key={opt.id} value={opt.id} className="bg-base-300">
93
+ {opt.label}
94
+ </option>
95
+ );
96
+ })}
97
+ </select>
98
+ </div>
99
+
100
+ {ideType === "custom" && (
101
+ <div className="mb-5">
102
+ <label htmlFor="editor-custom-command" className="block text-[12px] font-semibold text-base-content/40 mb-1">Shell command</label>
103
+ <p className="text-[11px] text-base-content/30 mb-2">Use {"{file}"} for the file path and {"{line}"} for the line number.</p>
104
+ <input
105
+ id="editor-custom-command"
106
+ type="text"
107
+ value={customCommand}
108
+ onChange={function (e) { handleCustomCommandChange(e.target.value); }}
109
+ placeholder="vim +{line} {file}"
110
+ className="w-full h-9 px-3 bg-base-300 border border-base-content/15 rounded-xl text-base-content font-mono text-[13px] focus:border-primary focus-visible:outline-none transition-colors duration-[120ms]"
111
+ />
112
+ </div>
113
+ )}
114
+
115
+ <SaveFooter
116
+ dirty={save.dirty}
117
+ saving={save.saving}
118
+ saveState={save.saveState}
119
+ onSave={handleSave}
120
+ />
121
+ </div>
122
+ );
123
+ }
@@ -1,5 +1,5 @@
1
1
  import { useState, useEffect } from "react";
2
- import { Plus, Pencil, Trash2 } from "lucide-react";
2
+ import { Plus, Pencil, Trash2, ExternalLink } from "lucide-react";
3
3
  import { useSaveState } from "../../hooks/useSaveState";
4
4
  import { SaveFooter } from "../ui/SaveFooter";
5
5
  import { useWebSocket } from "../../hooks/useWebSocket";
@@ -110,6 +110,15 @@ export function GlobalMcp() {
110
110
 
111
111
  return (
112
112
  <div className="py-2">
113
+ <a
114
+ href="https://docs.anthropic.com/en/docs/claude-code/mcp-servers"
115
+ target="_blank"
116
+ rel="noopener noreferrer"
117
+ className="text-[11px] text-base-content/30 hover:text-primary/70 flex items-center gap-1 mb-4 transition-colors"
118
+ >
119
+ <ExternalLink size={11} />
120
+ Claude Code docs
121
+ </a>
113
122
  {entries.length === 0 && !adding && (
114
123
  <div className="py-4 text-center text-[13px] text-base-content/30 mb-3">
115
124
  No global MCP servers configured.
@@ -0,0 +1,19 @@
1
+ import { ExternalLink } from "lucide-react";
2
+ import { ProjectMemory } from "../project-settings/ProjectMemory";
3
+
4
+ export function GlobalMemory() {
5
+ return (
6
+ <div>
7
+ <a
8
+ href="https://docs.anthropic.com/en/docs/claude-code/memory"
9
+ target="_blank"
10
+ rel="noopener noreferrer"
11
+ className="text-[11px] text-base-content/30 hover:text-primary/70 flex items-center gap-1 mb-4 transition-colors"
12
+ >
13
+ <ExternalLink size={11} />
14
+ Claude Code docs
15
+ </a>
16
+ <ProjectMemory projectSlug="__global__" />
17
+ </div>
18
+ );
19
+ }
@@ -0,0 +1,149 @@
1
+ import { useState, useEffect } from "react";
2
+ import { ChevronDown, ChevronRight, ExternalLink } from "lucide-react";
3
+ import { useWebSocket } from "../../hooks/useWebSocket";
4
+ import type { ServerMessage, SettingsDataMessage } from "@lattice/shared";
5
+
6
+ interface RuleEntry {
7
+ filename: string;
8
+ content: string;
9
+ }
10
+
11
+ export function GlobalRules() {
12
+ var { send, subscribe, unsubscribe } = useWebSocket();
13
+ var [rules, setRules] = useState<RuleEntry[]>([]);
14
+ var [claudeMd, setClaudeMd] = useState("");
15
+ var [expanded, setExpanded] = useState<Set<number>>(new Set());
16
+ var [claudeMdExpanded, setClaudeMdExpanded] = useState(false);
17
+
18
+ useEffect(function () {
19
+ function handleMessage(msg: ServerMessage) {
20
+ if (msg.type !== "settings:data") return;
21
+ var data = msg as SettingsDataMessage;
22
+ var cfg = data.config as unknown as Record<string, unknown>;
23
+ setClaudeMd(cfg.claudeMd ? String(cfg.claudeMd) : "");
24
+ setRules(data.globalRules ?? []);
25
+ }
26
+
27
+ subscribe("settings:data", handleMessage);
28
+ send({ type: "settings:get" });
29
+
30
+ return function () {
31
+ unsubscribe("settings:data", handleMessage);
32
+ };
33
+ }, []);
34
+
35
+ function toggle(idx: number) {
36
+ setExpanded(function (prev) {
37
+ var next = new Set(prev);
38
+ if (next.has(idx)) {
39
+ next.delete(idx);
40
+ } else {
41
+ next.add(idx);
42
+ }
43
+ return next;
44
+ });
45
+ }
46
+
47
+ function preview(content: string): string {
48
+ var trimmed = content.trim();
49
+ if (trimmed.length <= 80) return trimmed;
50
+ return trimmed.slice(0, 80) + "...";
51
+ }
52
+
53
+ return (
54
+ <div className="py-2">
55
+ <a
56
+ href="https://docs.anthropic.com/en/docs/claude-code/memory"
57
+ target="_blank"
58
+ rel="noopener noreferrer"
59
+ className="text-[11px] text-base-content/30 hover:text-primary/70 flex items-center gap-1 mb-4 transition-colors"
60
+ >
61
+ <ExternalLink size={11} />
62
+ Claude Code docs
63
+ </a>
64
+ <div className="mb-6">
65
+ <h2 className="text-[12px] font-semibold text-base-content/40 mb-3">
66
+ Global CLAUDE.md
67
+ </h2>
68
+ {!claudeMd && (
69
+ <div className="py-4 text-center text-[13px] text-base-content/30">
70
+ No global CLAUDE.md found.
71
+ </div>
72
+ )}
73
+ {claudeMd && (
74
+ <div className="border border-base-content/10 rounded-xl overflow-hidden">
75
+ <button
76
+ onClick={function () { setClaudeMdExpanded(!claudeMdExpanded); }}
77
+ className="w-full flex items-center gap-2 px-3 py-2 bg-base-300/50 hover:bg-base-300/70 transition-colors duration-[120ms] cursor-pointer text-left"
78
+ >
79
+ {claudeMdExpanded
80
+ ? <ChevronDown size={12} className="text-base-content/40 flex-shrink-0" />
81
+ : <ChevronRight size={12} className="text-base-content/40 flex-shrink-0" />
82
+ }
83
+ <span className="font-mono text-[12px] text-base-content/40 flex-1 truncate">
84
+ ~/.claude/CLAUDE.md
85
+ </span>
86
+ </button>
87
+ {!claudeMdExpanded && (
88
+ <div className="px-3 py-1.5 text-[11px] text-base-content/30 font-mono truncate">
89
+ {preview(claudeMd)}
90
+ </div>
91
+ )}
92
+ {claudeMdExpanded && (
93
+ <pre className="px-3 py-2 text-[12px] text-base-content/40 font-mono whitespace-pre-wrap break-words bg-base-300/30 max-h-[300px] overflow-auto">
94
+ {claudeMd}
95
+ </pre>
96
+ )}
97
+ </div>
98
+ )}
99
+ </div>
100
+
101
+ <div>
102
+ <h2 className="text-[12px] font-semibold text-base-content/40 mb-3">
103
+ Global Rules
104
+ </h2>
105
+ <p className="text-[11px] text-base-content/30 mb-3">
106
+ Rules from ~/.claude/rules/ — read-only.
107
+ </p>
108
+ {rules.length === 0 && (
109
+ <div className="py-4 text-center text-[13px] text-base-content/30">
110
+ No global rules found.
111
+ </div>
112
+ )}
113
+ {rules.length > 0 && (
114
+ <div className="flex flex-col gap-1.5">
115
+ {rules.map(function (rule, idx) {
116
+ var isExpanded = expanded.has(idx);
117
+ return (
118
+ <div key={rule.filename + "-" + idx} className="border border-base-content/10 rounded-xl overflow-hidden">
119
+ <button
120
+ onClick={function () { toggle(idx); }}
121
+ className="w-full flex items-center gap-2 px-3 py-2 bg-base-300/50 hover:bg-base-300/70 transition-colors duration-[120ms] cursor-pointer text-left"
122
+ >
123
+ {isExpanded
124
+ ? <ChevronDown size={12} className="text-base-content/40 flex-shrink-0" />
125
+ : <ChevronRight size={12} className="text-base-content/40 flex-shrink-0" />
126
+ }
127
+ <span className="font-mono text-[12px] text-base-content/40 flex-1 truncate">
128
+ {rule.filename}
129
+ </span>
130
+ </button>
131
+ {!isExpanded && (
132
+ <div className="px-3 py-1.5 text-[11px] text-base-content/30 font-mono truncate">
133
+ {preview(rule.content)}
134
+ </div>
135
+ )}
136
+ {isExpanded && (
137
+ <pre className="px-3 py-2 text-[12px] text-base-content/40 font-mono whitespace-pre-wrap break-words bg-base-300/30 max-h-[300px] overflow-auto">
138
+ {rule.content}
139
+ </pre>
140
+ )}
141
+ </div>
142
+ );
143
+ })}
144
+ </div>
145
+ )}
146
+ </div>
147
+ </div>
148
+ );
149
+ }
@@ -1,4 +1,5 @@
1
1
  import { useState, useEffect } from "react";
2
+ import { ExternalLink } from "lucide-react";
2
3
  import { useWebSocket } from "../../hooks/useWebSocket";
3
4
  import { SkillMarketplace } from "./SkillMarketplace";
4
5
  import { SkillItem, SkillActions, SkillViewModal } from "./skill-shared";
@@ -81,6 +82,15 @@ export function GlobalSkills() {
81
82
 
82
83
  return (
83
84
  <div className="py-2 space-y-6">
85
+ <a
86
+ href="https://docs.anthropic.com/en/docs/claude-code/slash-commands"
87
+ target="_blank"
88
+ rel="noopener noreferrer"
89
+ className="text-[11px] text-base-content/30 hover:text-primary/70 flex items-center gap-1 mb-4 transition-colors"
90
+ >
91
+ <ExternalLink size={11} />
92
+ Claude Code docs
93
+ </a>
84
94
  <div>
85
95
  <div className="text-[12px] font-semibold text-base-content/40 mb-2">Installed Skills</div>
86
96
  {skills.length === 0 ? (
@@ -0,0 +1,88 @@
1
+ import { useState } from "react";
2
+ import { useNotificationPreference } from "../../hooks/useNotifications";
3
+
4
+ var NOTIFICATION_TYPES = [
5
+ { key: "responses", label: "Claude responses", description: "When Claude finishes a response" },
6
+ { key: "mesh", label: "Mesh node changes", description: "When nodes come online or go offline" },
7
+ { key: "connection", label: "Connection events", description: "WebSocket connect and disconnect" },
8
+ ] as const;
9
+
10
+ function loadTypePrefs(): Record<string, boolean> {
11
+ try {
12
+ var stored = localStorage.getItem("lattice-notification-types");
13
+ if (stored) return JSON.parse(stored);
14
+ } catch {}
15
+ return { responses: true, mesh: true, connection: true };
16
+ }
17
+
18
+ function saveTypePrefs(prefs: Record<string, boolean>): void {
19
+ localStorage.setItem("lattice-notification-types", JSON.stringify(prefs));
20
+ }
21
+
22
+ export function Notifications() {
23
+ var notifPref = useNotificationPreference();
24
+ var [typePrefs, setTypePrefs] = useState<Record<string, boolean>>(loadTypePrefs);
25
+
26
+ function toggleType(key: string) {
27
+ setTypePrefs(function (prev) {
28
+ var next = { ...prev, [key]: !prev[key] };
29
+ saveTypePrefs(next);
30
+ return next;
31
+ });
32
+ }
33
+
34
+ return (
35
+ <div className="py-2 space-y-6">
36
+ <div>
37
+ <h2 className="text-[12px] font-semibold text-base-content/40 mb-3">
38
+ Browser Notifications
39
+ </h2>
40
+ <div className="flex items-center justify-between py-2 px-3 bg-base-300 border border-base-content/15 rounded-xl">
41
+ <div>
42
+ <div className="text-[13px] text-base-content">Enable notifications</div>
43
+ <div className="text-[11px] text-base-content/30 mt-0.5">
44
+ {notifPref.permission === "denied"
45
+ ? "Blocked by browser — update in browser settings"
46
+ : "Get notified about events in Lattice"}
47
+ </div>
48
+ </div>
49
+ <input
50
+ type="checkbox"
51
+ className="toggle toggle-sm toggle-primary"
52
+ checked={notifPref.enabled}
53
+ onChange={notifPref.toggle}
54
+ disabled={notifPref.permission === "denied"}
55
+ />
56
+ </div>
57
+ </div>
58
+
59
+ <div>
60
+ <h2 className="text-[12px] font-semibold text-base-content/40 mb-3">
61
+ Notification Types
62
+ </h2>
63
+ <div className="flex flex-col gap-2">
64
+ {NOTIFICATION_TYPES.map(function (nt) {
65
+ return (
66
+ <div
67
+ key={nt.key}
68
+ className="flex items-center justify-between py-2 px-3 bg-base-300 border border-base-content/15 rounded-xl"
69
+ >
70
+ <div>
71
+ <div className="text-[13px] text-base-content">{nt.label}</div>
72
+ <div className="text-[11px] text-base-content/30 mt-0.5">{nt.description}</div>
73
+ </div>
74
+ <input
75
+ type="checkbox"
76
+ className="toggle toggle-sm toggle-primary"
77
+ checked={typePrefs[nt.key] !== false}
78
+ onChange={function () { toggleType(nt.key); }}
79
+ disabled={!notifPref.enabled}
80
+ />
81
+ </div>
82
+ );
83
+ })}
84
+ </div>
85
+ </div>
86
+ </div>
87
+ );
88
+ }
@@ -6,24 +6,36 @@ import { Environment } from "./Environment";
6
6
  import { MeshStatus } from "./MeshStatus";
7
7
  import { GlobalMcp } from "./GlobalMcp";
8
8
  import { GlobalSkills } from "./GlobalSkills";
9
+ import { Editor } from "./Editor";
10
+ import { GlobalRules } from "./GlobalRules";
11
+ import { GlobalMemory } from "./GlobalMemory";
12
+ import { Notifications } from "./Notifications";
9
13
  import type { SettingsSection } from "../../stores/sidebar";
10
14
 
11
15
  var SECTION_CONFIG: Record<string, { title: string }> = {
12
16
  appearance: { title: "Appearance" },
17
+ notifications: { title: "Notifications" },
13
18
  claude: { title: "Claude Settings" },
14
19
  environment: { title: "Environment" },
15
20
  mcp: { title: "MCP Servers" },
16
21
  skills: { title: "Skills" },
17
22
  nodes: { title: "Mesh Nodes" },
23
+ editor: { title: "Editor" },
24
+ rules: { title: "Rules" },
25
+ memory: { title: "Memory" },
18
26
  };
19
27
 
20
28
  function renderSection(section: SettingsSection) {
21
29
  if (section === "appearance") return <Appearance />;
30
+ if (section === "notifications") return <Notifications />;
22
31
  if (section === "claude") return <ClaudeSettings />;
23
32
  if (section === "environment") return <Environment />;
24
33
  if (section === "mcp") return <GlobalMcp />;
25
34
  if (section === "skills") return <GlobalSkills />;
26
35
  if (section === "nodes") return <MeshStatus />;
36
+ if (section === "editor") return <Editor />;
37
+ if (section === "rules") return <GlobalRules />;
38
+ if (section === "memory") return <GlobalMemory />;
27
39
  return null;
28
40
  }
29
41
 
@@ -1,6 +1,7 @@
1
1
  import { useState } from "react";
2
2
  import { Trash2, RefreshCw, X, Loader2, FileText } from "lucide-react";
3
3
  import Markdown from "react-markdown";
4
+ import remarkGfm from "remark-gfm";
4
5
  import type { SkillInfo } from "@lattice/shared";
5
6
 
6
7
  function parseFrontmatter(content: string): { meta: Record<string, string>; body: string } {
@@ -163,7 +164,7 @@ export function SkillViewModal({ path, content, onClose }: { path: string; conte
163
164
 
164
165
  <div className="px-5 py-4">
165
166
  <div className="prose prose-sm max-w-none prose-headings:text-base-content prose-headings:font-mono prose-p:text-base-content/70 prose-strong:text-base-content prose-code:text-base-content/60 prose-code:bg-base-100/50 prose-code:px-1 prose-code:py-0.5 prose-code:rounded prose-code:text-[11px] prose-pre:bg-base-100 prose-pre:text-base-content/70 prose-pre:text-[11px] prose-a:text-primary prose-li:text-base-content/70 prose-li:marker:text-base-content/30 [&>*:first-child]:mt-0 [&>*:last-child]:mb-0">
166
- <Markdown>{parsed.body}</Markdown>
167
+ <Markdown remarkPlugins={[remarkGfm]}>{parsed.body}</Markdown>
167
168
  </div>
168
169
  </div>
169
170
  </div>
@@ -95,7 +95,7 @@ export function SetupWizard(props: SetupWizardProps) {
95
95
  }
96
96
 
97
97
  function handleDone() {
98
- localStorage.setItem("lattice-setup-complete", "1");
98
+ ws.send({ type: "settings:update", settings: { setupComplete: true } });
99
99
  props.onComplete();
100
100
  }
101
101
 
@@ -281,7 +281,7 @@ export function AddProjectModal({ isOpen, onClose }: AddProjectModalProps) {
281
281
  return (
282
282
  <div className="fixed inset-0 z-[9999] flex items-center justify-center">
283
283
  <div className="absolute inset-0 bg-black/50" onClick={onClose} />
284
- <div className="relative bg-base-200 border border-base-content/15 rounded-2xl shadow-2xl w-full max-w-md mx-4 overflow-hidden">
284
+ <div className="relative bg-base-200 border border-base-content/15 rounded-2xl shadow-2xl w-full max-w-3xl mx-4 overflow-hidden">
285
285
  <div className="flex items-center justify-between px-5 py-4 border-b border-base-content/15">
286
286
  <h2 className="text-[15px] font-mono font-bold text-base-content">Add Project</h2>
287
287
  <button
@@ -357,11 +357,12 @@ export function AddProjectModal({ isOpen, onClose }: AddProjectModalProps) {
357
357
  {!path.trim() && suggestions.length > 0 && !dropdownOpen && (
358
358
  <div className="mt-2">
359
359
  <div className="text-[11px] font-semibold text-base-content/30 mb-1.5">Projects Claude has worked in</div>
360
- <div className="flex flex-col gap-1 max-h-32 overflow-y-auto">
360
+ <div className="flex flex-col gap-1 max-h-48 overflow-y-auto">
361
361
  {suggestions.map(function (s) {
362
362
  return (
363
363
  <button
364
364
  key={s.path}
365
+ title={s.path}
365
366
  onClick={function () {
366
367
  setPath(s.path + "/");
367
368
  if (!titleManuallySet) setTitle(s.name);
@@ -25,6 +25,7 @@ export function NodeSettingsModal({ isOpen, onClose }: NodeSettingsModalProps) {
25
25
  var [tls, setTls] = useState(false);
26
26
  var [debug, setDebug] = useState(false);
27
27
  var [copied, setCopied] = useState(false);
28
+ var [wsl, setWsl] = useState<boolean | "auto">("auto");
28
29
  var copyTimeout = useRef<ReturnType<typeof setTimeout> | null>(null);
29
30
 
30
31
  useEffect(function () {
@@ -43,6 +44,7 @@ export function NodeSettingsModal({ isOpen, onClose }: NodeSettingsModalProps) {
43
44
  setPort(cfg.port);
44
45
  setTls(cfg.tls);
45
46
  setDebug(cfg.debug);
47
+ setWsl(cfg.wsl ?? "auto");
46
48
  save.resetFromServer();
47
49
  }
48
50
  }
@@ -65,7 +67,7 @@ export function NodeSettingsModal({ isOpen, onClose }: NodeSettingsModalProps) {
65
67
  save.startSave();
66
68
  send({
67
69
  type: "settings:update",
68
- settings: { name, port, tls, debug },
70
+ settings: { name, port, tls, debug, wsl },
69
71
  } as any);
70
72
  }
71
73
 
@@ -169,6 +171,26 @@ export function NodeSettingsModal({ isOpen, onClose }: NodeSettingsModalProps) {
169
171
  aria-label="Enable debug mode"
170
172
  />
171
173
  </div>
174
+
175
+ <div>
176
+ <div className="text-[12px] font-semibold text-base-content/40 mb-1.5">WSL Mode</div>
177
+ <div className="text-[11px] text-base-content/30 mb-2">Convert file paths for Windows editors when running under WSL</div>
178
+ <select
179
+ value={String(wsl)}
180
+ onChange={function (e) {
181
+ var val = e.target.value;
182
+ if (val === "true") { setWsl(true); }
183
+ else if (val === "false") { setWsl(false); }
184
+ else { setWsl("auto"); }
185
+ save.markDirty();
186
+ }}
187
+ className={inputClass}
188
+ >
189
+ <option value="auto">Auto-detect</option>
190
+ <option value="true">Enabled</option>
191
+ <option value="false">Disabled</option>
192
+ </select>
193
+ </div>
172
194
  </div>
173
195
 
174
196
  <div className="px-5 py-3 border-t border-base-content/15">