@cryptiklemur/lattice 1.43.2 → 1.43.3

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.
@@ -175,10 +175,25 @@ function PreviewPopover(props: { preview: SessionPreview | null; anchorRect: DOM
175
175
  return createPortal(content, document.body);
176
176
  }
177
177
 
178
+ function loadCachedSessions(slug: string | null): SessionSummary[] {
179
+ if (!slug) return [];
180
+ try {
181
+ var raw = localStorage.getItem("lattice:sessions:" + slug);
182
+ if (raw) return JSON.parse(raw);
183
+ } catch {}
184
+ return [];
185
+ }
186
+
187
+ function cacheSessions(slug: string, sessions: SessionSummary[]): void {
188
+ try {
189
+ localStorage.setItem("lattice:sessions:" + slug, JSON.stringify(sessions.slice(0, 100)));
190
+ } catch {}
191
+ }
192
+
178
193
  export function SessionList(props: SessionListProps) {
179
194
  useTimeTick();
180
195
  var ws = useWebSocket();
181
- var [sessions, setSessions] = useState<SessionSummary[]>([]);
196
+ var [sessions, setSessions] = useState<SessionSummary[]>(function () { return loadCachedSessions(props.projectSlug); });
182
197
  var [loading, setLoading] = useState<boolean>(false);
183
198
  var [loadingMore, setLoadingMore] = useState<boolean>(false);
184
199
  var [totalCount, setTotalCount] = useState<number>(0);
@@ -240,6 +255,9 @@ export function SessionList(props: SessionListProps) {
240
255
 
241
256
  setTotalCount(listTotal);
242
257
  offsetRef.current = listOffset + incoming.length;
258
+ if (props.projectSlug && listOffset === 0) {
259
+ cacheSessions(props.projectSlug, incoming);
260
+ }
243
261
  hasMoreRef.current = listOffset + incoming.length < listTotal;
244
262
  }
245
263
  } else if (msg.type === "session:created") {
@@ -286,8 +304,9 @@ export function SessionList(props: SessionListProps) {
286
304
 
287
305
  useEffect(function () {
288
306
  if (props.projectSlug && ws.status === "connected") {
289
- setSessions([]);
290
- setLoading(true);
307
+ var cached = loadCachedSessions(props.projectSlug);
308
+ setSessions(cached);
309
+ setLoading(cached.length === 0);
291
310
  offsetRef.current = 0;
292
311
  hasMoreRef.current = true;
293
312
  sendRef.current({ type: "session:list_request", projectSlug: props.projectSlug, offset: 0, limit: PAGE_SIZE });
@@ -12,9 +12,23 @@ export interface UseProjectsResult {
12
12
  setActiveProject: (project: ProjectInfo | null) => void;
13
13
  }
14
14
 
15
+ function loadCachedProjects(): ProjectInfo[] {
16
+ try {
17
+ var raw = localStorage.getItem("lattice:projects");
18
+ if (raw) return JSON.parse(raw);
19
+ } catch {}
20
+ return [];
21
+ }
22
+
23
+ function cacheProjects(projects: ProjectInfo[]): void {
24
+ try {
25
+ localStorage.setItem("lattice:projects", JSON.stringify(projects));
26
+ } catch {}
27
+ }
28
+
15
29
  export function useProjects(): UseProjectsResult {
16
30
  var ws = useWebSocket();
17
- var [projects, setProjects] = useState<ProjectInfo[]>([]);
31
+ var [projects, setProjects] = useState<ProjectInfo[]>(loadCachedProjects);
18
32
  var activeProjectSlug = useStore(getSidebarStore(), function (state) { return state.activeProjectSlug; });
19
33
 
20
34
  var handleRef = useRef<(msg: ServerMessage) => void>(function () {});
@@ -32,7 +46,9 @@ export function useProjects(): UseProjectsResult {
32
46
  for (var i = 0; i < kept.length; i++) {
33
47
  (kept[i] as any).online = false;
34
48
  }
35
- return incoming.concat(kept);
49
+ var merged = incoming.concat(kept);
50
+ cacheProjects(merged);
51
+ return merged;
36
52
  });
37
53
  var storeState = getSidebarStore().state;
38
54
  var currentSlug = storeState.activeProjectSlug;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cryptiklemur/lattice",
3
- "version": "1.43.2",
3
+ "version": "1.43.3",
4
4
  "description": "Multi-machine agentic dashboard for Claude Code. Monitor sessions, manage MCP servers and skills, orchestrate across mesh-networked nodes.",
5
5
  "license": "MIT",
6
6
  "author": "Aaron Scherer <me@aaronscherer.me>",
@@ -255,7 +255,11 @@ function refreshCliDetection(): void {
255
255
  var cmdline = cliPids[j].cmdline;
256
256
  var resumeIdx = cmdline.indexOf("--resume");
257
257
  if (resumeIdx !== -1 && resumeIdx + 1 < cmdline.length) {
258
- found = resolveSessionName(projectPath, cmdline[resumeIdx + 1]);
258
+ var sessionName = cmdline[resumeIdx + 1];
259
+ found = resolveSessionName(projectPath, sessionName);
260
+ if (!found) {
261
+ resolveSessionNameAsync(projectPath, sessionName);
262
+ }
259
263
  } else {
260
264
  found = findMostRecentSession(projectPath);
261
265
  }
@@ -303,37 +307,42 @@ function resolveSessionName(projectPath: string, name: string): string | null {
303
307
  var cached = sessionNameCache.get(cacheKey);
304
308
  if (cached) return cached;
305
309
 
306
- var hash = projectPath.replace(/\//g, "-");
307
- var dir = join(homedir(), ".claude", "projects", hash);
308
- if (!existsSync(dir)) return null;
309
-
310
310
  if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(name)) {
311
- if (existsSync(join(dir, name + ".jsonl"))) {
311
+ var hash = projectPath.replace(/\//g, "-");
312
+ if (existsSync(join(homedir(), ".claude", "projects", hash, name + ".jsonl"))) {
312
313
  sessionNameCache.set(cacheKey, name);
313
314
  return name;
314
315
  }
315
316
  }
316
317
 
317
- var entries = readdirSync(dir).filter(function (f) { return f.endsWith(".jsonl"); });
318
- for (var e = 0; e < entries.length; e++) {
319
- try {
320
- var content = readFileSync(join(dir, entries[e]), "utf-8");
321
- var titleIdx = content.indexOf('"custom-title"');
322
- if (titleIdx === -1) continue;
323
- var lineStart = content.lastIndexOf("\n", titleIdx) + 1;
324
- var lineEnd = content.indexOf("\n", titleIdx);
325
- var line = content.slice(lineStart, lineEnd === -1 ? undefined : lineEnd);
326
- var parsed = JSON.parse(line);
327
- if (parsed.type === "custom-title" && parsed.customTitle === name) {
328
- var id = entries[e].replace(".jsonl", "");
329
- sessionNameCache.set(cacheKey, id);
330
- return id;
331
- }
332
- } catch {}
333
- }
334
318
  return null;
335
319
  }
336
320
 
321
+ function resolveSessionNameAsync(projectPath: string, name: string): void {
322
+ var cacheKey = projectPath + ":" + name;
323
+ if (sessionNameCache.has(cacheKey)) return;
324
+
325
+ var hash = projectPath.replace(/\//g, "-");
326
+ var dir = join(homedir(), ".claude", "projects", hash);
327
+ if (!existsSync(dir)) return;
328
+
329
+ var proc = Bun.spawn(["grep", "-rl", "--include=*.jsonl", "-m", "1", name, dir], {
330
+ stdout: "pipe", stderr: "ignore",
331
+ });
332
+ void proc.exited.then(function () {
333
+ var output = new Response(proc.stdout).text();
334
+ return output;
335
+ }).then(function (text) {
336
+ var files = text.trim().split("\n").filter(Boolean);
337
+ if (files.length > 0) {
338
+ var match = files[0].match(/([0-9a-f-]{36})\.jsonl$/);
339
+ if (match) {
340
+ sessionNameCache.set(cacheKey, match[1]);
341
+ }
342
+ }
343
+ }).catch(function () {});
344
+ }
345
+
337
346
  function findMostRecentSession(projectPath: string): string | null {
338
347
  var hash = projectPath.replace(/\//g, "-");
339
348
  var dir = join(homedir(), ".claude", "projects", hash);