@cryptiklemur/lattice 1.18.2 → 1.18.4
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.
- package/client/src/components/analytics/AnalyticsView.tsx +1 -22
- package/client/src/components/analytics/charts/ActivityCalendar.tsx +21 -7
- package/client/src/components/project-settings/ProjectClaude.tsx +1 -1
- package/client/src/components/project-settings/ProjectEnvironment.tsx +1 -1
- package/client/src/components/project-settings/ProjectGeneral.tsx +1 -1
- package/client/src/components/project-settings/ProjectMcp.tsx +1 -1
- package/client/src/components/project-settings/ProjectPermissions.tsx +1 -1
- package/client/src/components/project-settings/ProjectRules.tsx +1 -1
- package/client/src/components/settings/BudgetSettings.tsx +1 -1
- package/client/src/components/settings/ClaudeSettings.tsx +1 -1
- package/client/src/components/settings/Editor.tsx +1 -1
- package/client/src/components/settings/Environment.tsx +2 -2
- package/client/src/components/settings/GlobalMcp.tsx +1 -1
- package/client/src/hooks/useSaveState.ts +7 -1
- package/client/src/utils/editorUrl.ts +3 -3
- package/package.json +1 -1
|
@@ -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="
|
|
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 =
|
|
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
|
|
109
|
-
<svg width={
|
|
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.
|
|
65
|
+
if (save.savingRef.current) {
|
|
66
66
|
save.confirmSave();
|
|
67
67
|
} else {
|
|
68
68
|
setClaudeMd(settings.claudeMd ?? "");
|
|
@@ -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.
|
|
38
|
+
if (save.savingRef.current) {
|
|
39
39
|
save.confirmSave();
|
|
40
40
|
} else {
|
|
41
41
|
setServers({ ...(settings.mcpServers ?? {}) });
|
|
@@ -31,7 +31,7 @@ export function ProjectRules({
|
|
|
31
31
|
var save = useSaveState();
|
|
32
32
|
|
|
33
33
|
useEffect(function () {
|
|
34
|
-
if (save.
|
|
34
|
+
if (save.savingRef.current) {
|
|
35
35
|
save.confirmSave();
|
|
36
36
|
} else {
|
|
37
37
|
setRules((settings.rules ?? []).map(function (r: { filename: string; content: string }) {
|
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
40
|
+
if (!save.savingRef.current) {
|
|
41
41
|
save.resetFromServer();
|
|
42
42
|
}
|
|
43
43
|
}
|
|
@@ -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
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
var JETBRAINS_IDS: Record<string, string> = {
|
|
2
|
-
webstorm: "
|
|
2
|
+
webstorm: "web-storm",
|
|
3
3
|
intellij: "idea",
|
|
4
|
-
pycharm: "
|
|
5
|
-
goland: "
|
|
4
|
+
pycharm: "py-charm",
|
|
5
|
+
goland: "go-land",
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
function toWindowsPath(linuxPath: string, wslDistro: string): string {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cryptiklemur/lattice",
|
|
3
|
-
"version": "1.18.
|
|
3
|
+
"version": "1.18.4",
|
|
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>",
|