@cryptiklemur/lattice 1.42.1 → 1.42.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.
@@ -38,7 +38,7 @@ export function QuickStats() {
38
38
  <div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
39
39
  {[0, 1, 2, 3].map(function (i) {
40
40
  return (
41
- <div key={i} className="bg-base-content/[0.03] border border-base-content/8 rounded-xl p-3.5 animate-pulse">
41
+ <div key={i} className="bg-base-content/[0.03] rounded-xl p-3.5 animate-pulse">
42
42
  <div className="h-2.5 w-16 bg-base-content/10 rounded mb-3" />
43
43
  <div className="h-6 w-12 bg-base-content/10 rounded" />
44
44
  </div>
@@ -60,7 +60,7 @@ export function QuickStats() {
60
60
 
61
61
  return (
62
62
  <div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
63
- <div className="bg-base-content/[0.03] border border-base-content/8 rounded-xl p-3.5">
63
+ <div className="bg-base-content/[0.03] rounded-xl p-3.5">
64
64
  <div className="flex items-center justify-between mb-1">
65
65
  <span className="text-[10px] font-mono font-bold uppercase tracking-wider text-base-content/40">Total Spend</span>
66
66
  {costSparkData.length > 1 && (
@@ -72,7 +72,7 @@ export function QuickStats() {
72
72
  <div className="text-[20px] font-mono text-base-content">${d.totalCost.toFixed(2)}</div>
73
73
  </div>
74
74
 
75
- <div className="bg-base-content/[0.03] border border-base-content/8 rounded-xl p-3.5">
75
+ <div className="bg-base-content/[0.03] rounded-xl p-3.5">
76
76
  <div className="flex items-center justify-between mb-1">
77
77
  <span className="text-[10px] font-mono font-bold uppercase tracking-wider text-base-content/40">Sessions</span>
78
78
  {sessionsSparkData.length > 1 && (
@@ -84,7 +84,7 @@ export function QuickStats() {
84
84
  <div className="text-[20px] font-mono text-base-content">{d.totalSessions}</div>
85
85
  </div>
86
86
 
87
- <div className="bg-base-content/[0.03] border border-base-content/8 rounded-xl p-3.5">
87
+ <div className="bg-base-content/[0.03] rounded-xl p-3.5">
88
88
  <div className="flex items-center justify-between mb-1">
89
89
  <span className="text-[10px] font-mono font-bold uppercase tracking-wider text-base-content/40">Total Tokens</span>
90
90
  {tokensSparkData.length > 1 && (
@@ -96,7 +96,7 @@ export function QuickStats() {
96
96
  <div className="text-[20px] font-mono text-base-content">{formatTokens(totalTokens)}</div>
97
97
  </div>
98
98
 
99
- <div className="bg-base-content/[0.03] border border-base-content/8 rounded-xl p-3.5">
99
+ <div className="bg-base-content/[0.03] rounded-xl p-3.5">
100
100
  <div className="mb-1">
101
101
  <span className="text-[10px] font-mono font-bold uppercase tracking-wider text-base-content/40">Cache Rate</span>
102
102
  </div>
@@ -755,22 +755,22 @@ export function ChatView({ sessionId: tabSessionId, projectSlug: tabProjectSlug
755
755
  ) : messages.length === 0 ? (
756
756
  <div className="flex items-center justify-center p-10 h-full">
757
757
  <div className="text-center max-w-[360px]">
758
- <div className="text-primary mb-4 flex justify-center">
759
- <LatticeLogomark size={48} />
758
+ <div className="text-base-content/10 mb-4 flex justify-center">
759
+ <LatticeLogomark size={40} />
760
760
  </div>
761
- <p className="text-[17px] font-mono font-bold text-base-content mb-2 tracking-tight">
761
+ <p className="text-[15px] font-mono font-semibold text-base-content/60 mb-1.5">
762
762
  {activeSessionId
763
- ? "Start the conversation"
763
+ ? "What are you working on?"
764
764
  : activeProject
765
- ? "Create or select a session"
765
+ ? "Ready when you are"
766
766
  : "Select a project"}
767
767
  </p>
768
- <p className="text-[13px] text-base-content/40 leading-relaxed">
768
+ <p className="text-[12px] text-base-content/30 leading-relaxed">
769
769
  {activeSessionId
770
- ? "Type a message below to begin chatting with Claude."
770
+ ? "Type a message below or press / for commands."
771
771
  : activeProject
772
- ? "Click the + button in the sidebar to start a new session."
773
- : "Choose a project from the sidebar to get started."}
772
+ ? "Start a new session from the sidebar to chat with Claude."
773
+ : "Choose a project from the rail to get started."}
774
774
  </p>
775
775
  </div>
776
776
  </div>
@@ -251,8 +251,9 @@ function InstalledPlugins({
251
251
  <div>
252
252
  <div className="text-[12px] font-semibold text-base-content/40 mb-2">Installed Plugins</div>
253
253
  {plugins.length === 0 ? (
254
- <div className="py-6 text-center text-[13px] text-base-content/30">
255
- No plugins installed. Search the marketplace below to get started.
254
+ <div className="py-6 text-center">
255
+ <div className="text-[12px] text-base-content/30 mb-0.5">No plugins installed</div>
256
+ <div className="text-[11px] text-base-content/20">Browse the marketplace or search below to add plugins.</div>
256
257
  </div>
257
258
  ) : (
258
259
  <div className="space-y-2">
@@ -94,8 +94,9 @@ export function GlobalSkills() {
94
94
  <div>
95
95
  <div className="text-[12px] font-semibold text-base-content/40 mb-2">Installed Skills</div>
96
96
  {skills.length === 0 ? (
97
- <div className="py-4 text-center text-[13px] text-base-content/30">
98
- No global skills installed.
97
+ <div className="py-4 text-center">
98
+ <div className="text-[12px] text-base-content/30 mb-0.5">No skills installed</div>
99
+ <div className="text-[11px] text-base-content/20">Search the marketplace below to add skills.</div>
99
100
  </div>
100
101
  ) : (
101
102
  <div className="space-y-2">
@@ -135,8 +135,8 @@ export function MeshStatus() {
135
135
  {localNode ? (
136
136
  <NodeRow node={localNode} onUnpair={handleUnpair} onReconnect={handleReconnect} />
137
137
  ) : (
138
- <div className="text-[12px] text-base-content/40 italic">
139
- Waiting for node info...
138
+ <div className="text-[12px] text-base-content/30">
139
+ Loading node information...
140
140
  </div>
141
141
  )}
142
142
  </div>
@@ -156,8 +156,9 @@ export function MeshStatus() {
156
156
  </div>
157
157
 
158
158
  {remoteNodes.length === 0 ? (
159
- <div className="p-4 rounded-lg border border-dashed border-base-content/15 text-center text-[12px] text-base-content/40 italic">
160
- No paired nodes yet.
159
+ <div className="p-4 rounded-lg border border-dashed border-base-content/10 text-center">
160
+ <div className="text-[12px] text-base-content/30 mb-0.5">No paired nodes</div>
161
+ <div className="text-[11px] text-base-content/20">Connect another machine to view its projects and sessions here.</div>
161
162
  </div>
162
163
  ) : (
163
164
  remoteNodes.map(function (node) {
@@ -17,7 +17,7 @@ import type { SettingsSection } from "../../stores/sidebar";
17
17
  var SECTION_CONFIG: Record<string, { title: string }> = {
18
18
  appearance: { title: "Appearance" },
19
19
  notifications: { title: "Notifications" },
20
- claude: { title: "Claude Settings" },
20
+ claude: { title: "Claude" },
21
21
  budget: { title: "Budget" },
22
22
  environment: { title: "Environment" },
23
23
  mcp: { title: "MCP Servers" },
@@ -1,4 +1,5 @@
1
1
  import { useState, useEffect, useRef } from "react";
2
+ import { createPortal } from "react-dom";
2
3
  import { Plus } from "lucide-react";
3
4
  import type { ProjectInfo, NodeInfo } from "@lattice/shared";
4
5
  import { LatticeLogomark } from "../ui/LatticeLogomark";
@@ -88,17 +89,17 @@ function ProjectButton(props: ProjectButtonProps) {
88
89
  className={
89
90
  "w-[42px] h-[42px] flex items-center justify-center text-[11px] font-bold tracking-[0.03em] cursor-pointer transition-all duration-[120ms] flex-shrink-0 " +
90
91
  (props.isActive
91
- ? "rounded-xl bg-primary text-primary-content"
92
+ ? "rounded-xl bg-base-content/10 text-base-content ring-1 ring-base-content/20"
92
93
  : hovered
93
94
  ? "rounded-xl bg-base-200 text-base-content/60"
94
- : "rounded-full bg-base-200 text-base-content/60")
95
+ : "rounded-full bg-base-200 text-base-content/40")
95
96
  }
96
97
  >
97
98
  {initials}
98
99
  </button>
99
100
 
100
101
  {props.group.activeSessions > 0 && (
101
- <div className="absolute -top-1 -right-1 min-w-[16px] h-[16px] rounded-full bg-primary text-primary-content text-[9px] font-bold flex items-center justify-center pointer-events-none px-1">
102
+ <div className="absolute -top-0.5 -right-0.5 min-w-[14px] h-[14px] rounded-full bg-success/80 text-success-content text-[8px] font-bold flex items-center justify-center pointer-events-none px-0.5">
102
103
  {props.group.activeSessions}
103
104
  </div>
104
105
  )}
@@ -109,17 +110,17 @@ function ProjectButton(props: ProjectButtonProps) {
109
110
  <div
110
111
  key={n.nodeId}
111
112
  className={
112
- "w-[11px] h-[11px] rounded-full border-[1.5px] border-base-100 " +
113
- (n.online ? "bg-success" : "bg-error")
113
+ "w-[8px] h-[8px] rounded-full border border-base-100 " +
114
+ (n.online ? "bg-success/70" : "bg-base-content/20")
114
115
  }
115
116
  />
116
117
  );
117
118
  })}
118
119
  </div>
119
120
 
120
- {hovered && (
121
+ {hovered && createPortal(
121
122
  <div
122
- className="pointer-events-none z-[9000] bg-base-300 border border-base-content/20 rounded-lg px-2.5 py-1.5 shadow-xl"
123
+ className="pointer-events-none z-[99999] bg-base-300 border border-base-content/20 rounded-lg px-2.5 py-1.5 shadow-xl"
123
124
  style={{
124
125
  position: "fixed",
125
126
  left: "calc(64px + 8px)",
@@ -139,7 +140,8 @@ function ProjectButton(props: ProjectButtonProps) {
139
140
  </div>
140
141
  );
141
142
  })}
142
- </div>
143
+ </div>,
144
+ document.body
143
145
  )}
144
146
  </div>
145
147
  );
@@ -162,17 +164,17 @@ function NodeIndicator({ node }: { node: NodeInfo }) {
162
164
  }}
163
165
  onMouseLeave={function () { setHovered(false); }}
164
166
  className={
165
- "w-[28px] h-[28px] flex items-center justify-center text-[10px] font-bold rounded-full cursor-pointer transition-all duration-[120ms] flex-shrink-0 border-2 " +
167
+ "w-[26px] h-[26px] flex items-center justify-center text-[9px] font-semibold rounded-full cursor-pointer transition-all duration-[120ms] flex-shrink-0 border " +
166
168
  (node.online
167
- ? "border-success/50 bg-base-200 text-base-content/50 hover:bg-base-200/80"
168
- : "border-error/30 bg-base-200/50 text-base-content/25 hover:bg-base-200/60")
169
+ ? "border-success/30 bg-base-200/60 text-base-content/40 hover:bg-base-200"
170
+ : "border-base-content/10 bg-base-200/30 text-base-content/20 hover:bg-base-200/40")
169
171
  }
170
172
  >
171
173
  {initial}
172
174
  </button>
173
- {hovered && (
175
+ {hovered && createPortal(
174
176
  <div
175
- className="pointer-events-none z-[9000] bg-base-300 border border-base-content/20 rounded-lg px-2.5 py-1.5 shadow-xl"
177
+ className="pointer-events-none z-[99999] bg-base-300 border border-base-content/20 rounded-lg px-2.5 py-1.5 shadow-xl"
176
178
  style={{
177
179
  position: "fixed",
178
180
  left: "calc(64px + 8px)",
@@ -192,7 +194,8 @@ function NodeIndicator({ node }: { node: NodeInfo }) {
192
194
  <div className="text-[10px] text-base-content/30 mt-0.5">
193
195
  {node.projects.length} project{node.projects.length !== 1 ? "s" : ""}
194
196
  </div>
195
- </div>
197
+ </div>,
198
+ document.body
196
199
  )}
197
200
  </div>
198
201
  );
@@ -283,8 +286,8 @@ export function ProjectRail(props: ProjectRailProps) {
283
286
  className={
284
287
  "relative w-[42px] h-[42px] flex items-center justify-center cursor-pointer transition-all duration-[120ms] flex-shrink-0 " +
285
288
  (props.isDashboardActive
286
- ? "rounded-xl bg-primary text-primary-content"
287
- : "rounded-full bg-base-200 text-base-content/60 hover:rounded-xl hover:bg-primary/20 hover:text-primary")
289
+ ? "rounded-xl bg-base-content/10 text-base-content ring-1 ring-base-content/20"
290
+ : "rounded-full bg-base-200 text-base-content/40 hover:rounded-xl hover:bg-base-200 hover:text-base-content/60")
288
291
  }
289
292
  title="Lattice Dashboard"
290
293
  >
@@ -339,13 +342,13 @@ export function ProjectRail(props: ProjectRailProps) {
339
342
 
340
343
  <div className="flex-1" />
341
344
 
342
- {contextMenu.visible && (
345
+ {contextMenu.visible && createPortal(
343
346
  <div
344
347
  ref={menuRef}
345
348
  role="menu"
346
349
  aria-label="Project actions"
347
350
  onClick={function (e) { e.stopPropagation(); }}
348
- className="fixed z-[9999] bg-base-300 border border-base-content/20 rounded-lg shadow-2xl py-1 min-w-[160px]"
351
+ className="fixed z-[99999] bg-base-300 border border-base-content/20 rounded-lg shadow-2xl py-1 min-w-[160px]"
349
352
  style={{ left: contextMenu.x + "px", top: contextMenu.y + "px" }}
350
353
  >
351
354
  <button
@@ -387,7 +390,8 @@ export function ProjectRail(props: ProjectRailProps) {
387
390
  >
388
391
  Remove Project
389
392
  </button>
390
- </div>
393
+ </div>,
394
+ document.body
391
395
  )}
392
396
 
393
397
  </div>
@@ -475,8 +475,15 @@ export function SessionList(props: SessionListProps) {
475
475
  <div className="flex flex-col flex-1 overflow-hidden min-h-0">
476
476
  <div ref={scrollContainerRef} className="flex-1 overflow-y-auto overflow-x-hidden scrollbar-hidden py-0.5 pb-16">
477
477
  {grouped.length === 0 ? (
478
- <div className="px-3 py-2 text-sm text-base-content/40 italic">
479
- {props.filter ? "No matches" : "No sessions yet"}
478
+ <div className="px-4 py-6 text-center">
479
+ {props.filter ? (
480
+ <div className="text-[12px] text-base-content/30">No sessions match your search</div>
481
+ ) : (
482
+ <>
483
+ <div className="text-[12px] text-base-content/30 mb-1">No sessions yet</div>
484
+ <div className="text-[11px] text-base-content/20">Click + above to start a conversation with Claude</div>
485
+ </>
486
+ )}
480
487
  </div>
481
488
  ) : (
482
489
  grouped.map(function (group) {
@@ -9,29 +9,29 @@ interface SettingsSidebarProps {
9
9
 
10
10
  var SETTINGS_NAV = [
11
11
  {
12
- group: "GENERAL",
12
+ group: "",
13
13
  items: [
14
14
  { id: "appearance" as SettingsSection, label: "Appearance", icon: <Palette size={14} /> },
15
15
  { id: "notifications" as SettingsSection, label: "Notifications", icon: <Bell size={14} /> },
16
- { id: "claude" as SettingsSection, label: "Claude Settings", icon: <FileText size={14} /> },
17
- { id: "budget" as SettingsSection, label: "Budget", icon: <Wallet size={14} /> },
18
- { id: "environment" as SettingsSection, label: "Environment", icon: <Terminal size={14} /> },
19
- { id: "editor" as SettingsSection, label: "Editor", icon: <MonitorCog size={14} /> },
20
16
  ],
21
17
  },
22
18
  {
23
- group: "CONFIGURATION",
19
+ group: "CLAUDE",
24
20
  items: [
25
- { id: "rules" as SettingsSection, label: "Rules", icon: <ScrollText size={14} /> },
26
- { id: "memory" as SettingsSection, label: "Memory", icon: <Brain size={14} /> },
21
+ { id: "claude" as SettingsSection, label: "Model & Prompts", icon: <FileText size={14} /> },
22
+ { id: "budget" as SettingsSection, label: "Budget", icon: <Wallet size={14} /> },
23
+ { id: "environment" as SettingsSection, label: "Environment", icon: <Terminal size={14} /> },
24
+ { id: "editor" as SettingsSection, label: "Editor", icon: <MonitorCog size={14} /> },
27
25
  ],
28
26
  },
29
27
  {
30
- group: "INTEGRATIONS",
28
+ group: "EXTENSIONS",
31
29
  items: [
32
- { id: "mcp" as SettingsSection, label: "MCP Servers", icon: <Plug size={14} /> },
33
- { id: "skills" as SettingsSection, label: "Skills", icon: <Puzzle size={14} /> },
34
30
  { id: "plugins" as SettingsSection, label: "Plugins", icon: <Blocks size={14} /> },
31
+ { id: "skills" as SettingsSection, label: "Skills", icon: <Puzzle size={14} /> },
32
+ { id: "mcp" as SettingsSection, label: "MCP Servers", icon: <Plug size={14} /> },
33
+ { id: "rules" as SettingsSection, label: "Rules", icon: <ScrollText size={14} /> },
34
+ { id: "memory" as SettingsSection, label: "Memory", icon: <Brain size={14} /> },
35
35
  ],
36
36
  },
37
37
  {
@@ -44,7 +44,7 @@ var SETTINGS_NAV = [
44
44
 
45
45
  var PROJECT_SETTINGS_NAV = [
46
46
  {
47
- group: "GENERAL",
47
+ group: "",
48
48
  items: [
49
49
  { id: "general" as ProjectSettingsSection, label: "General", icon: <Settings size={14} /> },
50
50
  { id: "notifications" as ProjectSettingsSection, label: "Notifications", icon: <Bell size={14} /> },
@@ -53,16 +53,11 @@ var PROJECT_SETTINGS_NAV = [
53
53
  ],
54
54
  },
55
55
  {
56
- group: "INTEGRATIONS",
56
+ group: "EXTENSIONS",
57
57
  items: [
58
- { id: "mcp" as ProjectSettingsSection, label: "MCP Servers", icon: <Plug size={14} /> },
59
- { id: "skills" as ProjectSettingsSection, label: "Skills", icon: <Puzzle size={14} /> },
60
58
  { id: "plugins" as ProjectSettingsSection, label: "Plugins", icon: <Blocks size={14} /> },
61
- ],
62
- },
63
- {
64
- group: "CONFIGURATION",
65
- items: [
59
+ { id: "skills" as ProjectSettingsSection, label: "Skills", icon: <Puzzle size={14} /> },
60
+ { id: "mcp" as ProjectSettingsSection, label: "MCP Servers", icon: <Plug size={14} /> },
66
61
  { id: "rules" as ProjectSettingsSection, label: "Rules", icon: <ScrollText size={14} /> },
67
62
  { id: "permissions" as ProjectSettingsSection, label: "Permissions", icon: <Shield size={14} /> },
68
63
  { id: "memory" as ProjectSettingsSection, label: "Memory", icon: <Brain size={14} /> },
@@ -92,19 +87,21 @@ export function SettingsSidebar({ projectName, onBack }: SettingsSidebarProps) {
92
87
 
93
88
  return (
94
89
  <div className="flex flex-col h-full w-full overflow-hidden bg-base-200">
95
- <div className="px-4 h-11 border-b border-base-300 flex-shrink-0 flex items-center">
90
+ <div className="px-4 h-12 border-b border-base-300 flex-shrink-0 flex items-center">
96
91
  <span className="text-[13px] font-mono font-bold text-base-content">{headerLabel}</span>
97
92
  </div>
98
93
 
99
94
  <div className="flex flex-col flex-1 overflow-y-auto min-h-0 py-2 pb-16">
100
95
  {nav.map(function (group) {
101
96
  return (
102
- <div key={group.group} className="mb-2">
103
- <div className="px-4 pt-3 pb-1">
104
- <span className="text-[10px] font-bold tracking-wider uppercase text-base-content/40">
105
- {group.group}
106
- </span>
107
- </div>
97
+ <div key={group.group || "ungrouped"} className="mb-2">
98
+ {group.group && (
99
+ <div className="px-4 pt-3 pb-1">
100
+ <span className="text-[10px] font-bold tracking-wider uppercase text-base-content/40">
101
+ {group.group}
102
+ </span>
103
+ </div>
104
+ )}
108
105
  {group.items.map(function (item) {
109
106
  var isActive = activeSection === item.id;
110
107
  return (
@@ -114,7 +111,7 @@ export function SettingsSidebar({ projectName, onBack }: SettingsSidebarProps) {
114
111
  className={
115
112
  "w-full flex items-center gap-2.5 px-4 py-2.5 sm:py-1.5 text-[13px] transition-colors duration-[100ms] text-left " +
116
113
  (isActive
117
- ? "bg-primary/20 text-base-content font-medium"
114
+ ? "bg-base-content/8 text-base-content font-medium"
118
115
  : "text-base-content/55 hover:bg-base-content/5 hover:text-base-content")
119
116
  }
120
117
  >
@@ -261,7 +261,7 @@ export function Sidebar({ onSessionSelect }: { onSessionSelect?: () => void }) {
261
261
  <>
262
262
  {sidebar.activeView.type === "dashboard" ? (
263
263
  <>
264
- <div className="px-4 h-11 border-b border-base-300 flex-shrink-0 flex items-center gap-2">
264
+ <div className="px-4 h-12 border-b border-base-300 flex-shrink-0 flex items-center gap-2">
265
265
  <LatticeLogomark size={18} />
266
266
  <span className="text-[13px] font-mono font-bold text-base-content/90">
267
267
  Lattice
@@ -315,12 +315,12 @@ export function Sidebar({ onSessionSelect }: { onSessionSelect?: () => void }) {
315
315
  </div>
316
316
  <SectionLabel label="Projects" />
317
317
  {projects.length === 0 && nodes.some(function (n) { return !n.isLocal && n.online; }) ? (
318
- <div className="flex items-center gap-2 text-[12px] text-base-content/30 px-4">
319
- <span className="w-2 h-2 rounded-full bg-primary animate-pulse flex-shrink-0" />
320
- Loading remote projects...
318
+ <div className="flex items-center gap-2 text-[12px] text-base-content/25 px-4">
319
+ <span className="w-1.5 h-1.5 rounded-full bg-base-content/30 animate-pulse flex-shrink-0" />
320
+ Syncing remote projects...
321
321
  </div>
322
322
  ) : (
323
- <div className="text-[12px] text-base-content/40 px-4">
323
+ <div className="text-[12px] text-base-content/25 px-4">
324
324
  Select a project from the rail to view sessions.
325
325
  </div>
326
326
  )}
@@ -334,17 +334,19 @@ export function Sidebar({ onSessionSelect }: { onSessionSelect?: () => void }) {
334
334
  onClick={sidebar.toggleProjectDropdown}
335
335
  aria-label="Switch project"
336
336
  aria-expanded={sidebar.projectDropdownOpen}
337
- className="w-full px-4 h-11 border-b border-base-300 flex-shrink-0 flex items-center justify-between cursor-pointer hover:bg-base-300/30 transition-colors text-left"
337
+ className={"w-full px-4 border-b border-base-300 flex-shrink-0 flex items-center justify-between cursor-pointer hover:bg-base-300/30 transition-colors text-left " + (activeProject?.isRemote ? "h-14 py-2" : "h-12 py-2.5")}
338
338
  >
339
- <span className="text-[13px] font-mono font-bold text-base-content/90">
340
- {activeProject?.title ?? (projects.length === 0 ? "Loading..." : "No Project")}
339
+ <div className="min-w-0">
340
+ <div className="text-[13px] font-mono font-bold text-base-content/90 truncate">
341
+ {activeProject?.title ?? (projects.length === 0 ? "Loading..." : "No Project")}
342
+ </div>
341
343
  {activeProject?.isRemote && (
342
- <span className="ml-1.5 text-[10px] font-normal text-base-content/30">
344
+ <div className="text-[10px] text-base-content/30 font-mono">
343
345
  on {activeProject.nodeName}
344
- </span>
346
+ </div>
345
347
  )}
346
- </span>
347
- <ChevronDown size={14} className="text-base-content/30" />
348
+ </div>
349
+ <ChevronDown size={14} className="text-base-content/30 flex-shrink-0" />
348
350
  </button>
349
351
 
350
352
  <button
@@ -361,8 +363,6 @@ export function Sidebar({ onSessionSelect }: { onSessionSelect?: () => void }) {
361
363
  { type: "files" as const, icon: FolderOpen, label: "Files", localOnly: true },
362
364
  { type: "terminal" as const, icon: TerminalSquare, label: "Terminal", localOnly: true },
363
365
  { type: "notes" as const, icon: StickyNote, label: "Notes", localOnly: false },
364
- { type: "tasks" as const, icon: Calendar, label: "Tasks", localOnly: false },
365
- { type: "bookmarks" as const, icon: Bookmark, label: "Bookmarks", localOnly: false },
366
366
  ].map(function (item) {
367
367
  var isDisabled = item.localOnly && activeProject?.isRemote;
368
368
  return (
@@ -86,7 +86,7 @@ export function UserIsland(props: UserIslandProps) {
86
86
  className="flex items-center gap-2 flex-1 min-w-0 rounded-lg px-1 py-1 -mx-1 hover:bg-base-content/5 transition-colors duration-[120ms] cursor-pointer"
87
87
  aria-label="Node info"
88
88
  >
89
- <div className="w-7 h-7 rounded-full bg-primary text-primary-content text-[12px] font-bold flex items-center justify-center flex-shrink-0">
89
+ <div className="w-7 h-7 rounded-full bg-base-content/10 text-base-content/50 text-[12px] font-bold flex items-center justify-center flex-shrink-0">
90
90
  {initial}
91
91
  </div>
92
92
  <div className="flex-1 min-w-0 text-left">
@@ -96,7 +96,7 @@ export function UserIsland(props: UserIslandProps) {
96
96
  <div className="text-[10px] font-mono flex items-center gap-1">
97
97
  <span className="text-base-content/30">{"v" + currentVersion}</span>
98
98
  {updateAvailable && latestVersion && (
99
- <span className="flex items-center gap-0.5 text-primary/70">
99
+ <span className="flex items-center gap-0.5 text-base-content/30">
100
100
  <ArrowUpCircle size={9} />
101
101
  {latestVersion}
102
102
  </span>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cryptiklemur/lattice",
3
- "version": "1.42.1",
3
+ "version": "1.42.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>",
@@ -27,7 +27,15 @@ export function addPeer(peer: PeerInfo): void {
27
27
  if (idx >= 0) {
28
28
  peers[idx] = peer;
29
29
  } else {
30
- peers.push(peer);
30
+ var addrSet = new Set(peer.addresses);
31
+ var dupeIdx = peers.findIndex(function (p) {
32
+ return p.addresses.some(function (a) { return addrSet.has(a); });
33
+ });
34
+ if (dupeIdx >= 0) {
35
+ peers[dupeIdx] = peer;
36
+ } else {
37
+ peers.push(peer);
38
+ }
31
39
  }
32
40
  savePeers(peers);
33
41
  }