@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,281 +1,281 @@
1
- import { useState, useEffect, useCallback } from "react";
2
- import { X, Copy, Check } from "lucide-react";
3
- import { useWebSocket } from "../../hooks/useWebSocket";
4
- import { useMesh } from "../../hooks/useMesh";
5
- import { clearInvite } from "../../stores/mesh";
6
- import type { ServerMessage } from "@lattice/shared";
7
-
8
- type Tab = "generate" | "enter";
9
- type PairStatus = "idle" | "connecting" | "paired" | "failed";
10
-
11
- interface PairingDialogProps {
12
- isOpen: boolean;
13
- onClose: () => void;
14
- }
15
-
16
- export function PairingDialog(props: PairingDialogProps) {
17
- var ws = useWebSocket();
18
- var mesh = useMesh();
19
- var [tab, setTab] = useState<Tab>("generate");
20
- var [pairCode, setPairCode] = useState("");
21
- var [pairStatus, setPairStatus] = useState<PairStatus>("idle");
22
- var [pairError, setPairError] = useState<string | null>(null);
23
- var [copied, setCopied] = useState(false);
24
-
25
- var handleKeyDown = useCallback(function (e: KeyboardEvent) {
26
- if (e.key === "Escape") {
27
- props.onClose();
28
- }
29
- }, [props.onClose]);
30
-
31
- useEffect(function () {
32
- if (!props.isOpen) {
33
- return;
34
- }
35
- document.addEventListener("keydown", handleKeyDown);
36
- return function () {
37
- document.removeEventListener("keydown", handleKeyDown);
38
- };
39
- }, [props.isOpen, handleKeyDown]);
40
-
41
- useEffect(function () {
42
- if (!props.isOpen) {
43
- clearInvite();
44
- setPairCode("");
45
- setPairStatus("idle");
46
- setPairError(null);
47
- setCopied(false);
48
- setTab("generate");
49
- }
50
- }, [props.isOpen]);
51
-
52
- useEffect(function () {
53
- if (pairStatus !== "connecting") {
54
- return;
55
- }
56
-
57
- function handler(msg: ServerMessage) {
58
- if (msg.type === "mesh:paired") {
59
- setPairStatus("paired");
60
- setPairError(null);
61
- }
62
- }
63
-
64
- ws.subscribe("mesh:paired", handler);
65
- return function () {
66
- ws.unsubscribe("mesh:paired", handler);
67
- };
68
- }, [ws, pairStatus]);
69
-
70
- function handleGenerateInvite() {
71
- clearInvite();
72
- mesh.generateInvite();
73
- }
74
-
75
- function handlePair() {
76
- var trimmed = pairCode.trim();
77
- if (!trimmed) {
78
- return;
79
- }
80
- setPairStatus("connecting");
81
- setPairError(null);
82
-
83
- var timeout = setTimeout(function () {
84
- setPairStatus(function (prev) {
85
- if (prev === "connecting") {
86
- setPairError("Pairing timed out. Check the code and try again.");
87
- return "failed";
88
- }
89
- return prev;
90
- });
91
- }, 30000);
92
-
93
- ws.send({ type: "mesh:pair", code: trimmed });
94
-
95
- return function () {
96
- clearTimeout(timeout);
97
- };
98
- }
99
-
100
- function handleCopyCode() {
101
- if (!mesh.inviteCode) {
102
- return;
103
- }
104
- navigator.clipboard.writeText(mesh.inviteCode).then(function () {
105
- setCopied(true);
106
- setTimeout(function () { setCopied(false); }, 2000);
107
- });
108
- }
109
-
110
- if (!props.isOpen) {
111
- return null;
112
- }
113
-
114
- return (
115
- <div
116
- role="dialog"
117
- aria-modal="true"
118
- aria-label="Pair a node"
119
- className="fixed inset-0 z-[10000] flex items-center justify-center bg-black/65 backdrop-blur-sm"
120
- onClick={props.onClose}
121
- >
122
- <div
123
- className="w-[440px] max-w-[calc(100vw-24px)] rounded-xl border border-base-300 bg-base-200 overflow-hidden shadow-2xl"
124
- onClick={function (e) { e.stopPropagation(); }}
125
- >
126
- <div className="flex items-center justify-between px-5 py-4 border-b border-base-300">
127
- <div className="text-[14px] font-semibold text-base-content">Pair a Node</div>
128
- <button
129
- onClick={props.onClose}
130
- aria-label="Close"
131
- className="btn btn-ghost btn-xs btn-square text-base-content/40 hover:text-base-content"
132
- >
133
- <X size={14} />
134
- </button>
135
- </div>
136
-
137
- <div className="flex border-b border-base-300">
138
- {(["generate", "enter"] as Tab[]).map(function (t) {
139
- var label = t === "generate" ? "Generate Invite" : "Enter Code";
140
- var isActive = tab === t;
141
- return (
142
- <button
143
- key={t}
144
- onClick={function () { setTab(t); }}
145
- className={
146
- "flex-1 px-4 py-2.5 text-[13px] cursor-pointer transition-colors duration-[120ms] border-b-2 " +
147
- (isActive
148
- ? "font-semibold text-base-content border-primary"
149
- : "font-normal text-base-content/40 border-transparent hover:text-base-content/70")
150
- }
151
- >
152
- {label}
153
- </button>
154
- );
155
- })}
156
- </div>
157
-
158
- <div className="p-5">
159
- {tab === "generate" && (
160
- <div>
161
- <div className="text-[12px] text-base-content/40 mb-4 leading-relaxed">
162
- Generate an invite code on this machine and share it with the other node.
163
- The code encodes this node&apos;s address and a one-time auth token.
164
- </div>
165
-
166
- {!mesh.inviteCode && (
167
- <button
168
- onClick={handleGenerateInvite}
169
- className="btn btn-primary btn-sm"
170
- >
171
- Generate Invite Code
172
- </button>
173
- )}
174
-
175
- {mesh.inviteCode && (
176
- <div>
177
- <div className="flex items-center gap-2 px-3.5 py-2.5 rounded bg-base-100 border border-base-300 mb-4">
178
- <code className="flex-1 font-mono text-[14px] font-semibold text-base-content tracking-[0.08em] break-all">
179
- {mesh.inviteCode}
180
- </code>
181
- <button
182
- onClick={handleCopyCode}
183
- title="Copy code"
184
- className={
185
- "btn btn-xs gap-1 flex-shrink-0 " +
186
- (copied ? "btn-success" : "btn-ghost border border-base-300")
187
- }
188
- >
189
- {copied ? <Check size={12} /> : <Copy size={12} />}
190
- {copied ? "Copied!" : "Copy"}
191
- </button>
192
- </div>
193
-
194
- {mesh.inviteQr && (
195
- <div className="flex justify-center mb-4">
196
- <img
197
- src={mesh.inviteQr}
198
- alt="QR code for invite"
199
- className="w-40 h-40 rounded border border-base-300"
200
- style={{ imageRendering: "pixelated" }}
201
- />
202
- </div>
203
- )}
204
-
205
- <button
206
- onClick={handleGenerateInvite}
207
- className="text-[12px] text-base-content/40 underline cursor-pointer"
208
- >
209
- Generate new code
210
- </button>
211
- </div>
212
- )}
213
- </div>
214
- )}
215
-
216
- {tab === "enter" && (
217
- <div>
218
- <div className="text-[12px] text-base-content/40 mb-4 leading-relaxed">
219
- Paste the invite code generated on the other node to pair with it.
220
- </div>
221
-
222
- <input
223
- type="text"
224
- value={pairCode}
225
- onChange={function (e) {
226
- setPairCode(e.target.value);
227
- if (pairStatus !== "idle") {
228
- setPairStatus("idle");
229
- setPairError(null);
230
- }
231
- }}
232
- onKeyDown={function (e) {
233
- if (e.key === "Enter") {
234
- handlePair();
235
- }
236
- }}
237
- placeholder="LTCE-XXXX-XXXX"
238
- disabled={pairStatus === "connecting" || pairStatus === "paired"}
239
- className="input input-bordered w-full bg-base-100 text-base-content font-mono text-[14px] tracking-[0.06em] mb-3 focus:border-primary"
240
- />
241
-
242
- {pairStatus === "idle" && (
243
- <button
244
- onClick={handlePair}
245
- disabled={!pairCode.trim()}
246
- className={
247
- "btn btn-sm " +
248
- (pairCode.trim() ? "btn-primary" : "btn-ghost border border-base-300 cursor-not-allowed")
249
- }
250
- >
251
- Pair
252
- </button>
253
- )}
254
-
255
- {pairStatus === "connecting" && (
256
- <div className="flex items-center gap-2 text-[13px] text-base-content/40">
257
- <span
258
- className="w-3 h-3 rounded-full border-2 border-primary border-t-transparent inline-block"
259
- style={{ animation: "spin 0.6s linear infinite" }}
260
- />
261
- Connecting...
262
- </div>
263
- )}
264
-
265
- {pairStatus === "paired" && (
266
- <div className="flex items-center gap-1.5 text-[13px] font-semibold text-success">
267
- <Check size={14} />
268
- Paired successfully!
269
- </div>
270
- )}
271
-
272
- {pairStatus === "failed" && pairError && (
273
- <div className="text-[12px] text-error mt-2">{pairError}</div>
274
- )}
275
- </div>
276
- )}
277
- </div>
278
- </div>
279
- </div>
280
- );
281
- }
1
+ import { useState, useEffect, useCallback } from "react";
2
+ import { X, Copy, Check } from "lucide-react";
3
+ import { useWebSocket } from "../../hooks/useWebSocket";
4
+ import { useMesh } from "../../hooks/useMesh";
5
+ import { clearInvite } from "../../stores/mesh";
6
+ import type { ServerMessage } from "@lattice/shared";
7
+
8
+ type Tab = "generate" | "enter";
9
+ type PairStatus = "idle" | "connecting" | "paired" | "failed";
10
+
11
+ interface PairingDialogProps {
12
+ isOpen: boolean;
13
+ onClose: () => void;
14
+ }
15
+
16
+ export function PairingDialog(props: PairingDialogProps) {
17
+ var ws = useWebSocket();
18
+ var mesh = useMesh();
19
+ var [tab, setTab] = useState<Tab>("generate");
20
+ var [pairCode, setPairCode] = useState("");
21
+ var [pairStatus, setPairStatus] = useState<PairStatus>("idle");
22
+ var [pairError, setPairError] = useState<string | null>(null);
23
+ var [copied, setCopied] = useState(false);
24
+
25
+ var handleKeyDown = useCallback(function (e: KeyboardEvent) {
26
+ if (e.key === "Escape") {
27
+ props.onClose();
28
+ }
29
+ }, [props.onClose]);
30
+
31
+ useEffect(function () {
32
+ if (!props.isOpen) {
33
+ return;
34
+ }
35
+ document.addEventListener("keydown", handleKeyDown);
36
+ return function () {
37
+ document.removeEventListener("keydown", handleKeyDown);
38
+ };
39
+ }, [props.isOpen, handleKeyDown]);
40
+
41
+ useEffect(function () {
42
+ if (!props.isOpen) {
43
+ clearInvite();
44
+ setPairCode("");
45
+ setPairStatus("idle");
46
+ setPairError(null);
47
+ setCopied(false);
48
+ setTab("generate");
49
+ }
50
+ }, [props.isOpen]);
51
+
52
+ useEffect(function () {
53
+ if (pairStatus !== "connecting") {
54
+ return;
55
+ }
56
+
57
+ function handler(msg: ServerMessage) {
58
+ if (msg.type === "mesh:paired") {
59
+ setPairStatus("paired");
60
+ setPairError(null);
61
+ }
62
+ }
63
+
64
+ ws.subscribe("mesh:paired", handler);
65
+ return function () {
66
+ ws.unsubscribe("mesh:paired", handler);
67
+ };
68
+ }, [ws, pairStatus]);
69
+
70
+ function handleGenerateInvite() {
71
+ clearInvite();
72
+ mesh.generateInvite();
73
+ }
74
+
75
+ function handlePair() {
76
+ var trimmed = pairCode.trim();
77
+ if (!trimmed) {
78
+ return;
79
+ }
80
+ setPairStatus("connecting");
81
+ setPairError(null);
82
+
83
+ var timeout = setTimeout(function () {
84
+ setPairStatus(function (prev) {
85
+ if (prev === "connecting") {
86
+ setPairError("Pairing timed out. Check the code and try again.");
87
+ return "failed";
88
+ }
89
+ return prev;
90
+ });
91
+ }, 30000);
92
+
93
+ ws.send({ type: "mesh:pair", code: trimmed });
94
+
95
+ return function () {
96
+ clearTimeout(timeout);
97
+ };
98
+ }
99
+
100
+ function handleCopyCode() {
101
+ if (!mesh.inviteCode) {
102
+ return;
103
+ }
104
+ navigator.clipboard.writeText(mesh.inviteCode).then(function () {
105
+ setCopied(true);
106
+ setTimeout(function () { setCopied(false); }, 2000);
107
+ });
108
+ }
109
+
110
+ if (!props.isOpen) {
111
+ return null;
112
+ }
113
+
114
+ return (
115
+ <div
116
+ role="dialog"
117
+ aria-modal="true"
118
+ aria-label="Pair a node"
119
+ className="fixed inset-0 z-[10000] flex items-center justify-center bg-black/65 backdrop-blur-sm"
120
+ onClick={props.onClose}
121
+ >
122
+ <div
123
+ className="w-[440px] max-w-[calc(100vw-24px)] rounded-xl border border-base-300 bg-base-200 overflow-hidden shadow-2xl"
124
+ onClick={function (e) { e.stopPropagation(); }}
125
+ >
126
+ <div className="flex items-center justify-between px-5 py-4 border-b border-base-300">
127
+ <div className="text-[14px] font-semibold text-base-content">Pair a Node</div>
128
+ <button
129
+ onClick={props.onClose}
130
+ aria-label="Close"
131
+ className="btn btn-ghost btn-xs btn-square text-base-content/40 hover:text-base-content"
132
+ >
133
+ <X size={14} />
134
+ </button>
135
+ </div>
136
+
137
+ <div className="flex border-b border-base-300">
138
+ {(["generate", "enter"] as Tab[]).map(function (t) {
139
+ var label = t === "generate" ? "Generate Invite" : "Enter Code";
140
+ var isActive = tab === t;
141
+ return (
142
+ <button
143
+ key={t}
144
+ onClick={function () { setTab(t); }}
145
+ className={
146
+ "flex-1 px-4 py-2.5 text-[13px] cursor-pointer transition-colors duration-[120ms] border-b-2 " +
147
+ (isActive
148
+ ? "font-semibold text-base-content border-primary"
149
+ : "font-normal text-base-content/40 border-transparent hover:text-base-content/70")
150
+ }
151
+ >
152
+ {label}
153
+ </button>
154
+ );
155
+ })}
156
+ </div>
157
+
158
+ <div className="p-5">
159
+ {tab === "generate" && (
160
+ <div>
161
+ <div className="text-[12px] text-base-content/40 mb-4 leading-relaxed">
162
+ Generate an invite code on this machine and share it with the other node.
163
+ The code encodes this node&apos;s address and a one-time auth token.
164
+ </div>
165
+
166
+ {!mesh.inviteCode && (
167
+ <button
168
+ onClick={handleGenerateInvite}
169
+ className="btn btn-primary btn-sm"
170
+ >
171
+ Generate Invite Code
172
+ </button>
173
+ )}
174
+
175
+ {mesh.inviteCode && (
176
+ <div>
177
+ <div className="flex items-center gap-2 px-3.5 py-2.5 rounded bg-base-100 border border-base-300 mb-4">
178
+ <code className="flex-1 font-mono text-[14px] font-semibold text-base-content tracking-[0.08em] break-all">
179
+ {mesh.inviteCode}
180
+ </code>
181
+ <button
182
+ onClick={handleCopyCode}
183
+ title="Copy code"
184
+ className={
185
+ "btn btn-xs gap-1 flex-shrink-0 " +
186
+ (copied ? "btn-success" : "btn-ghost border border-base-300")
187
+ }
188
+ >
189
+ {copied ? <Check size={12} /> : <Copy size={12} />}
190
+ {copied ? "Copied!" : "Copy"}
191
+ </button>
192
+ </div>
193
+
194
+ {mesh.inviteQr && (
195
+ <div className="flex justify-center mb-4">
196
+ <img
197
+ src={mesh.inviteQr}
198
+ alt="QR code for invite"
199
+ className="w-40 h-40 rounded border border-base-300"
200
+ style={{ imageRendering: "pixelated" }}
201
+ />
202
+ </div>
203
+ )}
204
+
205
+ <button
206
+ onClick={handleGenerateInvite}
207
+ className="text-[12px] text-base-content/40 underline cursor-pointer"
208
+ >
209
+ Generate new code
210
+ </button>
211
+ </div>
212
+ )}
213
+ </div>
214
+ )}
215
+
216
+ {tab === "enter" && (
217
+ <div>
218
+ <div className="text-[12px] text-base-content/40 mb-4 leading-relaxed">
219
+ Paste the invite code generated on the other node to pair with it.
220
+ </div>
221
+
222
+ <input
223
+ type="text"
224
+ value={pairCode}
225
+ onChange={function (e) {
226
+ setPairCode(e.target.value);
227
+ if (pairStatus !== "idle") {
228
+ setPairStatus("idle");
229
+ setPairError(null);
230
+ }
231
+ }}
232
+ onKeyDown={function (e) {
233
+ if (e.key === "Enter") {
234
+ handlePair();
235
+ }
236
+ }}
237
+ placeholder="LTCE-XXXX-XXXX"
238
+ disabled={pairStatus === "connecting" || pairStatus === "paired"}
239
+ className="input input-bordered w-full bg-base-100 text-base-content font-mono text-[14px] tracking-[0.06em] mb-3 focus:border-primary"
240
+ />
241
+
242
+ {pairStatus === "idle" && (
243
+ <button
244
+ onClick={handlePair}
245
+ disabled={!pairCode.trim()}
246
+ className={
247
+ "btn btn-sm " +
248
+ (pairCode.trim() ? "btn-primary" : "btn-ghost border border-base-300 cursor-not-allowed")
249
+ }
250
+ >
251
+ Pair
252
+ </button>
253
+ )}
254
+
255
+ {pairStatus === "connecting" && (
256
+ <div className="flex items-center gap-2 text-[13px] text-base-content/40">
257
+ <span
258
+ className="w-3 h-3 rounded-full border-2 border-primary border-t-transparent inline-block"
259
+ style={{ animation: "spin 0.6s linear infinite" }}
260
+ />
261
+ Connecting...
262
+ </div>
263
+ )}
264
+
265
+ {pairStatus === "paired" && (
266
+ <div className="flex items-center gap-1.5 text-[13px] font-semibold text-success">
267
+ <Check size={14} />
268
+ Paired successfully!
269
+ </div>
270
+ )}
271
+
272
+ {pairStatus === "failed" && pairError && (
273
+ <div className="text-[12px] text-error mt-2">{pairError}</div>
274
+ )}
275
+ </div>
276
+ )}
277
+ </div>
278
+ </div>
279
+ </div>
280
+ );
281
+ }