@cryptiklemur/lattice 1.43.8 → 1.44.0

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.
@@ -3,6 +3,7 @@ import { X, Columns2, Rows2, MessageSquare, FolderOpen, TerminalSquare, StickyNo
3
3
  import { useWorkspace } from "../../hooks/useWorkspace";
4
4
  import { useSession } from "../../hooks/useSession";
5
5
  import type { Tab, TabType } from "../../stores/workspace";
6
+ import { pinTab } from "../../stores/workspace";
6
7
  import { formatSessionTitle } from "../../utils/formatSessionTitle";
7
8
 
8
9
  interface TabBarProps {
@@ -155,6 +156,7 @@ export function TabBar({ paneId, isActivePane }: TabBarProps) {
155
156
  tabIndex={0}
156
157
  aria-selected={isActive}
157
158
  onClick={function () { handleTabClick(tab.id); }}
159
+ onDoubleClick={function () { if (!tab.pinned) pinTab(tab.id); }}
158
160
  onMouseDown={function (e) { handleMiddleClick(e, tab); }}
159
161
  onKeyDown={function (e) {
160
162
  if (e.key === "Enter" || e.key === " ") {
@@ -171,7 +173,7 @@ export function TabBar({ paneId, isActivePane }: TabBarProps) {
171
173
  }
172
174
  >
173
175
  <Icon size={14} className={isActive ? "text-primary" : ""} />
174
- <span className="truncate text-[12px]">{label}</span>
176
+ <span className={"truncate text-[12px]" + (tab.pinned ? "" : " italic")}>{label}</span>
175
177
  {tab.closeable && (
176
178
  <button
177
179
  aria-label={"Close " + label + " tab"}
@@ -18,7 +18,7 @@ import type {
18
18
  } from "@lattice/shared";
19
19
  import { useWebSocket } from "./useWebSocket";
20
20
  import { setActiveSessionId as setSidebarSessionId } from "../stores/sidebar";
21
- import { updateSessionTabTitle } from "../stores/workspace";
21
+ import { updateSessionTabTitle, pinTab } from "../stores/workspace";
22
22
  import {
23
23
  getSessionStore,
24
24
  setSessionMessages,
@@ -120,6 +120,7 @@ export function useSession(): UseSessionReturn {
120
120
  setPromptSuggestion(null);
121
121
  setIsProcessing(true);
122
122
  setSessionBusy(false);
123
+ pinTab("chat-" + currentSessionId);
123
124
  sendRef.current(msg as ChatSendMessage);
124
125
  }
125
126
 
@@ -156,17 +156,19 @@ export function decodeWorkspaceUrl(
156
156
  type: "chat",
157
157
  label: "Session",
158
158
  closeable: true,
159
+ pinned: true,
159
160
  sessionId: resolvedId,
160
161
  projectSlug: projectSlug,
161
162
  };
162
163
  } else if (tabType === "chat") {
163
- tab = { id: "chat", type: "chat", label: "Chat", closeable: false };
164
+ tab = { id: "chat", type: "chat", label: "Chat", closeable: false, pinned: true };
164
165
  } else {
165
166
  tab = {
166
167
  id: tabType,
167
168
  type: tabType,
168
169
  label: labels[tabType],
169
170
  closeable: true,
171
+ pinned: true,
170
172
  };
171
173
  }
172
174
 
@@ -190,7 +192,7 @@ export function decodeWorkspaceUrl(
190
192
  }
191
193
 
192
194
  if (panes.length === 0) {
193
- const defaultTab: Tab = { id: "chat", type: "chat", label: "Chat", closeable: false };
195
+ const defaultTab: Tab = { id: "chat", type: "chat", label: "Chat", closeable: false, pinned: true };
194
196
  tabs.push(defaultTab);
195
197
  panes.push({ id: "pane-1", tabIds: ["chat"], activeTabId: "chat" });
196
198
  }
@@ -8,6 +8,7 @@ export interface Tab {
8
8
  type: TabType;
9
9
  label: string;
10
10
  closeable: boolean;
11
+ pinned: boolean;
11
12
  sessionId?: string;
12
13
  projectSlug?: string;
13
14
  }
@@ -26,7 +27,7 @@ export interface WorkspaceState {
26
27
  splitRatio: number;
27
28
  }
28
29
 
29
- var CHAT_TAB: Tab = { id: "chat", type: "chat", label: "Chat", closeable: false };
30
+ var CHAT_TAB: Tab = { id: "chat", type: "chat", label: "Chat", closeable: false, pinned: true };
30
31
 
31
32
  var DEFAULT_PANE: Pane = { id: "pane-1", tabIds: ["chat"], activeTabId: "chat" };
32
33
 
@@ -77,6 +78,7 @@ export function openTab(type: TabType): void {
77
78
  type: type,
78
79
  label: labels[type],
79
80
  closeable: type !== "chat",
81
+ pinned: true,
80
82
  };
81
83
 
82
84
  var defaultChat = state.tabs.find(function (t) { return t.id === "chat" && !t.sessionId; });
@@ -135,36 +137,55 @@ export function openSessionTab(sessionId: string, projectSlug: string, title: st
135
137
  type: "chat",
136
138
  label: title || "Session",
137
139
  closeable: true,
140
+ pinned: false,
138
141
  sessionId: sessionId,
139
142
  projectSlug: projectSlug,
140
143
  };
141
144
 
145
+ var previewTab = state.tabs.find(function (t) { return t.type === "chat" && t.sessionId && !t.pinned; });
142
146
  var hadDefaultChat = state.tabs.some(function (t) { return t.id === "chat"; });
143
147
 
144
- var newTabs = hadDefaultChat
145
- ? state.tabs.filter(function (t) { return t.id !== "chat"; }).concat([tab])
146
- : [...state.tabs, tab];
147
-
148
- var newPanes = state.panes.map(function (p) {
149
- var updatedTabIds = hadDefaultChat
150
- ? p.tabIds.map(function (id) { return id === "chat" ? tabId : id; })
151
- : (p.id === state.activePaneId ? [...p.tabIds, tabId] : p.tabIds);
148
+ var newTabs: Tab[];
149
+ var newPanes: Pane[];
152
150
 
153
- var needsActiveUpdate = hadDefaultChat
154
- ? (p.activeTabId === "chat" || p.id === state.activePaneId)
155
- : p.id === state.activePaneId;
151
+ if (previewTab) {
152
+ var oldId = previewTab.id;
153
+ newTabs = state.tabs.map(function (t) { return t.id === oldId ? tab : t; });
154
+ newPanes = state.panes.map(function (p) {
155
+ var updatedTabIds = p.tabIds.map(function (id) { return id === oldId ? tabId : id; });
156
+ return {
157
+ ...p,
158
+ tabIds: updatedTabIds,
159
+ activeTabId: p.activeTabId === oldId ? tabId : p.activeTabId,
160
+ };
161
+ });
162
+ } else if (hadDefaultChat) {
163
+ newTabs = state.tabs.filter(function (t) { return t.id !== "chat"; }).concat([tab]);
164
+ newPanes = state.panes.map(function (p) {
165
+ var updatedTabIds = p.tabIds.map(function (id) { return id === "chat" ? tabId : id; });
166
+ var needsActiveUpdate = p.activeTabId === "chat" || p.id === state.activePaneId;
167
+ return { ...p, tabIds: updatedTabIds, activeTabId: needsActiveUpdate ? tabId : p.activeTabId };
168
+ });
169
+ } else {
170
+ newTabs = [...state.tabs, tab];
171
+ newPanes = state.panes.map(function (p) {
172
+ return p.id === state.activePaneId
173
+ ? { ...p, tabIds: [...p.tabIds, tabId], activeTabId: tabId }
174
+ : p;
175
+ });
176
+ }
156
177
 
157
- return {
158
- ...p,
159
- tabIds: updatedTabIds,
160
- activeTabId: needsActiveUpdate ? tabId : p.activeTabId,
161
- };
162
- });
178
+ return { ...state, tabs: newTabs, panes: newPanes };
179
+ });
180
+ }
163
181
 
182
+ export function pinTab(tabId: string): void {
183
+ workspaceStore.setState(function (state) {
164
184
  return {
165
185
  ...state,
166
- tabs: newTabs,
167
- panes: newPanes,
186
+ tabs: state.tabs.map(function (t) {
187
+ return t.id === tabId ? { ...t, pinned: true } : t;
188
+ }),
168
189
  };
169
190
  });
170
191
  }
@@ -198,7 +219,7 @@ export function closeTab(tabId: string): void {
198
219
  var isLastChatTab = tab.type === "chat" && chatTabCount <= 1;
199
220
 
200
221
  if (isLastChatTab) {
201
- var replacementTab: Tab = { id: "chat", type: "chat", label: "Chat", closeable: false };
222
+ var replacementTab: Tab = { id: "chat", type: "chat", label: "Chat", closeable: false, pinned: true };
202
223
  var replacedTabs = state.tabs.map(function (t) {
203
224
  if (t.id === tabId) return replacementTab;
204
225
  return t;
@@ -236,7 +257,7 @@ export function closeTab(tabId: string): void {
236
257
 
237
258
  var hasEmptySinglePane = newPanes.length === 1 && newPanes[0].tabIds.length === 0;
238
259
  if (hasEmptySinglePane) {
239
- var defaultTab: Tab = { id: "chat", type: "chat", label: "Chat", closeable: false };
260
+ var defaultTab: Tab = { id: "chat", type: "chat", label: "Chat", closeable: false, pinned: true };
240
261
  return {
241
262
  tabs: [defaultTab],
242
263
  panes: [{ ...newPanes[0], tabIds: ["chat"], activeTabId: "chat" }],
@@ -468,7 +489,7 @@ export function loadWorkspaceForProject(projectSlug: string | null): void {
468
489
  urlSyncSuppressed = true;
469
490
  workspaceStore.setState(function () {
470
491
  return {
471
- tabs: [{ id: "chat", type: "chat" as TabType, label: "Chat", closeable: false }],
492
+ tabs: [{ id: "chat", type: "chat" as TabType, label: "Chat", closeable: false, pinned: true }],
472
493
  panes: [{ id: "pane-1", tabIds: ["chat"], activeTabId: "chat" }],
473
494
  activePaneId: "pane-1",
474
495
  splitDirection: null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cryptiklemur/lattice",
3
- "version": "1.43.8",
3
+ "version": "1.44.0",
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>",