@cryptiklemur/lattice 0.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 (162) hide show
  1. package/.editorconfig +12 -0
  2. package/.github/workflows/release.yml +44 -0
  3. package/.impeccable.md +66 -0
  4. package/.releaserc.json +32 -0
  5. package/.serena/project.yml +138 -0
  6. package/CLAUDE.md +35 -0
  7. package/CONTRIBUTING.md +93 -0
  8. package/LICENSE +21 -0
  9. package/README.md +83 -0
  10. package/bun.lock +1459 -0
  11. package/bunfig.toml +2 -0
  12. package/client/index.html +32 -0
  13. package/client/package.json +37 -0
  14. package/client/public/icons/icon-192.svg +11 -0
  15. package/client/public/icons/icon-512.svg +11 -0
  16. package/client/public/manifest.json +24 -0
  17. package/client/public/sw.js +61 -0
  18. package/client/src/App.tsx +28 -0
  19. package/client/src/components/auth/PassphrasePrompt.tsx +70 -0
  20. package/client/src/components/chat/ChatInput.tsx +241 -0
  21. package/client/src/components/chat/ChatView.tsx +727 -0
  22. package/client/src/components/chat/Message.tsx +362 -0
  23. package/client/src/components/chat/ModelSelector.tsx +87 -0
  24. package/client/src/components/chat/PermissionModeSelector.tsx +41 -0
  25. package/client/src/components/chat/StatusBar.tsx +50 -0
  26. package/client/src/components/chat/ToolGroup.tsx +129 -0
  27. package/client/src/components/chat/ToolResultRenderer.tsx +343 -0
  28. package/client/src/components/chat/toolSummary.ts +41 -0
  29. package/client/src/components/dashboard/DashboardView.tsx +219 -0
  30. package/client/src/components/dashboard/ProjectDashboardView.tsx +168 -0
  31. package/client/src/components/mesh/NodeBadge.tsx +24 -0
  32. package/client/src/components/mesh/PairingDialog.tsx +281 -0
  33. package/client/src/components/panels/FileBrowser.tsx +241 -0
  34. package/client/src/components/panels/StickyNotes.tsx +187 -0
  35. package/client/src/components/panels/Terminal.tsx +128 -0
  36. package/client/src/components/project-settings/ProjectClaude.tsx +304 -0
  37. package/client/src/components/project-settings/ProjectEnvironment.tsx +235 -0
  38. package/client/src/components/project-settings/ProjectGeneral.tsx +76 -0
  39. package/client/src/components/project-settings/ProjectMcp.tsx +232 -0
  40. package/client/src/components/project-settings/ProjectPermissions.tsx +209 -0
  41. package/client/src/components/project-settings/ProjectRules.tsx +277 -0
  42. package/client/src/components/project-settings/ProjectSettingsView.tsx +99 -0
  43. package/client/src/components/project-settings/ProjectSkills.tsx +91 -0
  44. package/client/src/components/settings/Appearance.tsx +151 -0
  45. package/client/src/components/settings/ClaudeSettings.tsx +151 -0
  46. package/client/src/components/settings/Environment.tsx +185 -0
  47. package/client/src/components/settings/GlobalMcp.tsx +207 -0
  48. package/client/src/components/settings/GlobalSkills.tsx +125 -0
  49. package/client/src/components/settings/MeshStatus.tsx +145 -0
  50. package/client/src/components/settings/SettingsView.tsx +57 -0
  51. package/client/src/components/settings/SkillMarketplace.tsx +175 -0
  52. package/client/src/components/settings/mcp-shared.tsx +194 -0
  53. package/client/src/components/settings/skill-shared.tsx +177 -0
  54. package/client/src/components/setup/SetupWizard.tsx +750 -0
  55. package/client/src/components/sidebar/NodeSettingsModal.tsx +180 -0
  56. package/client/src/components/sidebar/ProjectDropdown.tsx +43 -0
  57. package/client/src/components/sidebar/ProjectRail.tsx +291 -0
  58. package/client/src/components/sidebar/SearchFilter.tsx +52 -0
  59. package/client/src/components/sidebar/SessionList.tsx +384 -0
  60. package/client/src/components/sidebar/SettingsSidebar.tsx +128 -0
  61. package/client/src/components/sidebar/Sidebar.tsx +209 -0
  62. package/client/src/components/sidebar/UserIsland.tsx +59 -0
  63. package/client/src/components/sidebar/UserMenu.tsx +101 -0
  64. package/client/src/components/ui/CommandPalette.tsx +321 -0
  65. package/client/src/components/ui/ErrorBoundary.tsx +56 -0
  66. package/client/src/components/ui/IconPicker.tsx +209 -0
  67. package/client/src/components/ui/LatticeLogomark.tsx +19 -0
  68. package/client/src/components/ui/PopupMenu.tsx +98 -0
  69. package/client/src/components/ui/SaveFooter.tsx +38 -0
  70. package/client/src/components/ui/Toast.tsx +112 -0
  71. package/client/src/hooks/useMesh.ts +89 -0
  72. package/client/src/hooks/useProjectSettings.ts +56 -0
  73. package/client/src/hooks/useProjects.ts +66 -0
  74. package/client/src/hooks/useSaveState.ts +59 -0
  75. package/client/src/hooks/useSession.ts +317 -0
  76. package/client/src/hooks/useSidebar.ts +74 -0
  77. package/client/src/hooks/useSkills.ts +30 -0
  78. package/client/src/hooks/useTheme.ts +114 -0
  79. package/client/src/hooks/useWebSocket.ts +26 -0
  80. package/client/src/main.tsx +10 -0
  81. package/client/src/providers/WebSocketProvider.tsx +146 -0
  82. package/client/src/router.tsx +391 -0
  83. package/client/src/stores/mesh.ts +78 -0
  84. package/client/src/stores/session.ts +322 -0
  85. package/client/src/stores/sidebar.ts +336 -0
  86. package/client/src/stores/theme.ts +44 -0
  87. package/client/src/styles/global.css +167 -0
  88. package/client/src/styles/theme-vars.css +18 -0
  89. package/client/src/themes/index.ts +79 -0
  90. package/client/src/utils/findDuplicateKeys.ts +12 -0
  91. package/client/tsconfig.json +14 -0
  92. package/client/vite.config.ts +20 -0
  93. package/package.json +46 -0
  94. package/server/package.json +22 -0
  95. package/server/src/auth/passphrase.ts +48 -0
  96. package/server/src/config.ts +55 -0
  97. package/server/src/daemon.ts +338 -0
  98. package/server/src/features/ralph-loop.ts +173 -0
  99. package/server/src/features/scheduler.ts +281 -0
  100. package/server/src/features/sticky-notes.ts +102 -0
  101. package/server/src/handlers/chat.ts +194 -0
  102. package/server/src/handlers/fs.ts +84 -0
  103. package/server/src/handlers/loop.ts +37 -0
  104. package/server/src/handlers/mesh.ts +125 -0
  105. package/server/src/handlers/notes.ts +45 -0
  106. package/server/src/handlers/project-settings.ts +174 -0
  107. package/server/src/handlers/scheduler.ts +47 -0
  108. package/server/src/handlers/session.ts +159 -0
  109. package/server/src/handlers/settings.ts +109 -0
  110. package/server/src/handlers/skills.ts +380 -0
  111. package/server/src/handlers/terminal.ts +70 -0
  112. package/server/src/identity.ts +26 -0
  113. package/server/src/index.ts +190 -0
  114. package/server/src/mesh/connector.ts +209 -0
  115. package/server/src/mesh/discovery.ts +123 -0
  116. package/server/src/mesh/pairing.ts +94 -0
  117. package/server/src/mesh/peers.ts +52 -0
  118. package/server/src/mesh/proxy.ts +103 -0
  119. package/server/src/mesh/session-sync.ts +107 -0
  120. package/server/src/project/context-breakdown.ts +289 -0
  121. package/server/src/project/file-browser.ts +106 -0
  122. package/server/src/project/project-files.ts +267 -0
  123. package/server/src/project/registry.ts +57 -0
  124. package/server/src/project/sdk-bridge.ts +566 -0
  125. package/server/src/project/session.ts +432 -0
  126. package/server/src/project/terminal.ts +69 -0
  127. package/server/src/tls.ts +51 -0
  128. package/server/src/ws/broadcast.ts +31 -0
  129. package/server/src/ws/router.ts +104 -0
  130. package/server/src/ws/server.ts +2 -0
  131. package/server/tsconfig.json +16 -0
  132. package/shared/package.json +11 -0
  133. package/shared/src/constants.ts +7 -0
  134. package/shared/src/index.ts +4 -0
  135. package/shared/src/messages.ts +638 -0
  136. package/shared/src/models.ts +136 -0
  137. package/shared/src/project-settings.ts +45 -0
  138. package/shared/tsconfig.json +11 -0
  139. package/themes/amoled.json +20 -0
  140. package/themes/ayu-light.json +9 -0
  141. package/themes/catppuccin-latte.json +9 -0
  142. package/themes/catppuccin-mocha.json +9 -0
  143. package/themes/clay-light.json +10 -0
  144. package/themes/clay.json +10 -0
  145. package/themes/dracula.json +9 -0
  146. package/themes/everforest-light.json +9 -0
  147. package/themes/everforest.json +9 -0
  148. package/themes/github-light.json +9 -0
  149. package/themes/gruvbox-dark.json +9 -0
  150. package/themes/gruvbox-light.json +9 -0
  151. package/themes/monokai.json +9 -0
  152. package/themes/nord-light.json +9 -0
  153. package/themes/nord.json +9 -0
  154. package/themes/one-dark.json +9 -0
  155. package/themes/one-light.json +9 -0
  156. package/themes/rose-pine-dawn.json +9 -0
  157. package/themes/rose-pine.json +9 -0
  158. package/themes/solarized-dark.json +9 -0
  159. package/themes/solarized-light.json +9 -0
  160. package/themes/tokyo-night-light.json +9 -0
  161. package/themes/tokyo-night.json +9 -0
  162. package/tsconfig.json +26 -0
@@ -0,0 +1,66 @@
1
+ import { useEffect, useRef, useState } from "react";
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";
6
+ import { useWebSocket } from "./useWebSocket";
7
+ import { setActiveProjectSlug, getSidebarStore } from "../stores/sidebar";
8
+
9
+ export interface UseProjectsResult {
10
+ projects: ProjectInfo[];
11
+ activeProject: ProjectInfo | null;
12
+ setActiveProject: (project: ProjectInfo | null) => void;
13
+ }
14
+
15
+ export function useProjects(): UseProjectsResult {
16
+ var ws = useWebSocket();
17
+ var [projects, setProjects] = useState<ProjectInfo[]>([]);
18
+ var activeProjectSlug = useStore(getSidebarStore(), function (state) { return state.activeProjectSlug; });
19
+
20
+ var handleRef = useRef<(msg: ServerMessage) => void>(function () {});
21
+
22
+ useEffect(function () {
23
+ handleRef.current = function (msg: ServerMessage) {
24
+ if (msg.type === "projects:list") {
25
+ var list = (msg as ProjectsListMessage).projects;
26
+ setProjects(list);
27
+ var storeState = getSidebarStore().state;
28
+ var currentSlug = storeState.activeProjectSlug;
29
+ if (currentSlug !== null) {
30
+ var found = list.find(function (p) { return p.slug === currentSlug; });
31
+ if (!found && list.length > 0) {
32
+ setActiveProjectSlug(list[0].slug);
33
+ }
34
+ }
35
+ }
36
+ };
37
+ });
38
+
39
+ useEffect(function () {
40
+ function handler(msg: ServerMessage) {
41
+ handleRef.current(msg);
42
+ }
43
+
44
+ ws.subscribe("projects:list", handler);
45
+
46
+ return function () {
47
+ ws.unsubscribe("projects:list", handler);
48
+ };
49
+ }, [ws]);
50
+
51
+ useEffect(function () {
52
+ if (ws.status === "connected") {
53
+ ws.send({ type: "settings:get" });
54
+ }
55
+ }, [ws.status, ws]);
56
+
57
+ var activeProject = activeProjectSlug !== null
58
+ ? (projects.find(function (p) { return p.slug === activeProjectSlug; }) ?? null)
59
+ : null;
60
+
61
+ function setActiveProject(project: ProjectInfo | null): void {
62
+ setActiveProjectSlug(project ? project.slug : null);
63
+ }
64
+
65
+ return { projects, activeProject, setActiveProject };
66
+ }
@@ -0,0 +1,59 @@
1
+ import { useState, useRef, useEffect } from "react";
2
+
3
+ export type SaveState = "idle" | "saved" | "error";
4
+
5
+ export interface UseSaveStateReturn {
6
+ dirty: boolean;
7
+ saving: boolean;
8
+ saveState: SaveState;
9
+ markDirty: () => void;
10
+ startSave: () => void;
11
+ confirmSave: () => void;
12
+ resetFromServer: () => void;
13
+ }
14
+
15
+ export function useSaveState(): UseSaveStateReturn {
16
+ var [dirty, setDirty] = useState(false);
17
+ var [saving, setSaving] = useState(false);
18
+ var [saveState, setSaveState] = useState<SaveState>("idle");
19
+ var saveTimeout = useRef<ReturnType<typeof setTimeout> | null>(null);
20
+
21
+ useEffect(function () {
22
+ return function () {
23
+ if (saveTimeout.current) clearTimeout(saveTimeout.current);
24
+ };
25
+ }, []);
26
+
27
+ function markDirty() {
28
+ setDirty(true);
29
+ setSaveState("idle");
30
+ }
31
+
32
+ function startSave() {
33
+ setSaving(true);
34
+ setSaveState("idle");
35
+
36
+ if (saveTimeout.current) clearTimeout(saveTimeout.current);
37
+ saveTimeout.current = setTimeout(function () {
38
+ setSaving(false);
39
+ setSaveState("error");
40
+ setTimeout(function () { setSaveState("idle"); }, 3000);
41
+ }, 5000);
42
+ }
43
+
44
+ function confirmSave() {
45
+ setSaving(false);
46
+ setSaveState("saved");
47
+ setDirty(false);
48
+ if (saveTimeout.current) clearTimeout(saveTimeout.current);
49
+ saveTimeout.current = setTimeout(function () { setSaveState("idle"); }, 1800);
50
+ }
51
+
52
+ function resetFromServer() {
53
+ setDirty(false);
54
+ setSaveState("idle");
55
+ setSaving(false);
56
+ }
57
+
58
+ return { dirty, saving, saveState, markDirty, startSave, confirmSave, resetFromServer };
59
+ }
@@ -0,0 +1,317 @@
1
+ import { useEffect, useRef } from "react";
2
+ import { useStore } from "@tanstack/react-store";
3
+ import type { HistoryMessage } from "@lattice/shared";
4
+ import type {
5
+ ChatDeltaMessage,
6
+ ChatSendMessage,
7
+ ChatToolStartMessage,
8
+ ChatToolResultMessage,
9
+ ChatPermissionRequestMessage,
10
+ ChatPermissionResolvedMessage,
11
+ ChatUserMessage,
12
+ ChatStatusMessage,
13
+ ChatContextUsageMessage,
14
+ ChatContextBreakdownMessage,
15
+ SessionHistoryMessage,
16
+ ServerMessage,
17
+ } from "@lattice/shared";
18
+ import { useWebSocket } from "./useWebSocket";
19
+ import { setActiveSessionId as setSidebarSessionId } from "../stores/sidebar";
20
+ import {
21
+ getSessionStore,
22
+ setSessionMessages,
23
+ addSessionMessage,
24
+ updateLastAssistantMessage,
25
+ updateToolResult,
26
+ setIsProcessing,
27
+ setActiveSession,
28
+ setSessionTitle,
29
+ setCurrentStatus,
30
+ setContextUsage,
31
+ setContextBreakdown,
32
+ getCurrentAssistantUuid,
33
+ setCurrentAssistantUuid,
34
+ incrementPendingPermissions,
35
+ updatePermissionStatus,
36
+ setLastResponseStats,
37
+ getLastReadIndex,
38
+ setLastReadIndex,
39
+ markSessionRead,
40
+ getStreamGeneration,
41
+ mergeToolResults,
42
+ setWasInterrupted,
43
+ } from "../stores/session";
44
+ import type { SessionState } from "../stores/session";
45
+
46
+ var subscriptionsActive = 0;
47
+ var activeStreamGeneration = 0;
48
+
49
+ export type { SessionState };
50
+
51
+ export interface UseSessionReturn extends SessionState {
52
+ sendMessage: (text: string, model?: string, effort?: string) => void;
53
+ activateSession: (projectSlug: string, sessionId: string) => void;
54
+ lastReadIndex: number | null;
55
+ }
56
+
57
+ export function useSession(): UseSessionReturn {
58
+ var store = getSessionStore();
59
+ var state = useStore(store, function (s) { return s; });
60
+ var { send, subscribe, unsubscribe } = useWebSocket();
61
+ var sendRef = useRef(send);
62
+ sendRef.current = send;
63
+
64
+ function activateSession(projectSlug: string, sessionId: string) {
65
+ setActiveSession(projectSlug, sessionId);
66
+ setSidebarSessionId(sessionId);
67
+ sendRef.current({ type: "session:activate", projectSlug, sessionId });
68
+ }
69
+
70
+ function sendMessage(text: string, model?: string, effort?: string) {
71
+ var currentSessionId = getSessionStore().state.activeSessionId;
72
+ if (!currentSessionId || !text.trim()) {
73
+ return;
74
+ }
75
+ var msg = { type: "chat:send" as const, text: text } as ChatSendMessage & { model?: string; effort?: string };
76
+ if (model && model !== "default") {
77
+ msg.model = model;
78
+ }
79
+ if (effort) {
80
+ msg.effort = effort;
81
+ }
82
+ activeStreamGeneration = getStreamGeneration();
83
+ sendRef.current(msg as ChatSendMessage);
84
+ setIsProcessing(true);
85
+ }
86
+
87
+ useEffect(function () {
88
+ subscriptionsActive++;
89
+ if (subscriptionsActive > 1) {
90
+ return function () { subscriptionsActive--; };
91
+ }
92
+
93
+ function isStaleStream(): boolean {
94
+ return activeStreamGeneration !== getStreamGeneration();
95
+ }
96
+
97
+ function handleUserMessage(msg: ServerMessage) {
98
+ if (isStaleStream()) return;
99
+ var m = msg as ChatUserMessage;
100
+ setCurrentAssistantUuid(null);
101
+ addSessionMessage({
102
+ type: "user",
103
+ uuid: m.uuid,
104
+ text: m.text,
105
+ timestamp: Date.now(),
106
+ } as HistoryMessage);
107
+ }
108
+
109
+ function handleDelta(msg: ServerMessage) {
110
+ if (isStaleStream()) return;
111
+ var m = msg as ChatDeltaMessage;
112
+ var uuid = getCurrentAssistantUuid();
113
+
114
+ if (!uuid) {
115
+ var newUuid = "assistant-" + Date.now();
116
+ setCurrentAssistantUuid(newUuid);
117
+ addSessionMessage({
118
+ type: "assistant",
119
+ uuid: newUuid,
120
+ text: m.text,
121
+ timestamp: Date.now(),
122
+ } as HistoryMessage);
123
+ } else {
124
+ updateLastAssistantMessage(uuid, m.text);
125
+ }
126
+ }
127
+
128
+ function handleToolStart(msg: ServerMessage) {
129
+ if (isStaleStream()) return;
130
+ var m = msg as ChatToolStartMessage;
131
+ setCurrentAssistantUuid(null);
132
+ addSessionMessage({
133
+ type: "tool_start",
134
+ toolId: m.toolId,
135
+ name: m.name,
136
+ args: m.args,
137
+ timestamp: Date.now(),
138
+ } as HistoryMessage);
139
+ }
140
+
141
+ function handleToolResult(msg: ServerMessage) {
142
+ if (isStaleStream()) return;
143
+ var m = msg as ChatToolResultMessage;
144
+ updateToolResult(m.toolId, m.content);
145
+ }
146
+
147
+ function handleDone(msg: ServerMessage) {
148
+ if (isStaleStream()) return;
149
+ var m = msg as { type: string; cost: number; duration: number; sessionId?: string };
150
+ setIsProcessing(false);
151
+ setCurrentStatus(null);
152
+ setCurrentAssistantUuid(null);
153
+ setLastResponseStats(m.cost || 0, m.duration || 0);
154
+ var activeId = getSessionStore().state.activeSessionId;
155
+ if (activeId) {
156
+ markSessionRead(activeId, getSessionStore().state.messages.length);
157
+ }
158
+ }
159
+
160
+ function handleError(msg: ServerMessage) {
161
+ if (isStaleStream()) return;
162
+ var m = msg as { type: string; message?: string };
163
+ setIsProcessing(false);
164
+ setCurrentStatus(null);
165
+ setCurrentAssistantUuid(null);
166
+ if (m.message) {
167
+ addSessionMessage({
168
+ type: "assistant",
169
+ uuid: "error-" + Date.now(),
170
+ text: "Error: " + m.message,
171
+ timestamp: Date.now(),
172
+ } as HistoryMessage);
173
+ }
174
+ }
175
+
176
+ function handleStatus(msg: ServerMessage) {
177
+ if (isStaleStream()) return;
178
+ var m = msg as ChatStatusMessage;
179
+ setCurrentStatus({ phase: m.phase, toolName: m.toolName, elapsed: m.elapsed, summary: m.summary });
180
+ }
181
+
182
+ function handleContextUsage(msg: ServerMessage) {
183
+ var m = msg as ChatContextUsageMessage;
184
+ setContextUsage({
185
+ inputTokens: m.inputTokens,
186
+ outputTokens: m.outputTokens,
187
+ cacheReadTokens: m.cacheReadTokens,
188
+ cacheCreationTokens: m.cacheCreationTokens,
189
+ contextWindow: m.contextWindow,
190
+ });
191
+ }
192
+
193
+ function handleContextBreakdown(msg: ServerMessage) {
194
+ var m = msg as ChatContextBreakdownMessage;
195
+ setContextBreakdown({
196
+ segments: m.segments,
197
+ contextWindow: m.contextWindow,
198
+ autocompactAt: m.autocompactAt,
199
+ });
200
+ }
201
+
202
+ function handlePermissionRequest(msg: ServerMessage) {
203
+ var m = msg as ChatPermissionRequestMessage;
204
+ setCurrentAssistantUuid(null);
205
+ addSessionMessage({
206
+ type: "permission_request",
207
+ toolId: m.requestId,
208
+ name: m.tool,
209
+ args: m.args,
210
+ title: m.title,
211
+ decisionReason: m.decisionReason,
212
+ permissionRule: m.permissionRule,
213
+ permissionStatus: "pending",
214
+ timestamp: Date.now(),
215
+ } as HistoryMessage);
216
+ incrementPendingPermissions();
217
+ }
218
+
219
+ function handlePermissionResolved(msg: ServerMessage) {
220
+ var m = msg as ChatPermissionResolvedMessage;
221
+ updatePermissionStatus(m.requestId, m.status);
222
+ }
223
+
224
+ function handleHistory(msg: ServerMessage) {
225
+ var m = msg as SessionHistoryMessage;
226
+ setCurrentAssistantUuid(null);
227
+ if (m.sessionId) {
228
+ var projectSlug = m.projectSlug || getSessionStore().state.activeProjectSlug;
229
+ setSidebarSessionId(m.sessionId);
230
+ activeStreamGeneration = getStreamGeneration();
231
+ getSessionStore().setState(function (state) {
232
+ return {
233
+ ...state,
234
+ activeProjectSlug: projectSlug,
235
+ activeSessionId: m.sessionId,
236
+ activeSessionTitle: m.title ?? null,
237
+ messages: mergeToolResults(m.messages),
238
+ isProcessing: false,
239
+ currentStatus: null,
240
+ pendingPermissionCount: 0,
241
+ lastResponseCost: null,
242
+ lastResponseDuration: null,
243
+ lastReadIndex: null,
244
+ historyLoading: false,
245
+ wasInterrupted: m.interrupted || false,
246
+ };
247
+ });
248
+ var storedIndex = getLastReadIndex(m.sessionId);
249
+ if (storedIndex >= 0 && storedIndex < m.messages.length) {
250
+ setLastReadIndex(storedIndex);
251
+ }
252
+ markSessionRead(m.sessionId, m.messages.length);
253
+ } else {
254
+ setSessionMessages(m.messages);
255
+ setIsProcessing(false);
256
+ setCurrentStatus(null);
257
+ if (m.projectSlug) {
258
+ getSessionStore().setState(function (state) {
259
+ return { ...state, activeProjectSlug: m.projectSlug };
260
+ });
261
+ }
262
+ if (m.title) {
263
+ setSessionTitle(m.title);
264
+ }
265
+ }
266
+ setSessionMessages(m.messages);
267
+ }
268
+
269
+ subscribe("chat:user_message", handleUserMessage);
270
+ subscribe("chat:delta", handleDelta);
271
+ subscribe("chat:tool_start", handleToolStart);
272
+ subscribe("chat:tool_result", handleToolResult);
273
+ subscribe("chat:done", handleDone);
274
+ subscribe("chat:error", handleError);
275
+ subscribe("chat:permission_request", handlePermissionRequest);
276
+ subscribe("chat:permission_resolved", handlePermissionResolved);
277
+ subscribe("chat:status", handleStatus);
278
+ subscribe("chat:context_usage", handleContextUsage);
279
+ subscribe("chat:context_breakdown", handleContextBreakdown);
280
+ subscribe("session:history", handleHistory);
281
+
282
+ return function () {
283
+ subscriptionsActive--;
284
+ unsubscribe("chat:user_message", handleUserMessage);
285
+ unsubscribe("chat:delta", handleDelta);
286
+ unsubscribe("chat:tool_start", handleToolStart);
287
+ unsubscribe("chat:tool_result", handleToolResult);
288
+ unsubscribe("chat:done", handleDone);
289
+ unsubscribe("chat:error", handleError);
290
+ unsubscribe("chat:permission_request", handlePermissionRequest);
291
+ unsubscribe("chat:permission_resolved", handlePermissionResolved);
292
+ unsubscribe("chat:status", handleStatus);
293
+ unsubscribe("chat:context_usage", handleContextUsage);
294
+ unsubscribe("chat:context_breakdown", handleContextBreakdown);
295
+ unsubscribe("session:history", handleHistory);
296
+ };
297
+ }, [subscribe, unsubscribe]);
298
+
299
+ return {
300
+ messages: state.messages,
301
+ isProcessing: state.isProcessing,
302
+ activeProjectSlug: state.activeProjectSlug,
303
+ activeSessionId: state.activeSessionId,
304
+ activeSessionTitle: state.activeSessionTitle,
305
+ sendMessage,
306
+ activateSession,
307
+ currentStatus: state.currentStatus,
308
+ contextUsage: state.contextUsage,
309
+ contextBreakdown: state.contextBreakdown,
310
+ pendingPermissionCount: state.pendingPermissionCount,
311
+ lastResponseCost: state.lastResponseCost,
312
+ lastResponseDuration: state.lastResponseDuration,
313
+ lastReadIndex: state.lastReadIndex,
314
+ historyLoading: state.historyLoading,
315
+ wasInterrupted: state.wasInterrupted,
316
+ };
317
+ }
@@ -0,0 +1,74 @@
1
+ import { useStore } from "@tanstack/react-store";
2
+ import {
3
+ getSidebarStore,
4
+ setActiveProjectSlug,
5
+ setActiveSessionId,
6
+ openSettings,
7
+ setSettingsSection,
8
+ openProjectSettings,
9
+ setProjectSettingsSection,
10
+ exitSettings,
11
+ toggleUserMenu,
12
+ toggleProjectDropdown,
13
+ closeMenus,
14
+ goToProjectDashboard,
15
+ goToDashboard,
16
+ toggleDrawer,
17
+ closeDrawer,
18
+ openNodeSettings,
19
+ closeNodeSettings,
20
+ navigateToSession,
21
+ } from "../stores/sidebar";
22
+ import type { SidebarState, SettingsSection, ProjectSettingsSection } from "../stores/sidebar";
23
+
24
+ export function useSidebar(): SidebarState & {
25
+ setActiveProjectSlug: (slug: string | null) => void;
26
+ setActiveSessionId: (sessionId: string | null) => void;
27
+ openSettings: (section: SettingsSection) => void;
28
+ setSettingsSection: (section: SettingsSection) => void;
29
+ openProjectSettings: (section: ProjectSettingsSection) => void;
30
+ setProjectSettingsSection: (section: ProjectSettingsSection) => void;
31
+ exitSettings: () => void;
32
+ toggleUserMenu: () => void;
33
+ toggleProjectDropdown: () => void;
34
+ closeMenus: () => void;
35
+ goToProjectDashboard: () => void;
36
+ goToDashboard: () => void;
37
+ toggleDrawer: () => void;
38
+ closeDrawer: () => void;
39
+ openNodeSettings: () => void;
40
+ closeNodeSettings: () => void;
41
+ navigateToSession: (projectSlug: string, sessionId: string) => void;
42
+ } {
43
+ var store = getSidebarStore();
44
+ var state = useStore(store, function (s) { return s; });
45
+
46
+ return {
47
+ activeProjectSlug: state.activeProjectSlug,
48
+ activeSessionId: state.activeSessionId,
49
+ sidebarMode: state.sidebarMode,
50
+ activeView: state.activeView,
51
+ previousView: state.previousView,
52
+ userMenuOpen: state.userMenuOpen,
53
+ projectDropdownOpen: state.projectDropdownOpen,
54
+ drawerOpen: state.drawerOpen,
55
+ setActiveProjectSlug: setActiveProjectSlug,
56
+ setActiveSessionId: setActiveSessionId,
57
+ openSettings: openSettings,
58
+ setSettingsSection: setSettingsSection,
59
+ openProjectSettings: openProjectSettings,
60
+ setProjectSettingsSection: setProjectSettingsSection,
61
+ exitSettings: exitSettings,
62
+ toggleUserMenu: toggleUserMenu,
63
+ toggleProjectDropdown: toggleProjectDropdown,
64
+ closeMenus: closeMenus,
65
+ goToProjectDashboard: goToProjectDashboard,
66
+ goToDashboard: goToDashboard,
67
+ toggleDrawer: toggleDrawer,
68
+ closeDrawer: closeDrawer,
69
+ navigateToSession: navigateToSession,
70
+ openNodeSettings: openNodeSettings,
71
+ closeNodeSettings: closeNodeSettings,
72
+ nodeSettingsOpen: state.nodeSettingsOpen,
73
+ };
74
+ }
@@ -0,0 +1,30 @@
1
+ import { useEffect, useState } from "react";
2
+ import type { SkillInfo } from "@lattice/shared";
3
+ import type { ServerMessage } from "@lattice/shared";
4
+ import { useWebSocket } from "./useWebSocket";
5
+
6
+ export function useSkills(): SkillInfo[] {
7
+ var ws = useWebSocket();
8
+ var [skills, setSkills] = useState<SkillInfo[]>([]);
9
+
10
+ useEffect(function () {
11
+ function handleSkillsList(msg: ServerMessage) {
12
+ if (msg.type === "skills:list") {
13
+ var listMsg = msg as { type: string; skills: SkillInfo[] };
14
+ setSkills(listMsg.skills);
15
+ }
16
+ }
17
+ ws.subscribe("skills:list", handleSkillsList);
18
+ return function () {
19
+ ws.unsubscribe("skills:list", handleSkillsList);
20
+ };
21
+ }, [ws]);
22
+
23
+ useEffect(function () {
24
+ if (ws.status === "connected") {
25
+ ws.send({ type: "skills:list_request" });
26
+ }
27
+ }, [ws.status, ws]);
28
+
29
+ return skills;
30
+ }
@@ -0,0 +1,114 @@
1
+ import { useStore } from "@tanstack/react-store";
2
+ import { useEffect } from "react";
3
+ import { getThemeStore, toggleMode, setThemeForMode } from "../stores/theme";
4
+ import { themes, type ThemeEntry } from "../themes/index";
5
+
6
+ function hexToOklch(hex: string): string {
7
+ var r = parseInt(hex.slice(0, 2), 16) / 255;
8
+ var g = parseInt(hex.slice(2, 4), 16) / 255;
9
+ var b = parseInt(hex.slice(4, 6), 16) / 255;
10
+
11
+ r = r > 0.04045 ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92;
12
+ g = g > 0.04045 ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92;
13
+ b = b > 0.04045 ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92;
14
+
15
+ var x = 0.4124564 * r + 0.3575761 * g + 0.1804375 * b;
16
+ var y = 0.2126729 * r + 0.7151522 * g + 0.0721750 * b;
17
+ var z = 0.0193339 * r + 0.1191920 * g + 0.9503041 * b;
18
+
19
+ var l_ = 0.8189330101 * x + 0.3618667424 * y - 0.1288597137 * z;
20
+ var m_ = 0.0329845436 * x + 0.9293118715 * y + 0.0361456387 * z;
21
+ var s_ = 0.0482003018 * x + 0.2643662691 * y + 0.6338517070 * z;
22
+
23
+ var l3 = Math.cbrt(l_);
24
+ var m3 = Math.cbrt(m_);
25
+ var s3 = Math.cbrt(s_);
26
+
27
+ var L = 0.2104542553 * l3 + 0.7936177850 * m3 - 0.0040720468 * s3;
28
+ var a = 1.9779984951 * l3 - 2.4285922050 * m3 + 0.4505937099 * s3;
29
+ var bOk = 0.0259040371 * l3 + 0.7827717662 * m3 - 0.8086757660 * s3;
30
+
31
+ var C = Math.sqrt(a * a + bOk * bOk);
32
+ var h = Math.atan2(bOk, a) * 180 / Math.PI;
33
+ if (h < 0) h += 360;
34
+
35
+ return (L * 100).toFixed(1) + "% " + C.toFixed(3) + " " + h.toFixed(0);
36
+ }
37
+
38
+ function contrastContent(hex: string): string {
39
+ var r = parseInt(hex.slice(0, 2), 16);
40
+ var g = parseInt(hex.slice(2, 4), 16);
41
+ var b = parseInt(hex.slice(4, 6), 16);
42
+ var lum = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
43
+ return lum > 0.5 ? "15% 0.01 0" : "98% 0.01 0";
44
+ }
45
+
46
+ function applyTheme(entry: ThemeEntry): void {
47
+ var root = document.documentElement;
48
+ var t = entry.theme;
49
+
50
+ root.style.setProperty("--base00", "#" + t.base00);
51
+ root.style.setProperty("--base01", "#" + t.base01);
52
+ root.style.setProperty("--base02", "#" + t.base02);
53
+ root.style.setProperty("--base03", "#" + t.base03);
54
+ root.style.setProperty("--base04", "#" + t.base04);
55
+ root.style.setProperty("--base05", "#" + t.base05);
56
+ root.style.setProperty("--base06", "#" + t.base06);
57
+ root.style.setProperty("--base07", "#" + t.base07);
58
+ root.style.setProperty("--base08", "#" + t.base08);
59
+ root.style.setProperty("--base09", "#" + t.base09);
60
+ root.style.setProperty("--base0A", "#" + t.base0A);
61
+ root.style.setProperty("--base0B", "#" + t.base0B);
62
+ root.style.setProperty("--base0C", "#" + t.base0C);
63
+ root.style.setProperty("--base0D", "#" + t.base0D);
64
+ root.style.setProperty("--base0E", "#" + t.base0E);
65
+ root.style.setProperty("--base0F", "#" + t.base0F);
66
+
67
+ root.style.setProperty("--color-base-100", "oklch(" + hexToOklch(t.base00) + ")");
68
+ root.style.setProperty("--color-base-200", "oklch(" + hexToOklch(t.base01) + ")");
69
+ root.style.setProperty("--color-base-300", "oklch(" + hexToOklch(t.base02) + ")");
70
+ root.style.setProperty("--color-base-content", "oklch(" + hexToOklch(t.base05) + ")");
71
+ root.style.setProperty("--color-primary", "oklch(" + hexToOklch(t.base0D) + ")");
72
+ root.style.setProperty("--color-primary-content", "oklch(" + contrastContent(t.base0D) + ")");
73
+ root.style.setProperty("--color-secondary", "oklch(" + hexToOklch(t.base0E) + ")");
74
+ root.style.setProperty("--color-secondary-content", "oklch(" + contrastContent(t.base0E) + ")");
75
+ root.style.setProperty("--color-accent", "oklch(" + hexToOklch(t.base0C) + ")");
76
+ root.style.setProperty("--color-accent-content", "oklch(" + contrastContent(t.base0C) + ")");
77
+ root.style.setProperty("--color-neutral", "oklch(" + hexToOklch(t.base02) + ")");
78
+ root.style.setProperty("--color-neutral-content", "oklch(" + hexToOklch(t.base04) + ")");
79
+ root.style.setProperty("--color-info", "oklch(" + hexToOklch(t.base0C) + ")");
80
+ root.style.setProperty("--color-info-content", "oklch(" + contrastContent(t.base0C) + ")");
81
+ root.style.setProperty("--color-success", "oklch(" + hexToOklch(t.base0B) + ")");
82
+ root.style.setProperty("--color-success-content", "oklch(" + contrastContent(t.base0B) + ")");
83
+ root.style.setProperty("--color-warning", "oklch(" + hexToOklch(t.base0A) + ")");
84
+ root.style.setProperty("--color-warning-content", "oklch(" + contrastContent(t.base0A) + ")");
85
+ root.style.setProperty("--color-error", "oklch(" + hexToOklch(t.base08) + ")");
86
+ root.style.setProperty("--color-error-content", "oklch(" + contrastContent(t.base08) + ")");
87
+
88
+ root.dataset.theme = entry.theme.variant === "dark" ? "lattice-dark" : "lattice-light";
89
+ root.style.colorScheme = entry.theme.variant === "dark" ? "dark" : "light";
90
+ }
91
+
92
+ export function useTheme() {
93
+ var store = getThemeStore();
94
+ var state = useStore(store, function (s) { return s; });
95
+
96
+ var currentThemeId = state.mode === "dark" ? state.darkThemeId : state.lightThemeId;
97
+ var currentEntry = themes.find(function (e) { return e.id === currentThemeId; }) ?? themes[0];
98
+
99
+ useEffect(function () {
100
+ applyTheme(currentEntry);
101
+ }, [currentEntry]);
102
+
103
+ function setTheme(themeId: string): void {
104
+ setThemeForMode(themeId);
105
+ }
106
+
107
+ return {
108
+ mode: state.mode,
109
+ currentThemeId,
110
+ toggleMode,
111
+ setTheme,
112
+ themes,
113
+ };
114
+ }
@@ -0,0 +1,26 @@
1
+ import { createContext, useContext } from "react";
2
+ import type { ClientMessage, ServerMessage } from "@lattice/shared";
3
+
4
+ export type WebSocketStatus = "connecting" | "connected" | "disconnected";
5
+
6
+ export interface WebSocketContextValue {
7
+ status: WebSocketStatus;
8
+ send: (msg: ClientMessage) => void;
9
+ subscribe: (type: string, callback: (msg: ServerMessage) => void) => void;
10
+ unsubscribe: (type: string, callback: (msg: ServerMessage) => void) => void;
11
+ }
12
+
13
+ export var WebSocketContext = createContext<WebSocketContextValue | null>(null);
14
+
15
+ export function useWebSocket(): WebSocketContextValue {
16
+ var ctx = useContext(WebSocketContext);
17
+ if (!ctx) {
18
+ throw new Error("useWebSocket must be used within a WebSocketProvider");
19
+ }
20
+ return ctx;
21
+ }
22
+
23
+ export function getWebSocketUrl(): string {
24
+ var protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
25
+ return protocol + "//" + window.location.host + "/ws";
26
+ }