@cryptiklemur/lattice 1.18.2 → 1.18.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.
@@ -50,9 +50,6 @@ import { ActivityCalendar } from "./charts/ActivityCalendar";
50
50
  import { HourlyHeatmap } from "./charts/HourlyHeatmap";
51
51
  import { SessionTimeline } from "./charts/SessionTimeline";
52
52
  import { DailySummaryCards } from "./charts/DailySummaryCards";
53
- import { ToolTreemap } from "./charts/ToolTreemap";
54
- import { ToolSunburst } from "./charts/ToolSunburst";
55
- import { PermissionBreakdown } from "./charts/PermissionBreakdown";
56
53
  import { ProjectRadar } from "./charts/ProjectRadar";
57
54
  import { SessionComplexityList } from "./charts/SessionComplexityList";
58
55
  import { NodeFleetOverview } from "./charts/NodeFleetOverview";
@@ -183,27 +180,9 @@ export function AnalyticsView() {
183
180
  </ChartErrorBoundary>
184
181
  </ChartCard>
185
182
 
186
- <SectionHeader label="Tools & Projects" />
183
+ <SectionHeader label="Projects" />
187
184
 
188
185
  <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
189
- <ChartCard title="Tool Usage (Treemap)">
190
- <ChartErrorBoundary name="Treemap">
191
- <ToolTreemap data={analytics.data.toolTreemap} />
192
- </ChartErrorBoundary>
193
- </ChartCard>
194
- <ChartCard title="Tool Categories">
195
- <ChartErrorBoundary name="Sunburst">
196
- <ToolSunburst data={analytics.data.toolSunburst} />
197
- </ChartErrorBoundary>
198
- </ChartCard>
199
- </div>
200
-
201
- <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
202
- <ChartCard title="Permissions">
203
- <ChartErrorBoundary name="Permissions">
204
- <PermissionBreakdown data={analytics.data.permissionStats} />
205
- </ChartErrorBoundary>
206
- </ChartCard>
207
186
  <ChartCard title="Project Comparison">
208
187
  <ChartErrorBoundary name="Radar">
209
188
  <ProjectRadar data={analytics.data.projectRadar} />
@@ -1,4 +1,4 @@
1
- import { useState } from "react";
1
+ import { useState, useRef, useEffect } from "react";
2
2
  import { getChartColors } from "../chartTokens";
3
3
 
4
4
  interface CalendarDatum {
@@ -12,11 +12,8 @@ interface ActivityCalendarProps {
12
12
  data: CalendarDatum[];
13
13
  }
14
14
 
15
- var CELL_SIZE = 11;
16
- var CELL_GAP = 2;
17
- var CELL_STEP = CELL_SIZE + CELL_GAP;
18
15
  var DAY_LABEL_WIDTH = 28;
19
- var MONTH_LABEL_HEIGHT = 14;
16
+ var MONTH_LABEL_HEIGHT = 16;
20
17
 
21
18
  var INTENSITY_OPACITIES = [0.05, 0.15, 0.3, 0.5, 0.8];
22
19
 
@@ -46,6 +43,18 @@ export function ActivityCalendar({ data }: ActivityCalendarProps) {
46
43
  var colors = getChartColors();
47
44
  var PRIMARY_COLOR = colors.primary;
48
45
  var [hover, setHover] = useState<{ x: number; y: number; datum: CalendarDatum } | null>(null);
46
+ var containerRef = useRef<HTMLDivElement>(null);
47
+ var [containerWidth, setContainerWidth] = useState(0);
48
+
49
+ useEffect(function () {
50
+ if (!containerRef.current) return;
51
+ let ro = new ResizeObserver(function (entries) {
52
+ setContainerWidth(entries[0].contentRect.width);
53
+ });
54
+ ro.observe(containerRef.current);
55
+ setContainerWidth(containerRef.current.clientWidth);
56
+ return function () { ro.disconnect(); };
57
+ }, []);
49
58
 
50
59
  if (!data || data.length === 0) {
51
60
  return (
@@ -101,12 +110,17 @@ export function ActivityCalendar({ data }: ActivityCalendarProps) {
101
110
  }
102
111
  }
103
112
 
113
+ var availableWidth = (containerWidth || 800) - DAY_LABEL_WIDTH;
114
+ var CELL_STEP = Math.max(3, Math.floor(availableWidth / weeks.length));
115
+ var CELL_GAP = Math.max(1, Math.round(CELL_STEP * 0.15));
116
+ var CELL_SIZE = CELL_STEP - CELL_GAP;
117
+
104
118
  var svgWidth = DAY_LABEL_WIDTH + weeks.length * CELL_STEP;
105
119
  var svgHeight = MONTH_LABEL_HEIGHT + 7 * CELL_STEP;
106
120
 
107
121
  return (
108
- <div className="relative overflow-x-auto">
109
- <svg width={svgWidth} height={svgHeight} className="block">
122
+ <div ref={containerRef} className="relative w-full">
123
+ <svg width="100%" height={svgHeight} viewBox={"0 0 " + svgWidth + " " + svgHeight} className="block">
110
124
  {monthLabels.map(function (ml, idx) {
111
125
  return (
112
126
  <text
@@ -62,7 +62,7 @@ export function ProjectClaude({ settings, updateSection }: ProjectClaudeProps) {
62
62
  var save = useSaveState();
63
63
 
64
64
  useEffect(function () {
65
- if (save.saving) {
65
+ if (save.savingRef.current) {
66
66
  save.confirmSave();
67
67
  } else {
68
68
  setClaudeMd(settings.claudeMd ?? "");
@@ -47,7 +47,7 @@ export function ProjectEnvironment({
47
47
  var save = useSaveState();
48
48
 
49
49
  useEffect(function () {
50
- if (save.saving) {
50
+ if (save.savingRef.current) {
51
51
  save.confirmSave();
52
52
  } else {
53
53
  setEntries(envToEntries(settings.env ?? {}));
@@ -15,7 +15,7 @@ export function ProjectGeneral({ settings, updateSection }: ProjectGeneralProps)
15
15
  var save = useSaveState();
16
16
 
17
17
  useEffect(function () {
18
- if (save.saving) {
18
+ if (save.savingRef.current) {
19
19
  save.confirmSave();
20
20
  } else {
21
21
  setTitle(settings.title);
@@ -35,7 +35,7 @@ export function ProjectMcp({
35
35
  var [confirmDelete, setConfirmDelete] = useState<string | null>(null);
36
36
 
37
37
  useEffect(function () {
38
- if (save.saving) {
38
+ if (save.savingRef.current) {
39
39
  save.confirmSave();
40
40
  } else {
41
41
  setServers({ ...(settings.mcpServers ?? {}) });
@@ -118,7 +118,7 @@ export function ProjectPermissions({
118
118
  var save = useSaveState();
119
119
 
120
120
  useEffect(function () {
121
- if (save.saving) {
121
+ if (save.savingRef.current) {
122
122
  save.confirmSave();
123
123
  } else {
124
124
  setAllow([...(settings.permissions.allow ?? [])]);
@@ -31,7 +31,7 @@ export function ProjectRules({
31
31
  var save = useSaveState();
32
32
 
33
33
  useEffect(function () {
34
- if (save.saving) {
34
+ if (save.savingRef.current) {
35
35
  save.confirmSave();
36
36
  } else {
37
37
  setRules((settings.rules ?? []).map(function (r: { filename: string; content: string }) {
@@ -26,7 +26,7 @@ export function BudgetSettings() {
26
26
  var data = msg as SettingsDataMessage;
27
27
  var cfg = data.config;
28
28
 
29
- if (save.saving) {
29
+ if (save.savingRef.current) {
30
30
  save.confirmSave();
31
31
  } else {
32
32
  if (cfg.costBudget) {
@@ -39,7 +39,7 @@ export function ClaudeSettings() {
39
39
  var newModel = cfg.defaultModel ? String(cfg.defaultModel) : CLAUDE_MODELS[0].id;
40
40
  var newEffort = cfg.defaultEffort ? String(cfg.defaultEffort) : "normal";
41
41
 
42
- if (save.saving) {
42
+ if (save.savingRef.current) {
43
43
  save.confirmSave();
44
44
  } else {
45
45
  setClaudeMd(newClaudeMd);
@@ -35,7 +35,7 @@ export function Editor() {
35
35
  var newType = editor?.type ?? "vscode";
36
36
  var newCustomCommand = editor?.customCommand ?? "";
37
37
 
38
- if (saveRef.current.saving) {
38
+ if (saveRef.current.savingRef.current) {
39
39
  saveRef.current.confirmSave();
40
40
  } else {
41
41
  setIdeType(newType);
@@ -29,7 +29,7 @@ export function Environment() {
29
29
  var data = msg as SettingsDataMessage;
30
30
  var env = data.config.globalEnv ?? {};
31
31
 
32
- if (save.saving) {
32
+ if (save.savingRef.current) {
33
33
  save.confirmSave();
34
34
  }
35
35
 
@@ -37,7 +37,7 @@ export function Environment() {
37
37
  return { id: genId(), key: k, value: String(v) };
38
38
  });
39
39
  setEntries(rows);
40
- if (!save.saving) {
40
+ if (!save.savingRef.current) {
41
41
  save.resetFromServer();
42
42
  }
43
43
  }
@@ -33,7 +33,7 @@ export function GlobalMcp() {
33
33
  var data = msg as SettingsDataMessage;
34
34
  var mcpServers = data.mcpServers ?? {};
35
35
 
36
- if (save.saving) {
36
+ if (save.savingRef.current) {
37
37
  save.confirmSave();
38
38
  } else {
39
39
  setServers({ ...mcpServers });
@@ -5,6 +5,7 @@ export type SaveState = "idle" | "saved" | "error";
5
5
  export interface UseSaveStateReturn {
6
6
  dirty: boolean;
7
7
  saving: boolean;
8
+ savingRef: React.RefObject<boolean>;
8
9
  saveState: SaveState;
9
10
  markDirty: () => void;
10
11
  startSave: () => void;
@@ -17,6 +18,7 @@ export function useSaveState(): UseSaveStateReturn {
17
18
  var [saving, setSaving] = useState(false);
18
19
  var [saveState, setSaveState] = useState<SaveState>("idle");
19
20
  var saveTimeout = useRef<ReturnType<typeof setTimeout> | null>(null);
21
+ var savingRef = useRef(false);
20
22
 
21
23
  useEffect(function () {
22
24
  return function () {
@@ -31,11 +33,13 @@ export function useSaveState(): UseSaveStateReturn {
31
33
 
32
34
  function startSave() {
33
35
  setSaving(true);
36
+ savingRef.current = true;
34
37
  setSaveState("idle");
35
38
 
36
39
  if (saveTimeout.current) clearTimeout(saveTimeout.current);
37
40
  saveTimeout.current = setTimeout(function () {
38
41
  setSaving(false);
42
+ savingRef.current = false;
39
43
  setSaveState("error");
40
44
  setTimeout(function () { setSaveState("idle"); }, 3000);
41
45
  }, 5000);
@@ -43,6 +47,7 @@ export function useSaveState(): UseSaveStateReturn {
43
47
 
44
48
  function confirmSave() {
45
49
  setSaving(false);
50
+ savingRef.current = false;
46
51
  setSaveState("saved");
47
52
  setDirty(false);
48
53
  if (saveTimeout.current) clearTimeout(saveTimeout.current);
@@ -53,7 +58,8 @@ export function useSaveState(): UseSaveStateReturn {
53
58
  setDirty(false);
54
59
  setSaveState("idle");
55
60
  setSaving(false);
61
+ savingRef.current = false;
56
62
  }
57
63
 
58
- return { dirty, saving, saveState, markDirty, startSave, confirmSave, resetFromServer };
64
+ return { dirty, saving, savingRef, saveState, markDirty, startSave, confirmSave, resetFromServer };
59
65
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cryptiklemur/lattice",
3
- "version": "1.18.2",
3
+ "version": "1.18.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>",