@cryptiklemur/lattice 0.0.0 → 1.2.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 (53) hide show
  1. package/.github/workflows/release.yml +4 -4
  2. package/.releaserc.json +2 -1
  3. package/client/src/components/auth/PassphrasePrompt.tsx +70 -70
  4. package/client/src/components/mesh/NodeBadge.tsx +24 -24
  5. package/client/src/components/mesh/PairingDialog.tsx +281 -281
  6. package/client/src/components/panels/FileBrowser.tsx +241 -241
  7. package/client/src/components/panels/StickyNotes.tsx +187 -187
  8. package/client/src/components/project-settings/ProjectMemory.tsx +471 -0
  9. package/client/src/components/project-settings/ProjectSettingsView.tsx +6 -0
  10. package/client/src/components/settings/Appearance.tsx +151 -151
  11. package/client/src/components/settings/MeshStatus.tsx +145 -145
  12. package/client/src/components/settings/SettingsView.tsx +57 -57
  13. package/client/src/components/setup/SetupWizard.tsx +750 -750
  14. package/client/src/components/sidebar/AddProjectModal.tsx +432 -0
  15. package/client/src/components/sidebar/ProjectRail.tsx +8 -4
  16. package/client/src/components/sidebar/SettingsSidebar.tsx +2 -1
  17. package/client/src/components/ui/ErrorBoundary.tsx +56 -56
  18. package/client/src/hooks/useSidebar.ts +16 -0
  19. package/client/src/router.tsx +453 -391
  20. package/client/src/stores/sidebar.ts +28 -0
  21. package/client/vite.config.ts +20 -20
  22. package/package.json +1 -1
  23. package/server/src/daemon.ts +1 -0
  24. package/server/src/handlers/chat.ts +194 -194
  25. package/server/src/handlers/fs.ts +159 -0
  26. package/server/src/handlers/memory.ts +179 -0
  27. package/server/src/handlers/settings.ts +114 -109
  28. package/shared/src/messages.ts +97 -2
  29. package/shared/src/project-settings.ts +1 -1
  30. package/themes/amoled.json +20 -20
  31. package/themes/ayu-light.json +9 -9
  32. package/themes/catppuccin-latte.json +9 -9
  33. package/themes/catppuccin-mocha.json +9 -9
  34. package/themes/clay-light.json +10 -10
  35. package/themes/clay.json +10 -10
  36. package/themes/dracula.json +9 -9
  37. package/themes/everforest-light.json +9 -9
  38. package/themes/everforest.json +9 -9
  39. package/themes/github-light.json +9 -9
  40. package/themes/gruvbox-dark.json +9 -9
  41. package/themes/gruvbox-light.json +9 -9
  42. package/themes/monokai.json +9 -9
  43. package/themes/nord-light.json +9 -9
  44. package/themes/nord.json +9 -9
  45. package/themes/one-dark.json +9 -9
  46. package/themes/one-light.json +9 -9
  47. package/themes/rose-pine-dawn.json +9 -9
  48. package/themes/rose-pine.json +9 -9
  49. package/themes/solarized-dark.json +9 -9
  50. package/themes/solarized-light.json +9 -9
  51. package/themes/tokyo-night-light.json +9 -9
  52. package/themes/tokyo-night.json +9 -9
  53. package/.serena/project.yml +0 -138
@@ -1,241 +1,241 @@
1
- import { useCallback, useEffect, useRef, useState } from "react";
2
- import { ChevronRight, FileIcon } from "lucide-react";
3
- import type { FileEntry, FsListResultMessage, FsReadResultMessage } from "@lattice/shared";
4
- import { useWebSocket } from "../../hooks/useWebSocket";
5
- import type { ServerMessage } from "@lattice/shared";
6
-
7
- interface TreeNode {
8
- entry: FileEntry;
9
- children: TreeNode[] | null;
10
- expanded: boolean;
11
- }
12
-
13
- function buildNodes(entries: FileEntry[]): TreeNode[] {
14
- return entries.map(function (entry) {
15
- return { entry, children: null, expanded: false };
16
- });
17
- }
18
-
19
- interface FileTreeItemProps {
20
- node: TreeNode;
21
- depth: number;
22
- selectedPath: string | null;
23
- onToggle: (path: string) => void;
24
- onSelect: (path: string) => void;
25
- }
26
-
27
- function FileTreeItem(props: FileTreeItemProps) {
28
- var { node, depth, selectedPath, onToggle, onSelect } = props;
29
- var isSelected = selectedPath === node.entry.path;
30
- var isDir = node.entry.isDirectory;
31
-
32
- return (
33
- <div>
34
- <div
35
- onClick={function () {
36
- if (isDir) {
37
- onToggle(node.entry.path);
38
- } else {
39
- onSelect(node.entry.path);
40
- }
41
- }}
42
- className={
43
- "flex items-center gap-1.5 py-[3px] pr-2 cursor-pointer text-[13px] rounded select-none " +
44
- (isSelected ? "bg-base-300 text-primary" : "text-base-content hover:bg-base-200")
45
- }
46
- style={{ paddingLeft: (8 + depth * 16) + "px" }}
47
- >
48
- {isDir ? (
49
- <ChevronRight
50
- size={12}
51
- className="flex-shrink-0 text-base-content/40 transition-transform duration-[120ms]"
52
- style={{ transform: node.expanded ? "rotate(90deg)" : "none" }}
53
- />
54
- ) : (
55
- <FileIcon
56
- size={12}
57
- className="flex-shrink-0 text-base-content/40"
58
- />
59
- )}
60
- <span
61
- className={
62
- "truncate " +
63
- (isDir ? "text-info" : "text-base-content")
64
- }
65
- >
66
- {node.entry.name}
67
- </span>
68
- </div>
69
- {isDir && node.expanded && node.children && (
70
- <div>
71
- {node.children.map(function (child) {
72
- return (
73
- <FileTreeItem
74
- key={child.entry.path}
75
- node={child}
76
- depth={depth + 1}
77
- selectedPath={selectedPath}
78
- onToggle={onToggle}
79
- onSelect={onSelect}
80
- />
81
- );
82
- })}
83
- </div>
84
- )}
85
- </div>
86
- );
87
- }
88
-
89
- export function FileBrowser() {
90
- var { send, subscribe, unsubscribe } = useWebSocket();
91
- var [rootNodes, setRootNodes] = useState<TreeNode[]>([]);
92
- var [selectedPath, setSelectedPath] = useState<string | null>(null);
93
- var [fileContent, setFileContent] = useState<string | null>(null);
94
- var [loadingContent, setLoadingContent] = useState(false);
95
- var nodesRef = useRef<TreeNode[]>([]);
96
-
97
- nodesRef.current = rootNodes;
98
-
99
- var handleListResult = useCallback(function (msg: ServerMessage) {
100
- var listMsg = msg as FsListResultMessage;
101
- var newNodes = buildNodes(listMsg.entries);
102
-
103
- if (listMsg.path === "" || listMsg.path === ".") {
104
- setRootNodes(newNodes);
105
- return;
106
- }
107
-
108
- function updateNodes(nodes: TreeNode[]): TreeNode[] {
109
- return nodes.map(function (node) {
110
- if (node.entry.path === listMsg.path) {
111
- return Object.assign({}, node, { children: newNodes, expanded: true });
112
- }
113
- if (node.children) {
114
- return Object.assign({}, node, { children: updateNodes(node.children) });
115
- }
116
- return node;
117
- });
118
- }
119
-
120
- setRootNodes(function (prev) {
121
- return updateNodes(prev);
122
- });
123
- }, []);
124
-
125
- var handleReadResult = useCallback(function (msg: ServerMessage) {
126
- var readMsg = msg as FsReadResultMessage;
127
- setFileContent(readMsg.content);
128
- setLoadingContent(false);
129
- }, []);
130
-
131
- var handleFsChanged = useCallback(function (msg: ServerMessage) {
132
- var changedPath = (msg as { path: string }).path;
133
- if (changedPath === selectedPath) {
134
- send({ type: "fs:read", path: changedPath });
135
- }
136
- }, [selectedPath, send]);
137
-
138
- useEffect(function () {
139
- subscribe("fs:list_result", handleListResult);
140
- subscribe("fs:read_result", handleReadResult);
141
- subscribe("fs:changed", handleFsChanged);
142
-
143
- send({ type: "fs:list", path: "." });
144
-
145
- return function () {
146
- unsubscribe("fs:list_result", handleListResult);
147
- unsubscribe("fs:read_result", handleReadResult);
148
- unsubscribe("fs:changed", handleFsChanged);
149
- };
150
- }, [handleListResult, handleReadResult, handleFsChanged, send, subscribe, unsubscribe]);
151
-
152
- function handleToggle(path: string) {
153
- function findAndToggle(nodes: TreeNode[]): TreeNode[] {
154
- return nodes.map(function (node) {
155
- if (node.entry.path === path) {
156
- if (!node.expanded && !node.children) {
157
- send({ type: "fs:list", path });
158
- return Object.assign({}, node, { expanded: true });
159
- }
160
- return Object.assign({}, node, { expanded: !node.expanded });
161
- }
162
- if (node.children) {
163
- return Object.assign({}, node, { children: findAndToggle(node.children) });
164
- }
165
- return node;
166
- });
167
- }
168
- setRootNodes(function (prev) {
169
- return findAndToggle(prev);
170
- });
171
- }
172
-
173
- function handleSelect(path: string) {
174
- setSelectedPath(path);
175
- setFileContent(null);
176
- setLoadingContent(true);
177
- send({ type: "fs:read", path });
178
- }
179
-
180
- return (
181
- <div className="flex h-full w-full overflow-hidden bg-base-100">
182
- <div className="w-[220px] flex-shrink-0 border-r border-base-300 overflow-y-auto p-2">
183
- <div className="text-[11px] font-semibold tracking-[0.06em] uppercase text-base-content/40 px-2 pb-2 pt-1">
184
- Files
185
- </div>
186
- {rootNodes.length === 0 ? (
187
- <div className="px-2 py-3 text-[12px] text-base-content/40">
188
- Loading...
189
- </div>
190
- ) : (
191
- rootNodes.map(function (node) {
192
- return (
193
- <FileTreeItem
194
- key={node.entry.path}
195
- node={node}
196
- depth={0}
197
- selectedPath={selectedPath}
198
- onToggle={handleToggle}
199
- onSelect={handleSelect}
200
- />
201
- );
202
- })
203
- )}
204
- </div>
205
-
206
- <div className="flex-1 flex flex-col overflow-hidden">
207
- {selectedPath && (
208
- <div className="h-9 flex-shrink-0 flex items-center px-4 border-b border-base-300 bg-base-200 text-[12px] text-base-content/60 font-mono overflow-hidden">
209
- <span className="truncate">{selectedPath}</span>
210
- </div>
211
- )}
212
-
213
- <div className="flex-1 overflow-auto">
214
- {!selectedPath && (
215
- <div className="h-full flex items-center justify-center text-base-content/40 text-[13px]">
216
- Select a file to view its contents
217
- </div>
218
- )}
219
-
220
- {selectedPath && loadingContent && (
221
- <div className="h-full flex items-center justify-center text-base-content/40 text-[13px]">
222
- Loading...
223
- </div>
224
- )}
225
-
226
- {selectedPath && !loadingContent && fileContent !== null && (
227
- <pre className="m-0 p-4 text-[13px] font-mono text-base-content leading-relaxed whitespace-pre-wrap break-words">
228
- {fileContent}
229
- </pre>
230
- )}
231
-
232
- {selectedPath && !loadingContent && fileContent === null && (
233
- <div className="h-full flex items-center justify-center text-base-content/40 text-[13px]">
234
- Cannot display this file (binary or too large)
235
- </div>
236
- )}
237
- </div>
238
- </div>
239
- </div>
240
- );
241
- }
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
+ import { ChevronRight, FileIcon } from "lucide-react";
3
+ import type { FileEntry, FsListResultMessage, FsReadResultMessage } from "@lattice/shared";
4
+ import { useWebSocket } from "../../hooks/useWebSocket";
5
+ import type { ServerMessage } from "@lattice/shared";
6
+
7
+ interface TreeNode {
8
+ entry: FileEntry;
9
+ children: TreeNode[] | null;
10
+ expanded: boolean;
11
+ }
12
+
13
+ function buildNodes(entries: FileEntry[]): TreeNode[] {
14
+ return entries.map(function (entry) {
15
+ return { entry, children: null, expanded: false };
16
+ });
17
+ }
18
+
19
+ interface FileTreeItemProps {
20
+ node: TreeNode;
21
+ depth: number;
22
+ selectedPath: string | null;
23
+ onToggle: (path: string) => void;
24
+ onSelect: (path: string) => void;
25
+ }
26
+
27
+ function FileTreeItem(props: FileTreeItemProps) {
28
+ var { node, depth, selectedPath, onToggle, onSelect } = props;
29
+ var isSelected = selectedPath === node.entry.path;
30
+ var isDir = node.entry.isDirectory;
31
+
32
+ return (
33
+ <div>
34
+ <div
35
+ onClick={function () {
36
+ if (isDir) {
37
+ onToggle(node.entry.path);
38
+ } else {
39
+ onSelect(node.entry.path);
40
+ }
41
+ }}
42
+ className={
43
+ "flex items-center gap-1.5 py-[3px] pr-2 cursor-pointer text-[13px] rounded select-none " +
44
+ (isSelected ? "bg-base-300 text-primary" : "text-base-content hover:bg-base-200")
45
+ }
46
+ style={{ paddingLeft: (8 + depth * 16) + "px" }}
47
+ >
48
+ {isDir ? (
49
+ <ChevronRight
50
+ size={12}
51
+ className="flex-shrink-0 text-base-content/40 transition-transform duration-[120ms]"
52
+ style={{ transform: node.expanded ? "rotate(90deg)" : "none" }}
53
+ />
54
+ ) : (
55
+ <FileIcon
56
+ size={12}
57
+ className="flex-shrink-0 text-base-content/40"
58
+ />
59
+ )}
60
+ <span
61
+ className={
62
+ "truncate " +
63
+ (isDir ? "text-info" : "text-base-content")
64
+ }
65
+ >
66
+ {node.entry.name}
67
+ </span>
68
+ </div>
69
+ {isDir && node.expanded && node.children && (
70
+ <div>
71
+ {node.children.map(function (child) {
72
+ return (
73
+ <FileTreeItem
74
+ key={child.entry.path}
75
+ node={child}
76
+ depth={depth + 1}
77
+ selectedPath={selectedPath}
78
+ onToggle={onToggle}
79
+ onSelect={onSelect}
80
+ />
81
+ );
82
+ })}
83
+ </div>
84
+ )}
85
+ </div>
86
+ );
87
+ }
88
+
89
+ export function FileBrowser() {
90
+ var { send, subscribe, unsubscribe } = useWebSocket();
91
+ var [rootNodes, setRootNodes] = useState<TreeNode[]>([]);
92
+ var [selectedPath, setSelectedPath] = useState<string | null>(null);
93
+ var [fileContent, setFileContent] = useState<string | null>(null);
94
+ var [loadingContent, setLoadingContent] = useState(false);
95
+ var nodesRef = useRef<TreeNode[]>([]);
96
+
97
+ nodesRef.current = rootNodes;
98
+
99
+ var handleListResult = useCallback(function (msg: ServerMessage) {
100
+ var listMsg = msg as FsListResultMessage;
101
+ var newNodes = buildNodes(listMsg.entries);
102
+
103
+ if (listMsg.path === "" || listMsg.path === ".") {
104
+ setRootNodes(newNodes);
105
+ return;
106
+ }
107
+
108
+ function updateNodes(nodes: TreeNode[]): TreeNode[] {
109
+ return nodes.map(function (node) {
110
+ if (node.entry.path === listMsg.path) {
111
+ return Object.assign({}, node, { children: newNodes, expanded: true });
112
+ }
113
+ if (node.children) {
114
+ return Object.assign({}, node, { children: updateNodes(node.children) });
115
+ }
116
+ return node;
117
+ });
118
+ }
119
+
120
+ setRootNodes(function (prev) {
121
+ return updateNodes(prev);
122
+ });
123
+ }, []);
124
+
125
+ var handleReadResult = useCallback(function (msg: ServerMessage) {
126
+ var readMsg = msg as FsReadResultMessage;
127
+ setFileContent(readMsg.content);
128
+ setLoadingContent(false);
129
+ }, []);
130
+
131
+ var handleFsChanged = useCallback(function (msg: ServerMessage) {
132
+ var changedPath = (msg as { path: string }).path;
133
+ if (changedPath === selectedPath) {
134
+ send({ type: "fs:read", path: changedPath });
135
+ }
136
+ }, [selectedPath, send]);
137
+
138
+ useEffect(function () {
139
+ subscribe("fs:list_result", handleListResult);
140
+ subscribe("fs:read_result", handleReadResult);
141
+ subscribe("fs:changed", handleFsChanged);
142
+
143
+ send({ type: "fs:list", path: "." });
144
+
145
+ return function () {
146
+ unsubscribe("fs:list_result", handleListResult);
147
+ unsubscribe("fs:read_result", handleReadResult);
148
+ unsubscribe("fs:changed", handleFsChanged);
149
+ };
150
+ }, [handleListResult, handleReadResult, handleFsChanged, send, subscribe, unsubscribe]);
151
+
152
+ function handleToggle(path: string) {
153
+ function findAndToggle(nodes: TreeNode[]): TreeNode[] {
154
+ return nodes.map(function (node) {
155
+ if (node.entry.path === path) {
156
+ if (!node.expanded && !node.children) {
157
+ send({ type: "fs:list", path });
158
+ return Object.assign({}, node, { expanded: true });
159
+ }
160
+ return Object.assign({}, node, { expanded: !node.expanded });
161
+ }
162
+ if (node.children) {
163
+ return Object.assign({}, node, { children: findAndToggle(node.children) });
164
+ }
165
+ return node;
166
+ });
167
+ }
168
+ setRootNodes(function (prev) {
169
+ return findAndToggle(prev);
170
+ });
171
+ }
172
+
173
+ function handleSelect(path: string) {
174
+ setSelectedPath(path);
175
+ setFileContent(null);
176
+ setLoadingContent(true);
177
+ send({ type: "fs:read", path });
178
+ }
179
+
180
+ return (
181
+ <div className="flex h-full w-full overflow-hidden bg-base-100">
182
+ <div className="w-[220px] flex-shrink-0 border-r border-base-300 overflow-y-auto p-2">
183
+ <div className="text-[11px] font-semibold tracking-[0.06em] uppercase text-base-content/40 px-2 pb-2 pt-1">
184
+ Files
185
+ </div>
186
+ {rootNodes.length === 0 ? (
187
+ <div className="px-2 py-3 text-[12px] text-base-content/40">
188
+ Loading...
189
+ </div>
190
+ ) : (
191
+ rootNodes.map(function (node) {
192
+ return (
193
+ <FileTreeItem
194
+ key={node.entry.path}
195
+ node={node}
196
+ depth={0}
197
+ selectedPath={selectedPath}
198
+ onToggle={handleToggle}
199
+ onSelect={handleSelect}
200
+ />
201
+ );
202
+ })
203
+ )}
204
+ </div>
205
+
206
+ <div className="flex-1 flex flex-col overflow-hidden">
207
+ {selectedPath && (
208
+ <div className="h-9 flex-shrink-0 flex items-center px-4 border-b border-base-300 bg-base-200 text-[12px] text-base-content/60 font-mono overflow-hidden">
209
+ <span className="truncate">{selectedPath}</span>
210
+ </div>
211
+ )}
212
+
213
+ <div className="flex-1 overflow-auto">
214
+ {!selectedPath && (
215
+ <div className="h-full flex items-center justify-center text-base-content/40 text-[13px]">
216
+ Select a file to view its contents
217
+ </div>
218
+ )}
219
+
220
+ {selectedPath && loadingContent && (
221
+ <div className="h-full flex items-center justify-center text-base-content/40 text-[13px]">
222
+ Loading...
223
+ </div>
224
+ )}
225
+
226
+ {selectedPath && !loadingContent && fileContent !== null && (
227
+ <pre className="m-0 p-4 text-[13px] font-mono text-base-content leading-relaxed whitespace-pre-wrap break-words">
228
+ {fileContent}
229
+ </pre>
230
+ )}
231
+
232
+ {selectedPath && !loadingContent && fileContent === null && (
233
+ <div className="h-full flex items-center justify-center text-base-content/40 text-[13px]">
234
+ Cannot display this file (binary or too large)
235
+ </div>
236
+ )}
237
+ </div>
238
+ </div>
239
+ </div>
240
+ );
241
+ }