@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,322 @@
1
+ import { Store } from "@tanstack/react-store";
2
+ import type { HistoryMessage } from "@lattice/shared";
3
+
4
+ export interface ContextUsage {
5
+ inputTokens: number;
6
+ outputTokens: number;
7
+ cacheReadTokens: number;
8
+ cacheCreationTokens: number;
9
+ contextWindow: number;
10
+ }
11
+
12
+ export interface ContextBreakdownSegment {
13
+ label: string;
14
+ tokens: number;
15
+ id: string;
16
+ estimated: boolean;
17
+ }
18
+
19
+ export interface ContextBreakdown {
20
+ segments: ContextBreakdownSegment[];
21
+ contextWindow: number;
22
+ autocompactAt: number;
23
+ }
24
+
25
+ export interface SessionState {
26
+ messages: HistoryMessage[];
27
+ isProcessing: boolean;
28
+ activeProjectSlug: string | null;
29
+ activeSessionId: string | null;
30
+ activeSessionTitle: string | null;
31
+ currentStatus: { phase: string; toolName?: string; elapsed?: number; summary?: string } | null;
32
+ contextUsage: ContextUsage | null;
33
+ contextBreakdown: ContextBreakdown | null;
34
+ pendingPermissionCount: number;
35
+ lastResponseCost: number | null;
36
+ lastResponseDuration: number | null;
37
+ lastReadIndex: number | null;
38
+ historyLoading: boolean;
39
+ wasInterrupted: boolean;
40
+ }
41
+
42
+ var sessionStore = new Store<SessionState>({
43
+ messages: [],
44
+ isProcessing: false,
45
+ activeProjectSlug: null,
46
+ activeSessionId: null,
47
+ activeSessionTitle: null,
48
+ currentStatus: null,
49
+ contextUsage: null,
50
+ contextBreakdown: null,
51
+ pendingPermissionCount: 0,
52
+ lastResponseCost: null,
53
+ lastResponseDuration: null,
54
+ lastReadIndex: null,
55
+ historyLoading: false,
56
+ wasInterrupted: false,
57
+ });
58
+
59
+ var streamGeneration = 0;
60
+
61
+ export function getStreamGeneration(): number {
62
+ return streamGeneration;
63
+ }
64
+
65
+ export function incrementStreamGeneration(): number {
66
+ streamGeneration++;
67
+ return streamGeneration;
68
+ }
69
+
70
+ var lastReadIndices = new Map<string, number>();
71
+ var sessionsWithUpdates = new Set<string>();
72
+
73
+ export function markSessionRead(sessionId: string, messageCount: number): void {
74
+ lastReadIndices.set(sessionId, messageCount);
75
+ sessionsWithUpdates.delete(sessionId);
76
+ }
77
+
78
+ export function getLastReadIndex(sessionId: string): number {
79
+ return lastReadIndices.get(sessionId) ?? -1;
80
+ }
81
+
82
+ export function markSessionHasUpdates(sessionId: string): void {
83
+ sessionsWithUpdates.add(sessionId);
84
+ }
85
+
86
+ export function sessionHasUpdates(sessionId: string): boolean {
87
+ return sessionsWithUpdates.has(sessionId);
88
+ }
89
+
90
+ export function setLastReadIndex(index: number): void {
91
+ sessionStore.setState(function (state) {
92
+ return { ...state, lastReadIndex: index };
93
+ });
94
+ }
95
+
96
+ var currentAssistantUuid: string | null = null;
97
+ var currentAssistantIndex: number = -1;
98
+
99
+ export function getSessionStore(): Store<SessionState> {
100
+ return sessionStore;
101
+ }
102
+
103
+ export function mergeToolResults(messages: HistoryMessage[]): HistoryMessage[] {
104
+ var result: HistoryMessage[] = [];
105
+ var toolStartMap = new Map<string, number>();
106
+ for (var i = 0; i < messages.length; i++) {
107
+ var msg = messages[i];
108
+ if (msg.type === "tool_start" && msg.toolId) {
109
+ toolStartMap.set(msg.toolId, result.length);
110
+ result.push({ ...msg });
111
+ } else if (msg.type === "tool_result" && msg.toolId) {
112
+ var startIdx = toolStartMap.get(msg.toolId);
113
+ if (startIdx !== undefined) {
114
+ result[startIdx] = { ...result[startIdx], content: msg.content };
115
+ }
116
+ } else {
117
+ result.push(msg);
118
+ }
119
+ }
120
+ for (var j = 0; j < result.length; j++) {
121
+ if (result[j].type === "tool_start" && !result[j].content) {
122
+ result[j] = { ...result[j], content: "(no output)" };
123
+ }
124
+ }
125
+ return result;
126
+ }
127
+
128
+ export function setSessionMessages(messages: HistoryMessage[]): void {
129
+ currentAssistantIndex = -1;
130
+ var merged = mergeToolResults(messages);
131
+ sessionStore.setState(function (state) {
132
+ return { ...state, messages: merged };
133
+ });
134
+ }
135
+
136
+ export function addSessionMessage(message: HistoryMessage): void {
137
+ sessionStore.setState(function (state) {
138
+ var newMessages = [...state.messages, message];
139
+ if (message.type === "assistant" && message.uuid) {
140
+ currentAssistantIndex = newMessages.length - 1;
141
+ }
142
+ return { ...state, messages: newMessages };
143
+ });
144
+ }
145
+
146
+ export function updateLastAssistantMessage(uuid: string, deltaText: string): void {
147
+ sessionStore.setState(function (state) {
148
+ var idx = currentAssistantIndex;
149
+ if (idx === -1 || idx >= state.messages.length || state.messages[idx].uuid !== uuid) {
150
+ idx = state.messages.findLastIndex(function (msg) {
151
+ return msg.uuid === uuid;
152
+ });
153
+ if (idx === -1) {
154
+ return state;
155
+ }
156
+ currentAssistantIndex = idx;
157
+ }
158
+ var updated = state.messages.slice();
159
+ updated[idx] = {
160
+ ...updated[idx],
161
+ text: (updated[idx].text || "") + deltaText,
162
+ };
163
+ return { ...state, messages: updated };
164
+ });
165
+ }
166
+
167
+ export function updateToolResult(toolId: string, content: string): void {
168
+ sessionStore.setState(function (state) {
169
+ var idx = state.messages.findLastIndex(function (msg) {
170
+ return msg.toolId === toolId && msg.type === "tool_start";
171
+ });
172
+ if (idx === -1) {
173
+ return state;
174
+ }
175
+ var updated = state.messages.slice();
176
+ updated[idx] = { ...updated[idx], content: content };
177
+ return { ...state, messages: updated };
178
+ });
179
+ }
180
+
181
+ export function setIsProcessing(processing: boolean): void {
182
+ sessionStore.setState(function (state) {
183
+ return { ...state, isProcessing: processing };
184
+ });
185
+ }
186
+
187
+ export function setActiveSession(projectSlug: string | null, sessionId: string | null, title?: string | null): void {
188
+ var prevSessionId = sessionStore.state.activeSessionId;
189
+ if (prevSessionId) {
190
+ markSessionRead(prevSessionId, sessionStore.state.messages.length);
191
+ }
192
+ currentAssistantUuid = null;
193
+ incrementStreamGeneration();
194
+ sessionStore.setState(function (state) {
195
+ return {
196
+ ...state,
197
+ activeProjectSlug: projectSlug,
198
+ activeSessionId: sessionId,
199
+ activeSessionTitle: title ?? null,
200
+ messages: [],
201
+ isProcessing: false,
202
+ currentStatus: null,
203
+ contextUsage: null,
204
+ contextBreakdown: null,
205
+ pendingPermissionCount: 0,
206
+ lastResponseCost: null,
207
+ lastResponseDuration: null,
208
+ lastReadIndex: null,
209
+ historyLoading: true,
210
+ wasInterrupted: false,
211
+ };
212
+ });
213
+ }
214
+
215
+ export function setSessionTitle(title: string | null): void {
216
+ sessionStore.setState(function (state) {
217
+ return { ...state, activeSessionTitle: title };
218
+ });
219
+ }
220
+
221
+ export function setCurrentStatus(status: SessionState["currentStatus"]): void {
222
+ sessionStore.setState(function (state) {
223
+ return { ...state, currentStatus: status };
224
+ });
225
+ }
226
+
227
+ export function setContextUsage(usage: ContextUsage): void {
228
+ sessionStore.setState(function (state) {
229
+ var prev = state.contextUsage;
230
+ var contextWindow = usage.contextWindow > 0 ? usage.contextWindow : (prev ? prev.contextWindow : 0);
231
+ return {
232
+ ...state,
233
+ contextUsage: {
234
+ inputTokens: usage.inputTokens,
235
+ outputTokens: usage.outputTokens,
236
+ cacheReadTokens: usage.cacheReadTokens,
237
+ cacheCreationTokens: usage.cacheCreationTokens,
238
+ contextWindow: contextWindow,
239
+ },
240
+ };
241
+ });
242
+ }
243
+
244
+ export function clearSession(): void {
245
+ var prevSessionId = sessionStore.state.activeSessionId;
246
+ if (prevSessionId) {
247
+ markSessionRead(prevSessionId, sessionStore.state.messages.length);
248
+ }
249
+ currentAssistantUuid = null;
250
+ currentAssistantIndex = -1;
251
+ sessionStore.setState(function () {
252
+ return {
253
+ messages: [],
254
+ isProcessing: false,
255
+ activeProjectSlug: null,
256
+ activeSessionId: null,
257
+ activeSessionTitle: null,
258
+ currentStatus: null,
259
+ contextUsage: null,
260
+ contextBreakdown: null,
261
+ pendingPermissionCount: 0,
262
+ lastResponseCost: null,
263
+ lastResponseDuration: null,
264
+ lastReadIndex: null,
265
+ historyLoading: false,
266
+ wasInterrupted: false,
267
+ };
268
+ });
269
+ }
270
+
271
+ export function setLastResponseStats(cost: number, duration: number): void {
272
+ sessionStore.setState(function (state) {
273
+ return { ...state, lastResponseCost: cost, lastResponseDuration: duration };
274
+ });
275
+ }
276
+
277
+ export function setWasInterrupted(interrupted: boolean): void {
278
+ sessionStore.setState(function (state) {
279
+ return { ...state, wasInterrupted: interrupted };
280
+ });
281
+ }
282
+
283
+ export function setContextBreakdown(breakdown: ContextBreakdown): void {
284
+ sessionStore.setState(function (state) {
285
+ return { ...state, contextBreakdown: breakdown };
286
+ });
287
+ }
288
+
289
+ export function getCurrentAssistantUuid(): string | null {
290
+ return currentAssistantUuid;
291
+ }
292
+
293
+ export function setCurrentAssistantUuid(uuid: string | null): void {
294
+ currentAssistantUuid = uuid;
295
+ if (uuid === null) {
296
+ currentAssistantIndex = -1;
297
+ }
298
+ }
299
+
300
+ export function incrementPendingPermissions(): void {
301
+ sessionStore.setState(function (state) {
302
+ return { ...state, pendingPermissionCount: state.pendingPermissionCount + 1 };
303
+ });
304
+ }
305
+
306
+ export function updatePermissionStatus(requestId: string, status: string): void {
307
+ sessionStore.setState(function (state) {
308
+ var idx = state.messages.findLastIndex(function (msg) {
309
+ return msg.toolId === requestId && msg.type === "permission_request";
310
+ });
311
+ if (idx === -1) {
312
+ return { ...state, pendingPermissionCount: Math.max(0, state.pendingPermissionCount - 1) };
313
+ }
314
+ var updated = state.messages.slice();
315
+ updated[idx] = { ...updated[idx], permissionStatus: status as HistoryMessage["permissionStatus"] };
316
+ return {
317
+ ...state,
318
+ messages: updated,
319
+ pendingPermissionCount: Math.max(0, state.pendingPermissionCount - 1),
320
+ };
321
+ });
322
+ }
@@ -0,0 +1,336 @@
1
+ import { Store } from "@tanstack/react-store";
2
+ import type { ProjectSettingsSection } from "@lattice/shared";
3
+
4
+ export type { ProjectSettingsSection };
5
+
6
+ export type SettingsSection =
7
+ | "appearance" | "claude" | "environment"
8
+ | "mcp" | "skills" | "nodes";
9
+
10
+ export type SidebarMode = "project" | "settings";
11
+
12
+ export type ActiveView =
13
+ | { type: "dashboard" }
14
+ | { type: "project-dashboard" }
15
+ | { type: "chat" }
16
+ | { type: "settings"; section: SettingsSection }
17
+ | { type: "project-settings"; section: ProjectSettingsSection };
18
+
19
+ export interface SidebarState {
20
+ activeProjectSlug: string | null;
21
+ activeSessionId: string | null;
22
+ sidebarMode: SidebarMode;
23
+ activeView: ActiveView;
24
+ previousView: ActiveView | null;
25
+ userMenuOpen: boolean;
26
+ projectDropdownOpen: boolean;
27
+ drawerOpen: boolean;
28
+ nodeSettingsOpen: boolean;
29
+ }
30
+
31
+ var SETTINGS_SECTIONS: SettingsSection[] = ["appearance", "claude", "environment", "mcp", "skills", "nodes"];
32
+
33
+ function parseInitialUrl(): { projectSlug: string | null; sessionId: string | null; settingsSection: SettingsSection | null; projectSettingsSection: ProjectSettingsSection | null } {
34
+ var path = window.location.pathname;
35
+ var parts = path.split("/").filter(function (p) { return p.length > 0; });
36
+ if (parts[0] === "settings") {
37
+ var section = parts[1] as SettingsSection;
38
+ if (section && SETTINGS_SECTIONS.indexOf(section) !== -1) {
39
+ return { projectSlug: null, sessionId: null, settingsSection: section, projectSettingsSection: null };
40
+ }
41
+ return { projectSlug: null, sessionId: null, settingsSection: "appearance", projectSettingsSection: null };
42
+ }
43
+ if (parts.length >= 2 && parts[1] === "settings") {
44
+ return { projectSlug: parts[0], sessionId: null, settingsSection: null, projectSettingsSection: (parts[2] || "general") as ProjectSettingsSection };
45
+ }
46
+ if (parts.length >= 2) {
47
+ return { projectSlug: parts[0], sessionId: parts[1], settingsSection: null, projectSettingsSection: null };
48
+ }
49
+ if (parts.length === 1) {
50
+ return { projectSlug: parts[0], sessionId: null, settingsSection: null, projectSettingsSection: null };
51
+ }
52
+ return { projectSlug: null, sessionId: null, settingsSection: null, projectSettingsSection: null };
53
+ }
54
+
55
+ var initialUrl = parseInitialUrl();
56
+
57
+ var sidebarStore = new Store<SidebarState>({
58
+ activeProjectSlug: initialUrl.settingsSection ? null : initialUrl.projectSlug,
59
+ activeSessionId: initialUrl.settingsSection ? null : initialUrl.sessionId,
60
+ sidebarMode: (initialUrl.settingsSection || initialUrl.projectSettingsSection) ? "settings" : "project",
61
+ activeView: initialUrl.settingsSection
62
+ ? { type: "settings", section: initialUrl.settingsSection }
63
+ : initialUrl.projectSettingsSection
64
+ ? { type: "project-settings", section: initialUrl.projectSettingsSection }
65
+ : initialUrl.projectSlug
66
+ ? (initialUrl.sessionId ? { type: "chat" } : { type: "project-dashboard" })
67
+ : { type: "dashboard" },
68
+ previousView: null,
69
+ userMenuOpen: false,
70
+ projectDropdownOpen: false,
71
+ drawerOpen: false,
72
+ nodeSettingsOpen: false,
73
+ });
74
+
75
+ function pushUrl(projectSlug: string | null, sessionId: string | null): void {
76
+ var path = "/";
77
+ if (projectSlug) {
78
+ path = "/" + projectSlug;
79
+ if (sessionId) {
80
+ path = path + "/" + sessionId;
81
+ }
82
+ }
83
+ if (window.location.pathname !== path) {
84
+ window.history.pushState(null, "", path);
85
+ }
86
+ }
87
+
88
+ export function getSidebarStore(): Store<SidebarState> {
89
+ return sidebarStore;
90
+ }
91
+
92
+ export function setActiveProjectSlug(slug: string | null): void {
93
+ sidebarStore.setState(function (state) {
94
+ return {
95
+ ...state,
96
+ activeProjectSlug: slug,
97
+ activeSessionId: null,
98
+ sidebarMode: "project",
99
+ activeView: slug ? { type: "project-dashboard" } : { type: "dashboard" },
100
+ userMenuOpen: false,
101
+ projectDropdownOpen: false,
102
+ };
103
+ });
104
+ pushUrl(slug, null);
105
+ }
106
+
107
+ export function setActiveSessionId(sessionId: string | null): void {
108
+ var state = sidebarStore.state;
109
+ sidebarStore.setState(function (s) {
110
+ return {
111
+ ...s,
112
+ activeSessionId: sessionId,
113
+ activeView: sessionId ? { type: "chat" } : s.activeView,
114
+ };
115
+ });
116
+ pushUrl(state.activeProjectSlug, sessionId);
117
+ }
118
+
119
+ export function navigateToSession(projectSlug: string, sessionId: string): void {
120
+ sidebarStore.setState(function (state) {
121
+ return {
122
+ ...state,
123
+ activeProjectSlug: projectSlug,
124
+ activeSessionId: sessionId,
125
+ sidebarMode: "project",
126
+ activeView: { type: "chat" },
127
+ userMenuOpen: false,
128
+ projectDropdownOpen: false,
129
+ };
130
+ });
131
+ pushUrl(projectSlug, sessionId);
132
+ }
133
+
134
+ export function openSettings(section: SettingsSection): void {
135
+ sidebarStore.setState(function (state) {
136
+ return {
137
+ ...state,
138
+ sidebarMode: "settings",
139
+ previousView: state.activeView.type !== "settings" ? state.activeView : state.previousView,
140
+ activeView: { type: "settings", section: section },
141
+ userMenuOpen: false,
142
+ projectDropdownOpen: false,
143
+ };
144
+ });
145
+ var path = "/settings/" + section;
146
+ if (window.location.pathname !== path) {
147
+ window.history.pushState(null, "", path);
148
+ }
149
+ }
150
+
151
+ export function setSettingsSection(section: SettingsSection): void {
152
+ sidebarStore.setState(function (state) {
153
+ return {
154
+ ...state,
155
+ activeView: { type: "settings", section: section },
156
+ };
157
+ });
158
+ var path = "/settings/" + section;
159
+ if (window.location.pathname !== path) {
160
+ window.history.replaceState(null, "", path);
161
+ }
162
+ }
163
+
164
+ export function openProjectSettings(section: ProjectSettingsSection): void {
165
+ sidebarStore.setState(function (state) {
166
+ return {
167
+ ...state,
168
+ sidebarMode: "settings",
169
+ previousView: state.activeView.type !== "settings" && state.activeView.type !== "project-settings" ? state.activeView : state.previousView,
170
+ activeView: { type: "project-settings", section: section },
171
+ userMenuOpen: false,
172
+ projectDropdownOpen: false,
173
+ };
174
+ });
175
+ var state = sidebarStore.state;
176
+ var path = "/" + state.activeProjectSlug + "/settings/" + section;
177
+ if (window.location.pathname !== path) {
178
+ window.history.pushState(null, "", path);
179
+ }
180
+ }
181
+
182
+ export function setProjectSettingsSection(section: ProjectSettingsSection): void {
183
+ sidebarStore.setState(function (state) {
184
+ return {
185
+ ...state,
186
+ activeView: { type: "project-settings", section: section },
187
+ };
188
+ });
189
+ var state = sidebarStore.state;
190
+ var path = "/" + state.activeProjectSlug + "/settings/" + section;
191
+ if (window.location.pathname !== path) {
192
+ window.history.replaceState(null, "", path);
193
+ }
194
+ }
195
+
196
+ export function exitSettings(): void {
197
+ var state = sidebarStore.state;
198
+ var restored = state.previousView ?? { type: "chat" } as ActiveView;
199
+ sidebarStore.setState(function (s) {
200
+ return {
201
+ ...s,
202
+ sidebarMode: "project",
203
+ activeView: restored,
204
+ previousView: null,
205
+ };
206
+ });
207
+ if (state.activeView.type === "project-settings") {
208
+ pushUrl(state.activeProjectSlug, null);
209
+ } else {
210
+ pushUrl(state.activeProjectSlug, state.activeSessionId);
211
+ }
212
+ }
213
+
214
+ export function goToProjectDashboard(): void {
215
+ sidebarStore.setState(function (state) {
216
+ return {
217
+ ...state,
218
+ activeSessionId: null,
219
+ sidebarMode: "project",
220
+ activeView: { type: "project-dashboard" },
221
+ userMenuOpen: false,
222
+ projectDropdownOpen: false,
223
+ };
224
+ });
225
+ var state = sidebarStore.state;
226
+ pushUrl(state.activeProjectSlug, null);
227
+ }
228
+
229
+ export function goToDashboard(): void {
230
+ sidebarStore.setState(function (state) {
231
+ return {
232
+ ...state,
233
+ activeProjectSlug: null,
234
+ activeSessionId: null,
235
+ sidebarMode: "project",
236
+ activeView: { type: "dashboard" },
237
+ userMenuOpen: false,
238
+ projectDropdownOpen: false,
239
+ };
240
+ });
241
+ pushUrl(null, null);
242
+ }
243
+
244
+ export function handlePopState(): void {
245
+ var url = parseInitialUrl();
246
+ if (url.settingsSection) {
247
+ sidebarStore.setState(function (state) {
248
+ return {
249
+ ...state,
250
+ sidebarMode: "settings",
251
+ activeView: { type: "settings", section: url.settingsSection! },
252
+ userMenuOpen: false,
253
+ projectDropdownOpen: false,
254
+ };
255
+ });
256
+ } else if (url.projectSettingsSection) {
257
+ sidebarStore.setState(function (state) {
258
+ return {
259
+ ...state,
260
+ activeProjectSlug: url.projectSlug,
261
+ sidebarMode: "settings",
262
+ activeView: { type: "project-settings", section: url.projectSettingsSection! },
263
+ userMenuOpen: false,
264
+ projectDropdownOpen: false,
265
+ };
266
+ });
267
+ } else {
268
+ sidebarStore.setState(function (state) {
269
+ return {
270
+ ...state,
271
+ activeProjectSlug: url.projectSlug,
272
+ activeSessionId: url.sessionId,
273
+ sidebarMode: "project",
274
+ activeView: url.projectSlug
275
+ ? (url.sessionId ? { type: "chat" } : { type: "project-dashboard" })
276
+ : { type: "dashboard" },
277
+ userMenuOpen: false,
278
+ projectDropdownOpen: false,
279
+ };
280
+ });
281
+ }
282
+ }
283
+
284
+ export function toggleUserMenu(): void {
285
+ sidebarStore.setState(function (state) {
286
+ return {
287
+ ...state,
288
+ userMenuOpen: !state.userMenuOpen,
289
+ projectDropdownOpen: false,
290
+ };
291
+ });
292
+ }
293
+
294
+ export function toggleProjectDropdown(): void {
295
+ sidebarStore.setState(function (state) {
296
+ return {
297
+ ...state,
298
+ projectDropdownOpen: !state.projectDropdownOpen,
299
+ userMenuOpen: false,
300
+ };
301
+ });
302
+ }
303
+
304
+ export function closeMenus(): void {
305
+ sidebarStore.setState(function (state) {
306
+ return {
307
+ ...state,
308
+ userMenuOpen: false,
309
+ projectDropdownOpen: false,
310
+ };
311
+ });
312
+ }
313
+
314
+ export function toggleDrawer(): void {
315
+ sidebarStore.setState(function (state) {
316
+ return { ...state, drawerOpen: !state.drawerOpen };
317
+ });
318
+ }
319
+
320
+ export function closeDrawer(): void {
321
+ sidebarStore.setState(function (state) {
322
+ return { ...state, drawerOpen: false };
323
+ });
324
+ }
325
+
326
+ export function openNodeSettings(): void {
327
+ sidebarStore.setState(function (state) {
328
+ return { ...state, nodeSettingsOpen: true, userMenuOpen: false };
329
+ });
330
+ }
331
+
332
+ export function closeNodeSettings(): void {
333
+ sidebarStore.setState(function (state) {
334
+ return { ...state, nodeSettingsOpen: false };
335
+ });
336
+ }
@@ -0,0 +1,44 @@
1
+ import { Store } from "@tanstack/react-store";
2
+
3
+ export interface ThemeState {
4
+ mode: "dark" | "light";
5
+ darkThemeId: string;
6
+ lightThemeId: string;
7
+ }
8
+
9
+ function loadInitialState(): ThemeState {
10
+ var mode = localStorage.getItem("lattice-theme-mode");
11
+ var darkThemeId = localStorage.getItem("lattice-theme-dark");
12
+ var lightThemeId = localStorage.getItem("lattice-theme-light");
13
+
14
+ return {
15
+ mode: mode === "light" ? "light" : "dark",
16
+ darkThemeId: darkThemeId ?? "dracula",
17
+ lightThemeId: lightThemeId ?? "ayu-light",
18
+ };
19
+ }
20
+
21
+ var themeStore = new Store<ThemeState>(loadInitialState());
22
+
23
+ export function getThemeStore(): Store<ThemeState> {
24
+ return themeStore;
25
+ }
26
+
27
+ export function toggleMode(): void {
28
+ themeStore.setState(function (state) {
29
+ var next: ThemeState["mode"] = state.mode === "dark" ? "light" : "dark";
30
+ localStorage.setItem("lattice-theme-mode", next);
31
+ return { ...state, mode: next };
32
+ });
33
+ }
34
+
35
+ export function setThemeForMode(themeId: string): void {
36
+ themeStore.setState(function (state) {
37
+ if (state.mode === "dark") {
38
+ localStorage.setItem("lattice-theme-dark", themeId);
39
+ return { ...state, darkThemeId: themeId };
40
+ }
41
+ localStorage.setItem("lattice-theme-light", themeId);
42
+ return { ...state, lightThemeId: themeId };
43
+ });
44
+ }