@cryptiklemur/lattice 2.0.0 → 4.0.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 (204) hide show
  1. package/bin/lattice +6 -1
  2. package/dist/client/assets/{angular-html-BBi1Szz5.js → angular-html-N8PCEquT.js} +1 -1
  3. package/dist/client/assets/{angular-ts-2PMpSPqc.js → angular-ts-CJ8RJIPD.js} +1 -1
  4. package/dist/client/assets/{apl-B-NEtBvQ.js → apl-BD6tCLWN.js} +1 -1
  5. package/dist/client/assets/{astro-mxH1DA5Z.js → astro-CpIIfBs6.js} +1 -1
  6. package/dist/client/assets/{blade-BUQ9XSm3.js → blade-D3qgnjiV.js} +1 -1
  7. package/dist/client/assets/{c-DrQixLw7.js → c-Dr6ADN_t.js} +1 -1
  8. package/dist/client/assets/{cobol-42amjOMh.js → cobol-BIfDE0Hr.js} +1 -1
  9. package/dist/client/assets/{coffee-CMSEUKyy.js → coffee-DHQ57vfY.js} +1 -1
  10. package/dist/client/assets/{cpp-C4yJBzl2.js → cpp-CEBY6JOp.js} +1 -1
  11. package/dist/client/assets/{crystal-DZ27EoiA.js → crystal-D125CSmP.js} +1 -1
  12. package/dist/client/assets/{css-CdQwkSNm.js → css-CBmrkYSr.js} +1 -1
  13. package/dist/client/assets/{dist-CBRIe7YG.js → dist-A_mCRD1f.js} +2 -2
  14. package/dist/client/assets/{edge-J8VemHZC.js → edge-Ccsz7cJW.js} +1 -1
  15. package/dist/client/assets/{elixir-DU66qSn_.js → elixir-Do6gk14X.js} +1 -1
  16. package/dist/client/assets/{elm-8cz1JjRg.js → elm-Db22zT4C.js} +1 -1
  17. package/dist/client/assets/{erb-DMl63J6s.js → erb-MXVqAAJD.js} +1 -1
  18. package/dist/client/assets/{git-rebase-DjlZL8cj.js → git-rebase-B-LLWBOA.js} +1 -1
  19. package/dist/client/assets/{glimmer-js-Fu7vOQY-.js → glimmer-js-eWszRU73.js} +1 -1
  20. package/dist/client/assets/{glimmer-ts-AJljRs0A.js → glimmer-ts-VQmwGqUp.js} +1 -1
  21. package/dist/client/assets/{glsl-CfodGzSv.js → glsl-B8ilOfAl.js} +1 -1
  22. package/dist/client/assets/{graphql-C8GPKPx1.js → graphql-DnTqxeOc.js} +1 -1
  23. package/dist/client/assets/{hack-Gp6OuE21.js → hack-XJsHYSQb.js} +1 -1
  24. package/dist/client/assets/{haml-BOyG6GfM.js → haml-CQ7Vqzwp.js} +1 -1
  25. package/dist/client/assets/{handlebars-B9IoVRxd.js → handlebars-C4szooBf.js} +1 -1
  26. package/dist/client/assets/{html-CFrSukiU.js → html-B6EgAiSd.js} +1 -1
  27. package/dist/client/assets/{html-derivative-D0_gRK_M.js → html-derivative-DdinogQX.js} +1 -1
  28. package/dist/client/assets/{http-D4MmFixi.js → http-BSLxCgRq.js} +1 -1
  29. package/dist/client/assets/{hurl-D1xbk-LS.js → hurl-pOsTwNfp.js} +1 -1
  30. package/dist/client/assets/{index-DrTbNMzI.css → index-2jfMHAda.css} +1 -1
  31. package/dist/client/assets/{index-ZIuI5BCP.js → index-BHQ_8mvl.js} +62 -62
  32. package/dist/client/assets/{java-CfPMY1Dv.js → java-DRQLiiST.js} +1 -1
  33. package/dist/client/assets/{javascript-B8vNHeqm.js → javascript-DvEK2-47.js} +1 -1
  34. package/dist/client/assets/{jinja-CF0K8bFV.js → jinja-D2NYJ25y.js} +1 -1
  35. package/dist/client/assets/{jison-BP7my5TL.js → jison-DDZaLNAp.js} +1 -1
  36. package/dist/client/assets/{json-TeXNjpMZ.js → json-TGR0NIWd.js} +1 -1
  37. package/dist/client/assets/{jsx-C86NmHc2.js → jsx-BjUoPYga.js} +1 -1
  38. package/dist/client/assets/{julia-C58U_aQP.js → julia-C4gjSpFu.js} +1 -1
  39. package/dist/client/assets/{just-h6Vco8Qu.js → just-H351x5u_.js} +1 -1
  40. package/dist/client/assets/{latex-DRsNisjT.js → latex-BiTmf6gf.js} +1 -1
  41. package/dist/client/assets/{liquid-C9jXC9M7.js → liquid-86ufjRy-.js} +1 -1
  42. package/dist/client/assets/{lua-BUYvdn-F.js → lua-BNxR0F_8.js} +1 -1
  43. package/dist/client/assets/{marko-BqB4tqw-.js → marko-CvRxpRjM.js} +1 -1
  44. package/dist/client/assets/{mdc-C8Rs68Nw.js → mdc-CYbAIy2C.js} +1 -1
  45. package/dist/client/assets/{nginx-Dj6E3HRL.js → nginx-egdgMq-F.js} +1 -1
  46. package/dist/client/assets/{nim-BizJb4iF.js → nim-CXBJVz_w.js} +1 -1
  47. package/dist/client/assets/{perl-DnJAtWNn.js → perl-XRfMobzg.js} +1 -1
  48. package/dist/client/assets/{php-BeY_VEeb.js → php-Br7a8uil.js} +1 -1
  49. package/dist/client/assets/{pug-DXhFbVDh.js → pug-BVbbUVvy.js} +1 -1
  50. package/dist/client/assets/{qml-CngzXsz8.js → qml-ByKvrL1j.js} +1 -1
  51. package/dist/client/assets/{r-BMmy9apu.js → r-mVoV0Ni6.js} +1 -1
  52. package/dist/client/assets/{razor-kIo5DNUk.js → razor-T5O-9UJL.js} +1 -1
  53. package/dist/client/assets/{regexp-BIa5IayX.js → regexp-CioRuhuN.js} +1 -1
  54. package/dist/client/assets/{rst-McWv3WP0.js → rst-V__uTudD.js} +1 -1
  55. package/dist/client/assets/{ruby-CkenUDKe.js → ruby-C_PuKPTI.js} +1 -1
  56. package/dist/client/assets/{sas-Dgn9Yrr5.js → sas-D_DqqQH4.js} +1 -1
  57. package/dist/client/assets/{scss-Tfn9jzBB.js → scss-D-TjzZ4c.js} +1 -1
  58. package/dist/client/assets/{shellscript-vGpgBWOS.js → shellscript-E5759VHu.js} +1 -1
  59. package/dist/client/assets/{shellsession-7CA0dOzR.js → shellsession-AESTM-Pv.js} +1 -1
  60. package/dist/client/assets/{soy-DbBURueN.js → soy-QrbrrcDv.js} +1 -1
  61. package/dist/client/assets/{sql-BJGtbEM8.js → sql-0M8VcDHD.js} +1 -1
  62. package/dist/client/assets/{stata-L0kV9FQT.js → stata-CgeIpGtc.js} +1 -1
  63. package/dist/client/assets/{surrealql-C17jcHRl.js → surrealql-DBGwnZbw.js} +1 -1
  64. package/dist/client/assets/{svelte-B5_CmVcu.js → svelte-Cv0PvUc_.js} +1 -1
  65. package/dist/client/assets/{templ-C5f2EORt.js → templ-B9t7xRE4.js} +1 -1
  66. package/dist/client/assets/{tex-DvaBtCMN.js → tex-DhZZ8dr2.js} +1 -1
  67. package/dist/client/assets/{ts-tags-DlZzQdBU.js → ts-tags-BFv8sbnd.js} +1 -1
  68. package/dist/client/assets/{tsx-DaLOIju9.js → tsx-CXC9KSbY.js} +1 -1
  69. package/dist/client/assets/{twig-BB7320dl.js → twig-CM_OO66r.js} +1 -1
  70. package/dist/client/assets/{typescript-D8B-VgXj.js → typescript-BdgOTaoD.js} +1 -1
  71. package/dist/client/assets/{vue-DylGFDrG.js → vue-BnQhjnCm.js} +1 -1
  72. package/dist/client/assets/{vue-html-Dt8r3B9y.js → vue-html-CNnGecRI.js} +1 -1
  73. package/dist/client/assets/{vue-vine-DdN02uhY.js → vue-vine-DCuMkRhK.js} +1 -1
  74. package/dist/client/assets/{xml-CBEuB6I0.js → xml-CbTD7cB8.js} +1 -1
  75. package/dist/client/assets/{xsl-CuhlgDL5.js → xsl-uOqqo7cf.js} +1 -1
  76. package/dist/client/assets/{yaml-Da6ymghi.js → yaml-BNrLoH59.js} +1 -1
  77. package/dist/client/index.html +2 -2
  78. package/dist/client/sw.js +1 -1
  79. package/package.json +5 -2
  80. package/src/client/components/analytics/AnalyticsView.tsx +1 -1
  81. package/src/client/components/analytics/ChartCard.tsx +1 -1
  82. package/src/client/components/analytics/charts/NodeFleetOverview.tsx +1 -1
  83. package/src/client/components/chat/AttachmentChips.tsx +4 -4
  84. package/src/client/components/chat/ChatView.tsx +2 -2
  85. package/src/client/components/chat/CommandPalette.tsx +1 -1
  86. package/src/client/components/chat/ElicitationCard.tsx +3 -0
  87. package/src/client/components/chat/Message.tsx +7 -6
  88. package/src/client/components/chat/PromptQuestion.tsx +1 -1
  89. package/src/client/components/chat/TodoCard.tsx +1 -1
  90. package/src/client/components/chat/ToolGroup.tsx +1 -1
  91. package/src/client/components/chat/ToolResultRenderer.tsx +2 -2
  92. package/src/client/components/dashboard/DashboardView.tsx +1 -1
  93. package/src/client/components/dashboard/ProjectDashboardView.tsx +1 -1
  94. package/src/client/components/mesh/NodeBadge.tsx +1 -1
  95. package/src/client/components/mesh/PairingDialog.tsx +2 -2
  96. package/src/client/components/project-settings/ProjectClaude.tsx +1 -1
  97. package/src/client/components/project-settings/ProjectEnvironment.tsx +1 -1
  98. package/src/client/components/project-settings/ProjectGeneral.tsx +1 -1
  99. package/src/client/components/project-settings/ProjectMcp.tsx +1 -1
  100. package/src/client/components/project-settings/ProjectMemory.tsx +3 -3
  101. package/src/client/components/project-settings/ProjectPermissions.tsx +1 -1
  102. package/src/client/components/project-settings/ProjectPlugins.tsx +1 -1
  103. package/src/client/components/project-settings/ProjectRules.tsx +1 -1
  104. package/src/client/components/project-settings/ProjectSettingsView.tsx +1 -1
  105. package/src/client/components/project-settings/ProjectSkills.tsx +1 -1
  106. package/src/client/components/settings/Appearance.tsx +1 -1
  107. package/src/client/components/settings/BudgetSettings.tsx +1 -1
  108. package/src/client/components/settings/ClaudeSettings.tsx +1 -1
  109. package/src/client/components/settings/Editor.tsx +1 -1
  110. package/src/client/components/settings/Environment.tsx +1 -1
  111. package/src/client/components/settings/GlobalMcp.tsx +2 -2
  112. package/src/client/components/settings/GlobalPlugins.tsx +2 -2
  113. package/src/client/components/settings/GlobalRules.tsx +1 -1
  114. package/src/client/components/settings/GlobalSkills.tsx +1 -1
  115. package/src/client/components/settings/MeshStatus.tsx +1 -1
  116. package/src/client/components/settings/SkillMarketplace.tsx +1 -1
  117. package/src/client/components/settings/ThemeWizard.tsx +1 -1
  118. package/src/client/components/settings/mcp-shared.tsx +1 -1
  119. package/src/client/components/settings/skill-shared.tsx +2 -2
  120. package/src/client/components/setup/SetupWizard.tsx +5 -0
  121. package/src/client/components/sidebar/AddProjectModal.tsx +2 -2
  122. package/src/client/components/sidebar/NodeSettingsModal.tsx +2 -2
  123. package/src/client/components/sidebar/ProjectRail.tsx +1 -1
  124. package/src/client/components/sidebar/SessionList.tsx +2 -2
  125. package/src/client/components/sidebar/Sidebar.tsx +1 -1
  126. package/src/client/components/sidebar/UserIsland.tsx +1 -1
  127. package/src/client/components/ui/CommandPalette.tsx +1 -1
  128. package/src/client/components/ui/IconPicker.tsx +3 -1
  129. package/src/client/components/ui/KeyboardShortcuts.tsx +1 -1
  130. package/src/client/components/ui/UpdateBanner.tsx +1 -1
  131. package/src/client/components/workspace/BookmarksView.tsx +2 -2
  132. package/src/client/components/workspace/FileBrowser.tsx +1 -1
  133. package/src/client/components/workspace/FileTree.tsx +1 -1
  134. package/src/client/components/workspace/NoteCard.tsx +2 -1
  135. package/src/client/components/workspace/NotesView.tsx +1 -1
  136. package/src/client/components/workspace/ScheduledTasksView.tsx +1 -1
  137. package/src/client/components/workspace/TaskCard.tsx +2 -1
  138. package/src/client/components/workspace/TaskEditModal.tsx +2 -2
  139. package/src/client/components/workspace/TerminalInstance.tsx +1 -1
  140. package/src/client/hooks/useAnalytics.ts +7 -7
  141. package/src/client/hooks/useAttachments.ts +49 -16
  142. package/src/client/hooks/useBookmarks.ts +1 -1
  143. package/src/client/hooks/useEditorConfig.ts +1 -1
  144. package/src/client/hooks/useFocusTrap.ts +4 -2
  145. package/src/client/hooks/useIdleDetection.ts +6 -0
  146. package/src/client/hooks/useMesh.ts +1 -1
  147. package/src/client/hooks/useProjectSettings.ts +1 -1
  148. package/src/client/hooks/useProjects.ts +3 -3
  149. package/src/client/hooks/useSession.ts +31 -32
  150. package/src/client/hooks/useSkills.ts +2 -2
  151. package/src/client/hooks/useSpinnerVerb.ts +1 -1
  152. package/src/client/hooks/useVoiceRecorder.ts +5 -2
  153. package/src/client/hooks/useWebSocket.ts +1 -1
  154. package/src/client/providers/WebSocketProvider.tsx +9 -1
  155. package/src/client/router.tsx +1 -1
  156. package/src/client/stores/analytics.ts +1 -1
  157. package/src/client/stores/bookmarks.ts +1 -1
  158. package/src/client/stores/mesh.ts +1 -1
  159. package/src/client/stores/session.ts +1 -11
  160. package/src/client/stores/sidebar.ts +1 -1
  161. package/src/client/styles/global.css +12 -0
  162. package/src/server/analytics/engine.ts +1 -1
  163. package/src/server/auth/passphrase.ts +16 -6
  164. package/src/server/config.ts +2 -2
  165. package/src/server/daemon.ts +32 -12
  166. package/src/server/features/ralph-loop.ts +1 -1
  167. package/src/server/features/scheduler.ts +1 -1
  168. package/src/server/features/sticky-notes.ts +1 -1
  169. package/src/server/handlers/analytics.ts +1 -1
  170. package/src/server/handlers/attachment.ts +4 -3
  171. package/src/server/handlers/bookmarks.ts +1 -1
  172. package/src/server/handlers/chat.ts +1 -1
  173. package/src/server/handlers/editor.ts +1 -1
  174. package/src/server/handlers/fs.ts +1 -1
  175. package/src/server/handlers/loop.ts +1 -1
  176. package/src/server/handlers/memory.ts +1 -1
  177. package/src/server/handlers/mesh.ts +3 -3
  178. package/src/server/handlers/notes.ts +1 -1
  179. package/src/server/handlers/plugins.ts +1 -1
  180. package/src/server/handlers/project-settings.ts +1 -1
  181. package/src/server/handlers/scheduler.ts +1 -1
  182. package/src/server/handlers/session.ts +2 -1
  183. package/src/server/handlers/settings.ts +4 -4
  184. package/src/server/handlers/skills.ts +5 -5
  185. package/src/server/handlers/terminal.ts +12 -1
  186. package/src/server/handlers/themes.ts +1 -1
  187. package/src/server/handlers/update.ts +1 -1
  188. package/src/server/identity.ts +3 -1
  189. package/src/server/index.ts +2 -2
  190. package/src/server/mesh/connector.ts +1 -1
  191. package/src/server/mesh/peers.ts +1 -1
  192. package/src/server/mesh/proxy.ts +1 -1
  193. package/src/server/mesh/session-sync.ts +1 -1
  194. package/src/server/project/bookmarks.ts +1 -1
  195. package/src/server/project/context-breakdown.ts +1 -1
  196. package/src/server/project/file-browser.ts +1 -1
  197. package/src/server/project/registry.ts +1 -1
  198. package/src/server/project/sdk-bridge.ts +28 -2
  199. package/src/server/project/session.ts +7 -6
  200. package/src/server/tls.ts +15 -1
  201. package/src/server/tui.ts +2 -2
  202. package/src/server/ws/router.ts +1 -1
  203. package/tsconfig.json +1 -1
  204. package/vite.config.ts +1 -1
@@ -1,6 +1,6 @@
1
- import { useState, useCallback, useRef } from "react";
1
+ import { useState, useCallback, useRef, useEffect } from "react";
2
2
  import { useWebSocket } from "./useWebSocket";
3
- import type { ServerMessage } from "@lattice/shared";
3
+ import type { ServerMessage } from "#shared";
4
4
 
5
5
  var CHUNK_SIZE = 64 * 1024;
6
6
  var MAX_FILE_SIZE = 50 * 1024 * 1024;
@@ -76,6 +76,8 @@ export function useAttachments(): UseAttachmentsReturn {
76
76
  var { send, subscribe, unsubscribe } = useWebSocket();
77
77
  var pendingResolvers = useRef(new Map<string, { resolve: () => void; reject: (err: string) => void; timer: ReturnType<typeof setTimeout> }>());
78
78
  var fileCache = useRef(new Map<string, File>());
79
+ var attachmentsRef = useRef(attachments);
80
+ attachmentsRef.current = attachments;
79
81
 
80
82
  var updateAttachment = useCallback(function (id: string, updates: Partial<ClientAttachment>) {
81
83
  setAttachments(function (prev) {
@@ -111,7 +113,11 @@ export function useAttachments(): UseAttachmentsReturn {
111
113
  var start = chunkIndex * CHUNK_SIZE;
112
114
  var end = Math.min(start + CHUNK_SIZE, bytes.length);
113
115
  var chunk = bytes.slice(start, end);
114
- var base64 = btoa(String.fromCharCode.apply(null, chunk as unknown as number[]));
116
+ var binary = "";
117
+ for (var bi = 0; bi < chunk.length; bi++) {
118
+ binary += String.fromCharCode(chunk[bi]);
119
+ }
120
+ var base64 = btoa(binary);
115
121
 
116
122
  send({
117
123
  type: "attachment:chunk",
@@ -178,9 +184,6 @@ export function useAttachments(): UseAttachmentsReturn {
178
184
  if (file.size > MAX_FILE_SIZE) {
179
185
  return;
180
186
  }
181
- if (attachments.length >= MAX_ATTACHMENTS) {
182
- return;
183
- }
184
187
 
185
188
  var id = crypto.randomUUID();
186
189
  var mime = guessMimeType(file);
@@ -202,14 +205,24 @@ export function useAttachments(): UseAttachmentsReturn {
202
205
  previewUrl,
203
206
  };
204
207
 
205
- fileCache.current.set(id, file);
206
- setAttachments(function (prev) { return [...prev, att]; });
207
- uploadFile(att, file);
208
- }, [attachments.length, uploadFile]);
208
+ var added = false;
209
+ setAttachments(function (prev) {
210
+ if (prev.length >= MAX_ATTACHMENTS) {
211
+ return prev;
212
+ }
213
+ added = true;
214
+ return [...prev, att];
215
+ });
209
216
 
210
- var addPaste = useCallback(function (text: string) {
211
- if (attachments.length >= MAX_ATTACHMENTS) return;
217
+ if (added) {
218
+ fileCache.current.set(id, file);
219
+ uploadFile(att, file);
220
+ } else if (previewUrl) {
221
+ URL.revokeObjectURL(previewUrl);
222
+ }
223
+ }, [uploadFile]);
212
224
 
225
+ var addPaste = useCallback(function (text: string) {
213
226
  var id = crypto.randomUUID();
214
227
  var blob = new Blob([text], { type: "text/plain" });
215
228
  var file = new File([blob], "pasted-text.txt", { type: "text/plain" });
@@ -227,10 +240,20 @@ export function useAttachments(): UseAttachmentsReturn {
227
240
  content: text,
228
241
  };
229
242
 
230
- fileCache.current.set(id, file);
231
- setAttachments(function (prev) { return [...prev, att]; });
232
- uploadFile(att, file);
233
- }, [attachments.length, uploadFile]);
243
+ var added = false;
244
+ setAttachments(function (prev) {
245
+ if (prev.length >= MAX_ATTACHMENTS) {
246
+ return prev;
247
+ }
248
+ added = true;
249
+ return [...prev, att];
250
+ });
251
+
252
+ if (added) {
253
+ fileCache.current.set(id, file);
254
+ uploadFile(att, file);
255
+ }
256
+ }, [uploadFile]);
234
257
 
235
258
  var removeAttachment = useCallback(function (id: string) {
236
259
  setAttachments(function (prev) {
@@ -259,6 +282,16 @@ export function useAttachments(): UseAttachmentsReturn {
259
282
  fileCache.current.clear();
260
283
  }, [attachments]);
261
284
 
285
+ useEffect(function () {
286
+ return function () {
287
+ attachmentsRef.current.forEach(function (att) {
288
+ if (att.previewUrl) {
289
+ URL.revokeObjectURL(att.previewUrl);
290
+ }
291
+ });
292
+ };
293
+ }, []);
294
+
262
295
  var readyIds = attachments
263
296
  .filter(function (a) { return a.status === "ready"; })
264
297
  .map(function (a) { return a.id; });
@@ -1,6 +1,6 @@
1
1
  import { useEffect } from "react";
2
2
  import { useStore } from "@tanstack/react-store";
3
- import type { ServerMessage, BookmarkListResultMessage, MessageBookmark } from "@lattice/shared";
3
+ import type { ServerMessage, BookmarkListResultMessage, MessageBookmark } from "#shared";
4
4
  import { useWebSocket } from "./useWebSocket";
5
5
  import { getBookmarkStore, setBookmarks, setAllBookmarks } from "../stores/bookmarks";
6
6
  import { getSessionStore } from "../stores/session";
@@ -1,6 +1,6 @@
1
1
  import { useEffect, useState } from "react";
2
2
  import { useWebSocket } from "./useWebSocket";
3
- import type { ServerMessage, SettingsDataMessage } from "@lattice/shared";
3
+ import type { ServerMessage, SettingsDataMessage } from "#shared";
4
4
 
5
5
  export function useEditorConfig() {
6
6
  var ws = useWebSocket();
@@ -15,6 +15,8 @@ export function useFocusTrap(
15
15
  active: boolean = true,
16
16
  ): void {
17
17
  var previouslyFocusedRef = useRef<HTMLElement | null>(null);
18
+ var onCloseRef = useRef(onClose);
19
+ onCloseRef.current = onClose;
18
20
 
19
21
  useEffect(function () {
20
22
  if (!active) return;
@@ -31,7 +33,7 @@ export function useFocusTrap(
31
33
 
32
34
  function handleKeyDown(e: KeyboardEvent) {
33
35
  if (e.key === "Escape") {
34
- onClose();
36
+ onCloseRef.current();
35
37
  return;
36
38
  }
37
39
 
@@ -68,5 +70,5 @@ export function useFocusTrap(
68
70
  previouslyFocusedRef.current.focus();
69
71
  }
70
72
  };
71
- }, [containerRef, onClose, active]);
73
+ }, [containerRef, active]);
72
74
  }
@@ -7,7 +7,13 @@ export function useIdleDetection(): boolean {
7
7
  var timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
8
8
 
9
9
  useEffect(function () {
10
+ var lastReset = 0;
11
+
10
12
  function resetTimer() {
13
+ var now = Date.now();
14
+ if (now - lastReset < 500) return;
15
+ lastReset = now;
16
+
11
17
  if (timerRef.current) clearTimeout(timerRef.current);
12
18
  setIsIdle(false);
13
19
  timerRef.current = setTimeout(function () {
@@ -1,6 +1,6 @@
1
1
  import { useEffect, useRef } from "react";
2
2
  import { useStore } from "@tanstack/react-store";
3
- import type { ServerMessage, NodeInfo } from "@lattice/shared";
3
+ import type { ServerMessage, NodeInfo } from "#shared";
4
4
  import { useWebSocket } from "./useWebSocket";
5
5
  import {
6
6
  getMeshStore,
@@ -1,6 +1,6 @@
1
1
  import { useState, useEffect } from "react";
2
2
  import { useWebSocket } from "./useWebSocket";
3
- import type { ServerMessage, ProjectSettings, ProjectSettingsDataMessage, ProjectSettingsErrorMessage } from "@lattice/shared";
3
+ import type { ServerMessage, ProjectSettings, ProjectSettingsDataMessage, ProjectSettingsErrorMessage } from "#shared";
4
4
 
5
5
  export function useProjectSettings(projectSlug: string | null) {
6
6
  var { status, send, subscribe, unsubscribe } = useWebSocket();
@@ -1,8 +1,8 @@
1
1
  import { useEffect, useRef, useState } from "react";
2
2
  import { useStore } from "@tanstack/react-store";
3
- import type { ProjectInfo } from "@lattice/shared";
4
- import type { ProjectsListMessage } from "@lattice/shared";
5
- import type { ServerMessage } from "@lattice/shared";
3
+ import type { ProjectInfo } from "#shared";
4
+ import type { ProjectsListMessage } from "#shared";
5
+ import type { ServerMessage } from "#shared";
6
6
  import { useWebSocket } from "./useWebSocket";
7
7
  import { setActiveProjectSlug, getSidebarStore } from "../stores/sidebar";
8
8
 
@@ -1,6 +1,6 @@
1
1
  import { useEffect, useRef } from "react";
2
2
  import { useStore } from "@tanstack/react-store";
3
- import type { HistoryMessage } from "@lattice/shared";
3
+ import type { HistoryMessage } from "#shared";
4
4
  import type {
5
5
  ChatDeltaMessage,
6
6
  ChatSendMessage,
@@ -16,7 +16,7 @@ import type {
16
16
  ChatElicitationRequestMessage,
17
17
  SessionHistoryMessage,
18
18
  ServerMessage,
19
- } from "@lattice/shared";
19
+ } from "#shared";
20
20
  import { useWebSocket } from "./useWebSocket";
21
21
  import { setActiveSessionId as setSidebarSessionId } from "../stores/sidebar";
22
22
  import { updateSessionTabTitle, pinTab } from "../stores/workspace";
@@ -46,7 +46,6 @@ import {
46
46
  setPromptSuggestion,
47
47
  setFailedInput,
48
48
  enqueueMessage,
49
- dequeueMessage,
50
49
  removeQueuedMessage,
51
50
  updateQueuedMessage,
52
51
  clearMessageQueue,
@@ -59,13 +58,6 @@ import {
59
58
  } from "../stores/session";
60
59
  import type { SessionState, BudgetStatus } from "../stores/session";
61
60
 
62
- var subscriptionsActive = 0;
63
- var activeStreamGeneration = 0;
64
- var streamSessionId: string | null = null;
65
- var lastSentText: string | null = null;
66
- var lastUsedModel: string | undefined = undefined;
67
- var lastUsedEffort: string | undefined = undefined;
68
-
69
61
  export type { SessionState };
70
62
 
71
63
  export interface UseSessionReturn extends SessionState {
@@ -91,6 +83,12 @@ export function useSession(): UseSessionReturn {
91
83
  var sendRef = useRef(send);
92
84
  sendRef.current = send;
93
85
  var sendMessageRef = useRef(function (_text: string, _attachmentIds?: string[], _model?: string, _effort?: string) {});
86
+ var subscriptionsActiveRef = useRef(0);
87
+ var activeStreamGenerationRef = useRef(0);
88
+ var streamSessionIdRef = useRef<string | null>(null);
89
+ var lastSentTextRef = useRef<string | null>(null);
90
+ var lastUsedModelRef = useRef<string | undefined>(undefined);
91
+ var lastUsedEffortRef = useRef<string | undefined>(undefined);
94
92
 
95
93
  function activateSession(projectSlug: string, sessionId: string) {
96
94
  setActiveSession(projectSlug, sessionId);
@@ -113,11 +111,11 @@ export function useSession(): UseSessionReturn {
113
111
  if (effort) {
114
112
  msg.effort = effort;
115
113
  }
116
- activeStreamGeneration = getStreamGeneration();
117
- streamSessionId = currentSessionId;
118
- lastSentText = text;
119
- lastUsedModel = model;
120
- lastUsedEffort = effort;
114
+ activeStreamGenerationRef.current = getStreamGeneration();
115
+ streamSessionIdRef.current = currentSessionId;
116
+ lastSentTextRef.current = text;
117
+ lastUsedModelRef.current = model;
118
+ lastUsedEffortRef.current = effort;
121
119
  setFailedInput(null);
122
120
  setPromptSuggestion(null);
123
121
  setIsProcessing(true);
@@ -134,15 +132,15 @@ export function useSession(): UseSessionReturn {
134
132
  sendMessageRef.current = sendMessage;
135
133
 
136
134
  useEffect(function () {
137
- subscriptionsActive++;
138
- if (subscriptionsActive > 1) {
139
- return function () { subscriptionsActive--; };
135
+ subscriptionsActiveRef.current++;
136
+ if (subscriptionsActiveRef.current > 1) {
137
+ return function () { subscriptionsActiveRef.current--; };
140
138
  }
141
139
 
142
140
  function isStaleStream(): boolean {
143
- if (activeStreamGeneration !== getStreamGeneration()) return true;
141
+ if (activeStreamGenerationRef.current !== getStreamGeneration()) return true;
144
142
  var currentActiveId = getSessionStore().state.activeSessionId;
145
- if (streamSessionId && currentActiveId && streamSessionId !== currentActiveId) return true;
143
+ if (streamSessionIdRef.current && currentActiveId && streamSessionIdRef.current !== currentActiveId) return true;
146
144
  return false;
147
145
  }
148
146
 
@@ -220,7 +218,7 @@ export function useSession(): UseSessionReturn {
220
218
  function handleDone(msg: ServerMessage) {
221
219
  if (isStaleStream()) return;
222
220
  var m = msg as { type: string; cost: number; duration: number; sessionId?: string };
223
- lastSentText = null;
221
+ lastSentTextRef.current = null;
224
222
  setIsProcessing(false);
225
223
  setCurrentStatus(null);
226
224
  setCurrentAssistantUuid(null);
@@ -234,7 +232,7 @@ export function useSession(): UseSessionReturn {
234
232
  var combined = queue.join("\n\n");
235
233
  clearMessageQueue();
236
234
  setTimeout(function () {
237
- sendMessageRef.current(combined, [], lastUsedModel, lastUsedEffort);
235
+ sendMessageRef.current(combined, [], lastUsedModelRef.current, lastUsedEffortRef.current);
238
236
  }, 100);
239
237
  }
240
238
  }
@@ -242,12 +240,13 @@ export function useSession(): UseSessionReturn {
242
240
  function handleError(msg: ServerMessage) {
243
241
  if (isStaleStream()) return;
244
242
  var m = msg as { type: string; message?: string };
243
+ if (m.message && m.message.includes("Sent before connected")) return;
245
244
  setIsProcessing(false);
246
245
  setCurrentStatus(null);
247
246
  setCurrentAssistantUuid(null);
248
- if (lastSentText) {
249
- setFailedInput(lastSentText);
250
- lastSentText = null;
247
+ if (lastSentTextRef.current) {
248
+ setFailedInput(lastSentTextRef.current);
249
+ lastSentTextRef.current = null;
251
250
  }
252
251
  if (m.message) {
253
252
  addSessionMessage({
@@ -324,7 +323,7 @@ export function useSession(): UseSessionReturn {
324
323
  }
325
324
 
326
325
  function handleHistoryPage(msg: ServerMessage) {
327
- var m = msg as { type: string; sessionId: string; messages: HistoryMessage[]; hasMore: boolean };
326
+ var m = msg as { type: string; sessionId: string; messages: HistoryMessage[]; hasMore: boolean; totalMessages?: number };
328
327
  var state = getSessionStore().state;
329
328
  if (m.sessionId !== state.activeSessionId) return;
330
329
  getSessionStore().setState(function (s) {
@@ -332,6 +331,7 @@ export function useSession(): UseSessionReturn {
332
331
  ...s,
333
332
  messages: mergeToolResults(m.messages).concat(s.messages),
334
333
  historyHasMore: m.hasMore,
334
+ historyTotalMessages: m.totalMessages ?? s.historyTotalMessages,
335
335
  };
336
336
  });
337
337
  }
@@ -359,7 +359,7 @@ export function useSession(): UseSessionReturn {
359
359
  }
360
360
  var projectSlug = m.projectSlug || getSessionStore().state.activeProjectSlug;
361
361
  setSidebarSessionId(m.sessionId);
362
- streamSessionId = m.sessionId;
362
+ streamSessionIdRef.current = m.sessionId;
363
363
  if (m.title) {
364
364
  updateSessionTabTitle(m.sessionId, m.title);
365
365
  }
@@ -418,7 +418,6 @@ export function useSession(): UseSessionReturn {
418
418
  setSessionTitle(m.title);
419
419
  }
420
420
  }
421
- setSessionMessages(m.messages);
422
421
  }
423
422
 
424
423
  function handlePromptSuggestion(msg: ServerMessage) {
@@ -498,7 +497,7 @@ export function useSession(): UseSessionReturn {
498
497
  subscribe("chat:rate_limit", handleRateLimit);
499
498
 
500
499
  return function () {
501
- subscriptionsActive--;
500
+ subscriptionsActiveRef.current--;
502
501
  unsubscribe("session:loading_progress", handleLoadingProgress);
503
502
  unsubscribe("chat:user_message", handleUserMessage);
504
503
  unsubscribe("chat:delta", handleDelta);
@@ -571,9 +570,9 @@ export function useSession(): UseSessionReturn {
571
570
  },
572
571
  dismissBudgetExceeded: function () {
573
572
  setBudgetExceeded(false);
574
- if (lastSentText) {
575
- setFailedInput(lastSentText);
576
- lastSentText = null;
573
+ if (lastSentTextRef.current) {
574
+ setFailedInput(lastSentTextRef.current);
575
+ lastSentTextRef.current = null;
577
576
  }
578
577
  },
579
578
  rateLimits: state.rateLimits,
@@ -1,6 +1,6 @@
1
1
  import { useEffect, useState } from "react";
2
- import type { SkillInfo } from "@lattice/shared";
3
- import type { ServerMessage } from "@lattice/shared";
2
+ import type { SkillInfo } from "#shared";
3
+ import type { ServerMessage } from "#shared";
4
4
  import { useWebSocket } from "./useWebSocket";
5
5
 
6
6
  export function useSkills(): SkillInfo[] {
@@ -1,6 +1,6 @@
1
1
  import { useEffect, useState } from "react";
2
2
  import { useWebSocket } from "./useWebSocket";
3
- import type { ServerMessage, SettingsDataMessage } from "@lattice/shared";
3
+ import type { ServerMessage, SettingsDataMessage } from "#shared";
4
4
 
5
5
  var DEFAULT_VERBS = ["Thinking", "Analyzing", "Processing", "Computing", "Evaluating"];
6
6
 
@@ -63,8 +63,11 @@ export function useVoiceRecorder(): UseVoiceRecorderReturn {
63
63
  var recognitionRef = useRef<SpeechRecognitionLike | null>(null);
64
64
  var timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
65
65
  var finalTranscriptRef = useRef("");
66
+ var interimTranscriptRef = useRef("");
66
67
  var startTimeRef = useRef(0);
67
68
 
69
+ interimTranscriptRef.current = interimTranscript;
70
+
68
71
  var SpeechRecognitionClass = typeof window !== "undefined"
69
72
  ? (window as unknown as { SpeechRecognition?: new () => SpeechRecognitionLike; webkitSpeechRecognition?: new () => SpeechRecognitionLike }).SpeechRecognition
70
73
  || (window as unknown as { webkitSpeechRecognition?: new () => SpeechRecognitionLike }).webkitSpeechRecognition
@@ -140,10 +143,10 @@ export function useVoiceRecorder(): UseVoiceRecorderReturn {
140
143
  if (recognitionRef.current) {
141
144
  recognitionRef.current.stop();
142
145
  }
143
- var transcript = finalTranscriptRef.current || interimTranscript;
146
+ var transcript = finalTranscriptRef.current || interimTranscriptRef.current;
144
147
  cleanup();
145
148
  return transcript;
146
- }, [interimTranscript, cleanup]);
149
+ }, [cleanup]);
147
150
 
148
151
  var cancel = useCallback(function () {
149
152
  if (recognitionRef.current) {
@@ -1,5 +1,5 @@
1
1
  import { createContext, useContext } from "react";
2
- import type { ClientMessage, ServerMessage } from "@lattice/shared";
2
+ import type { ClientMessage, ServerMessage } from "#shared";
3
3
 
4
4
  export type WebSocketStatus = "connecting" | "connected" | "disconnected";
5
5
 
@@ -1,6 +1,6 @@
1
1
  import { useEffect, useRef, useState } from "react";
2
2
  import type { ReactNode } from "react";
3
- import type { ClientMessage, ServerMessage } from "@lattice/shared";
3
+ import type { ClientMessage, ServerMessage } from "#shared";
4
4
  import { WebSocketContext, getWebSocketUrl } from "../hooks/useWebSocket";
5
5
  import type { WebSocketStatus } from "../hooks/useWebSocket";
6
6
  import { showToast } from "../components/ui/Toast";
@@ -21,6 +21,7 @@ export function WebSocketProvider(props: WebSocketProviderProps) {
21
21
  var unmountedRef = useRef<boolean>(false);
22
22
  var listenersRef = useRef<Map<string, Set<(msg: ServerMessage) => void>>>(new Map());
23
23
  var hasConnectedRef = useRef<boolean>(false);
24
+ var outgoingQueueRef = useRef<ClientMessage[]>([]);
24
25
 
25
26
  function connect() {
26
27
  if (unmountedRef.current) {
@@ -54,6 +55,11 @@ export function WebSocketProvider(props: WebSocketProviderProps) {
54
55
  }
55
56
  }
56
57
  hasConnectedRef.current = true;
58
+ var queued = outgoingQueueRef.current;
59
+ outgoingQueueRef.current = [];
60
+ queued.forEach(function (msg) {
61
+ ws.send(JSON.stringify(msg));
62
+ });
57
63
  };
58
64
 
59
65
  ws.onmessage = function (event: MessageEvent) {
@@ -131,6 +137,8 @@ export function WebSocketProvider(props: WebSocketProviderProps) {
131
137
  var ws = wsRef.current;
132
138
  if (ws && ws.readyState === WebSocket.OPEN) {
133
139
  ws.send(JSON.stringify(msg));
140
+ } else {
141
+ outgoingQueueRef.current.push(msg);
134
142
  }
135
143
  }
136
144
 
@@ -319,7 +319,7 @@ function RemoveProjectConfirm() {
319
319
 
320
320
  return (
321
321
  <div ref={removeModalRef} className="fixed inset-0 z-[9999] flex items-center justify-center" role="dialog" aria-modal="true" aria-label="Remove Project">
322
- <div className="absolute inset-0 bg-black/50" onClick={sidebar.closeConfirmRemove} />
322
+ <div className="absolute inset-0 bg-base-content/50" onClick={sidebar.closeConfirmRemove} />
323
323
  <div className="relative bg-base-200 border border-base-content/15 rounded-2xl shadow-2xl w-full max-w-sm mx-4 overflow-hidden">
324
324
  <div className="px-5 py-4 border-b border-base-content/15">
325
325
  <h2 className="text-[15px] font-mono font-bold text-base-content">Remove Project</h2>
@@ -1,5 +1,5 @@
1
1
  import { Store } from "@tanstack/react-store";
2
- import type { AnalyticsPayload, AnalyticsPeriod, AnalyticsScope, AnalyticsSectionName } from "@lattice/shared";
2
+ import type { AnalyticsPayload, AnalyticsPeriod, AnalyticsScope, AnalyticsSectionName } from "#shared";
3
3
 
4
4
  export interface AnalyticsState {
5
5
  data: Partial<AnalyticsPayload>;
@@ -1,5 +1,5 @@
1
1
  import { Store } from "@tanstack/react-store";
2
- import type { MessageBookmark } from "@lattice/shared";
2
+ import type { MessageBookmark } from "#shared";
3
3
 
4
4
  export interface BookmarkState {
5
5
  bookmarks: MessageBookmark[];
@@ -1,5 +1,5 @@
1
1
  import { Store } from "@tanstack/react-store";
2
- import type { NodeInfo } from "@lattice/shared";
2
+ import type { NodeInfo } from "#shared";
3
3
 
4
4
  export interface MeshState {
5
5
  nodes: NodeInfo[];
@@ -1,5 +1,5 @@
1
1
  import { Store } from "@tanstack/react-store";
2
- import type { HistoryMessage } from "@lattice/shared";
2
+ import type { HistoryMessage } from "#shared";
3
3
 
4
4
  export interface ContextUsage {
5
5
  inputTokens: number;
@@ -505,16 +505,6 @@ export function enqueueMessage(text: string): void {
505
505
  });
506
506
  }
507
507
 
508
- export function dequeueMessage(): string | null {
509
- var queue = sessionStore.state.messageQueue;
510
- if (queue.length === 0) return null;
511
- var first = queue[0];
512
- sessionStore.setState(function (state) {
513
- return { ...state, messageQueue: state.messageQueue.slice(1) };
514
- });
515
- return first;
516
- }
517
-
518
508
  export function removeQueuedMessage(index: number): void {
519
509
  sessionStore.setState(function (state) {
520
510
  var updated = state.messageQueue.slice();
@@ -1,5 +1,5 @@
1
1
  import { Store } from "@tanstack/react-store";
2
- import type { ProjectSettingsSection } from "@lattice/shared";
2
+ import type { ProjectSettingsSection } from "#shared";
3
3
  import { encodeWorkspaceUrl, decodeWorkspaceUrl, isLegacySessionUrl, shortSessionId } from "../lib/workspace-url";
4
4
  import type { DecodedWorkspace } from "../lib/workspace-url";
5
5
  import { getWorkspaceStore, restoreWorkspace, setUrlSyncCallback, switchProjectWorkspace, setCurrentProjectKey } from "./workspace";
@@ -267,6 +267,10 @@ html, body {
267
267
  min-width: 44px;
268
268
  }
269
269
 
270
+ .btn-xs.min-h-0 {
271
+ min-height: 44px;
272
+ }
273
+
270
274
  .select-xs,
271
275
  .select-sm {
272
276
  min-width: unset;
@@ -277,6 +281,14 @@ html, body {
277
281
  min-width: 36px;
278
282
  padding: 10px 12px;
279
283
  }
284
+
285
+ .icon-action {
286
+ min-height: 44px;
287
+ min-width: 44px;
288
+ display: inline-flex;
289
+ align-items: center;
290
+ justify-content: center;
291
+ }
280
292
  }
281
293
 
282
294
  /* Neutralize DaisyUI 5's root scroll lock for the drawer.
@@ -2,7 +2,7 @@ import { readdirSync, existsSync, readFileSync, statSync } from "node:fs";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import { homedir } from "node:os";
4
4
  import { join } from "node:path";
5
- import type { AnalyticsPayload, AnalyticsPeriod, AnalyticsScope, AnalyticsSectionName } from "@lattice/shared";
5
+ import type { AnalyticsPayload, AnalyticsPeriod, AnalyticsScope, AnalyticsSectionName } from "#shared";
6
6
  import { estimateCost, projectPathToHash } from "../project/session";
7
7
  import { loadConfig } from "../config";
8
8
 
@@ -1,17 +1,26 @@
1
- import { scryptSync, randomBytes, timingSafeEqual } from "node:crypto";
1
+ import { scrypt, randomBytes, timingSafeEqual } from "node:crypto";
2
+
3
+ function scryptAsync(password: string, salt: string, keylen: number): Promise<Buffer> {
4
+ return new Promise(function (resolve, reject) {
5
+ scrypt(password, salt, keylen, function (err, derivedKey) {
6
+ if (err) reject(err);
7
+ else resolve(derivedKey);
8
+ });
9
+ });
10
+ }
2
11
 
3
12
  var TOKEN_TTL = 86400000;
4
13
  var CLEANUP_INTERVAL = 600000;
5
14
 
6
15
  var activeSessions = new Map<string, number>();
7
16
 
8
- export function hashPassphrase(passphrase: string): string {
17
+ export async function hashPassphrase(passphrase: string): Promise<string> {
9
18
  var salt = randomBytes(16).toString("hex");
10
- var hash = scryptSync(passphrase, salt, 64).toString("hex");
19
+ var hash = (await scryptAsync(passphrase, salt, 64)).toString("hex");
11
20
  return salt + ":" + hash;
12
21
  }
13
22
 
14
- export function verifyPassphrase(passphrase: string, storedHash: string): boolean {
23
+ export async function verifyPassphrase(passphrase: string, storedHash: string): Promise<boolean> {
15
24
  var parts = storedHash.split(":");
16
25
  if (parts.length !== 2) {
17
26
  return false;
@@ -19,7 +28,7 @@ export function verifyPassphrase(passphrase: string, storedHash: string): boolea
19
28
  var salt = parts[0];
20
29
  var hash = parts[1];
21
30
  try {
22
- var derived = scryptSync(passphrase, salt, 64);
31
+ var derived = await scryptAsync(passphrase, salt, 64);
23
32
  var expected = Buffer.from(hash, "hex");
24
33
  if (derived.length !== expected.length) {
25
34
  return false;
@@ -58,7 +67,7 @@ export function clearSessions(): void {
58
67
  activeSessions.clear();
59
68
  }
60
69
 
61
- setInterval(function () {
70
+ var cleanupInterval = setInterval(function () {
62
71
  var now = Date.now();
63
72
  activeSessions.forEach(function (createdAt, token) {
64
73
  if (now - createdAt > TOKEN_TTL) {
@@ -66,3 +75,4 @@ setInterval(function () {
66
75
  }
67
76
  });
68
77
  }, CLEANUP_INTERVAL);
78
+ cleanupInterval.unref();
@@ -1,8 +1,8 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { homedir, hostname } from "node:os";
3
3
  import { join } from "node:path";
4
- import { DEFAULT_PORT, LATTICE_HOME_DIR } from "@lattice/shared";
5
- import type { LatticeConfig } from "@lattice/shared";
4
+ import { DEFAULT_PORT, LATTICE_HOME_DIR } from "#shared";
5
+ import type { LatticeConfig } from "#shared";
6
6
 
7
7
  var home = process.env.LATTICE_HOME || join(homedir(), LATTICE_HOME_DIR);
8
8
  var cachedConfig: LatticeConfig | null = null;