@cryptiklemur/lattice 1.11.4 → 1.11.6

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.
@@ -1,4 +1,4 @@
1
- import { useEffect, useRef, useState } from "react";
1
+ import { useEffect, useRef, useState, useMemo } from "react";
2
2
  import type { SessionSummary, SessionListMessage, SessionCreatedMessage } from "@lattice/shared";
3
3
  import type { ServerMessage } from "@lattice/shared";
4
4
  import { useWebSocket } from "../../hooks/useWebSocket";
@@ -251,6 +251,15 @@ export function SessionList(props: SessionListProps) {
251
251
  }
252
252
  }
253
253
 
254
+ var grouped = useMemo(function () {
255
+ var displayed = props.filter
256
+ ? sessions.filter(function (s) {
257
+ return s.title.toLowerCase().includes(props.filter!.toLowerCase());
258
+ })
259
+ : sessions;
260
+ return groupByTime(displayed);
261
+ }, [sessions, props.filter]);
262
+
254
263
  if (!props.projectSlug) {
255
264
  return (
256
265
  <div className="flex-1 flex items-start px-3 py-1.5">
@@ -269,14 +278,6 @@ export function SessionList(props: SessionListProps) {
269
278
  );
270
279
  }
271
280
 
272
- var displayed = props.filter
273
- ? sessions.filter(function (s) {
274
- return s.title.toLowerCase().includes(props.filter!.toLowerCase());
275
- })
276
- : sessions;
277
-
278
- var grouped = groupByTime(displayed);
279
-
280
281
  return (
281
282
  <div className="flex flex-col flex-1 overflow-hidden min-h-0">
282
283
  <div className="flex-1 overflow-y-auto overflow-x-hidden scrollbar-hidden py-0.5 pb-16">
@@ -83,7 +83,10 @@ function LoadingScreen() {
83
83
  var logoRight = centerX + logoHalf;
84
84
  var logoBottom = centerY + logoHalf;
85
85
 
86
- var primary = "oklch(55% 0.25 280)";
86
+ var computedPrimary = getComputedStyle(document.documentElement).getPropertyValue("--color-primary").trim();
87
+ var primary = computedPrimary ? "oklch(" + computedPrimary + ")" : "oklch(55% 0.25 280)";
88
+
89
+ var prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
87
90
 
88
91
  type Dot = { x: number; y: number; col: number; row: number; hidden: boolean; brightness: number };
89
92
  var dots: Dot[] = [];
@@ -123,6 +126,37 @@ function LoadingScreen() {
123
126
  return result;
124
127
  }
125
128
 
129
+ function drawStatic() {
130
+ ctx!.clearRect(0, 0, canvasSize, canvasSize);
131
+ for (var di = 0; di < dots.length; di++) {
132
+ var dot = dots[di];
133
+ if (dot.hidden) continue;
134
+ ctx!.beginPath();
135
+ ctx!.arc(dot.x, dot.y, dotRadius, 0, Math.PI * 2);
136
+ ctx!.fillStyle = primary;
137
+ ctx!.globalAlpha = 0.15;
138
+ ctx!.fill();
139
+ ctx!.globalAlpha = 1.0;
140
+ }
141
+ var squares = [
142
+ [logoLeft, logoTop],
143
+ [logoLeft + logoSquare + logoGap, logoTop],
144
+ [logoLeft, logoTop + logoSquare + logoGap],
145
+ [logoLeft + logoSquare + logoGap, logoTop + logoSquare + logoGap],
146
+ ];
147
+ for (var si = 0; si < squares.length; si++) {
148
+ ctx!.fillStyle = primary;
149
+ ctx!.globalAlpha = 0.9;
150
+ ctx!.fillRect(squares[si][0], squares[si][1], logoSquare, logoSquare);
151
+ ctx!.globalAlpha = 1.0;
152
+ }
153
+ }
154
+
155
+ if (prefersReducedMotion) {
156
+ drawStatic();
157
+ return;
158
+ }
159
+
126
160
  function animate() {
127
161
  now = performance.now();
128
162
  ctx!.clearRect(0, 0, canvasSize, canvasSize);
@@ -277,8 +311,17 @@ function RemoveProjectConfirm() {
277
311
  }
278
312
  })();
279
313
 
314
+ useEffect(function () {
315
+ if (!slug) return;
316
+ function handleKeyDown(e: KeyboardEvent) {
317
+ if (e.key === "Escape") sidebar.closeConfirmRemove();
318
+ }
319
+ document.addEventListener("keydown", handleKeyDown);
320
+ return function () { document.removeEventListener("keydown", handleKeyDown); };
321
+ }, [slug]);
322
+
280
323
  return (
281
- <div className="fixed inset-0 z-[9999] flex items-center justify-center">
324
+ <div className="fixed inset-0 z-[9999] flex items-center justify-center" role="dialog" aria-modal="true" aria-label="Remove Project">
282
325
  <div className="absolute inset-0 bg-black/50" onClick={sidebar.closeConfirmRemove} />
283
326
  <div className="relative bg-base-200 border border-base-content/15 rounded-2xl shadow-2xl w-full max-w-sm mx-4 overflow-hidden">
284
327
  <div className="px-5 py-4 border-b border-base-content/15">
@@ -369,6 +412,9 @@ function RootLayout() {
369
412
 
370
413
  return (
371
414
  <div className="flex w-full h-full overflow-hidden bg-base-100">
415
+ <a href="#main-content" className="sr-only focus:not-sr-only focus:fixed focus:top-2 focus:left-2 focus:z-[99999] focus:px-4 focus:py-2 focus:bg-primary focus:text-primary-content focus:rounded-lg focus:text-sm focus:font-semibold">
416
+ Skip to content
417
+ </a>
372
418
  <LoadingScreen />
373
419
  <div className="drawer lg:drawer-open h-full w-full">
374
420
  <input
@@ -376,12 +422,12 @@ function RootLayout() {
376
422
  type="checkbox"
377
423
  className="drawer-toggle"
378
424
  checked={sidebar.drawerOpen}
379
- onChange={function () {}}
425
+ readOnly
380
426
  />
381
427
 
382
- <div className="drawer-content flex flex-col h-full min-w-0 overflow-hidden">
428
+ <main id="main-content" className="drawer-content flex flex-col h-full min-w-0 overflow-hidden">
383
429
  <Outlet />
384
- </div>
430
+ </main>
385
431
 
386
432
  <div ref={drawerSideRef} className="drawer-side z-50 h-full">
387
433
  <label
@@ -390,9 +436,9 @@ function RootLayout() {
390
436
  className="drawer-overlay"
391
437
  onClick={closeDrawer}
392
438
  />
393
- <div className="h-full w-full lg:w-[284px] flex flex-col overflow-hidden">
439
+ <nav aria-label="Sidebar navigation" className="h-full w-full lg:w-[284px] flex flex-col overflow-hidden">
394
440
  <Sidebar onSessionSelect={closeDrawer} />
395
- </div>
441
+ </nav>
396
442
  </div>
397
443
  </div>
398
444
  <NodeSettingsModal
@@ -408,24 +454,71 @@ function RootLayout() {
408
454
  );
409
455
  }
410
456
 
411
- function IndexPage() {
412
- var sidebar = useSidebar();
413
- if (sidebar.activeView.type === "dashboard") {
414
- return <DashboardView />;
457
+ import { Component } from "react";
458
+ import type { ReactNode, ErrorInfo } from "react";
459
+ import { AlertTriangle, RefreshCw } from "lucide-react";
460
+
461
+ class ViewErrorBoundary extends Component<{ children: ReactNode; viewName: string }, { hasError: boolean; error: Error | null }> {
462
+ constructor(props: { children: ReactNode; viewName: string }) {
463
+ super(props);
464
+ this.state = { hasError: false, error: null };
415
465
  }
416
- if (sidebar.activeView.type === "settings") {
417
- return <SettingsView />;
466
+ static getDerivedStateFromError(error: Error) {
467
+ return { hasError: true, error: error };
418
468
  }
419
- if (sidebar.activeView.type === "project-settings") {
420
- return <ProjectSettingsView />;
469
+ componentDidCatch(error: Error, info: ErrorInfo) {
470
+ console.error("[lattice] View error in " + this.props.viewName + ":", error, info.componentStack);
421
471
  }
422
- if (sidebar.activeView.type === "project-dashboard") {
423
- return <ProjectDashboardView />;
472
+ render() {
473
+ if (this.state.hasError) {
474
+ var self = this;
475
+ return (
476
+ <div className="flex flex-col items-center justify-center h-full bg-base-100 bg-lattice-grid gap-4 p-8">
477
+ <AlertTriangle size={32} className="text-warning/50" />
478
+ <div className="text-center max-w-[500px]">
479
+ <p className="text-[14px] font-mono text-base-content/60 mb-1">
480
+ Error in {this.props.viewName}
481
+ </p>
482
+ <p className="text-[12px] text-base-content/30 mb-4 font-mono break-all">
483
+ {this.state.error?.message || "Unknown error"}
484
+ </p>
485
+ <button
486
+ onClick={function () { self.setState({ hasError: false, error: null }); }}
487
+ className="btn btn-ghost btn-sm text-[12px] gap-1.5"
488
+ >
489
+ <RefreshCw size={12} />
490
+ Retry
491
+ </button>
492
+ </div>
493
+ </div>
494
+ );
495
+ }
496
+ return this.props.children;
424
497
  }
425
- if (sidebar.activeView.type === "analytics") {
426
- return <AnalyticsView />;
498
+ }
499
+
500
+ function IndexPage() {
501
+ var sidebar = useSidebar();
502
+ var viewName = sidebar.activeView.type;
503
+ var content;
504
+ if (viewName === "dashboard") {
505
+ content = <DashboardView />;
506
+ } else if (viewName === "settings") {
507
+ content = <SettingsView />;
508
+ } else if (viewName === "project-settings") {
509
+ content = <ProjectSettingsView />;
510
+ } else if (viewName === "project-dashboard") {
511
+ content = <ProjectDashboardView />;
512
+ } else if (viewName === "analytics") {
513
+ content = <AnalyticsView />;
514
+ } else {
515
+ content = <WorkspaceView />;
427
516
  }
428
- return <WorkspaceView />;
517
+ return (
518
+ <ViewErrorBoundary viewName={viewName}>
519
+ {content}
520
+ </ViewErrorBoundary>
521
+ );
429
522
  }
430
523
 
431
524
  var rootRoute = createRootRoute({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cryptiklemur/lattice",
3
- "version": "1.11.4",
3
+ "version": "1.11.6",
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>",
@@ -439,6 +439,13 @@ export function startChatStream(options: ChatStreamOptions): void {
439
439
  }
440
440
  }
441
441
 
442
+ if (toolName === "Bash") {
443
+ var cmd = ((input.command || "") as string).trim();
444
+ if (cmd.startsWith("cd ")) {
445
+ return Promise.resolve({ behavior: "allow", updatedInput: input, toolUseID: options.toolUseID } as PermissionResult);
446
+ }
447
+ }
448
+
442
449
  var allowRules: string[] = [];
443
450
  if (existsSync(projectSettingsPath)) {
444
451
  try {