@cryptiklemur/lattice 1.24.5 → 1.25.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.
@@ -419,7 +419,7 @@ export function ChatView({ sessionId: tabSessionId, projectSlug: tabProjectSlug
419
419
  return (
420
420
  <div className="flex flex-col h-full w-full bg-base-100 overflow-hidden relative">
421
421
  <div className="bg-base-100 border-b border-base-300 flex-shrink-0 px-2 sm:px-4">
422
- <div className="flex items-center min-h-10 sm:min-h-12 gap-1.5">
422
+ <div className="flex items-center h-11 gap-1.5">
423
423
  <button
424
424
  className="btn btn-ghost btn-sm btn-square lg:hidden"
425
425
  aria-label="Toggle sidebar"
@@ -90,7 +90,7 @@ export function SettingsSidebar({ projectName, onBack }: SettingsSidebarProps) {
90
90
 
91
91
  return (
92
92
  <div className="flex flex-col h-full w-full overflow-hidden bg-base-200">
93
- <div className="px-4 py-3 border-b border-base-300 flex-shrink-0">
93
+ <div className="px-4 h-11 border-b border-base-300 flex-shrink-0 flex items-center">
94
94
  <span className="text-[13px] font-mono font-bold text-base-content">{headerLabel}</span>
95
95
  </div>
96
96
 
@@ -327,7 +327,7 @@ export function Sidebar({ onSessionSelect }: { onSessionSelect?: () => void }) {
327
327
  onClick={sidebar.toggleProjectDropdown}
328
328
  aria-label="Switch project"
329
329
  aria-expanded={sidebar.projectDropdownOpen}
330
- className="w-full px-4 py-3 border-b border-base-300 flex-shrink-0 flex items-center justify-between cursor-pointer hover:bg-base-300/30 transition-colors text-left"
330
+ 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"
331
331
  >
332
332
  <span className="text-[13px] font-mono font-bold text-base-content/90">
333
333
  {activeProject?.title ?? "No Project"}
@@ -2,7 +2,7 @@ import { Store } from "@tanstack/react-store";
2
2
  import type { ProjectSettingsSection } from "@lattice/shared";
3
3
  import { encodeWorkspaceUrl, decodeWorkspaceUrl, isLegacySessionUrl, shortSessionId } from "../lib/workspace-url";
4
4
  import type { DecodedWorkspace } from "../lib/workspace-url";
5
- import { getWorkspaceStore, restoreWorkspace, setUrlSyncCallback } from "./workspace";
5
+ import { getWorkspaceStore, restoreWorkspace, setUrlSyncCallback, switchProjectWorkspace, setCurrentProjectKey } from "./workspace";
6
6
 
7
7
  export type { ProjectSettingsSection };
8
8
 
@@ -106,6 +106,7 @@ function parseInitialUrl(): ParsedUrl {
106
106
 
107
107
  const initialUrl = parseInitialUrl();
108
108
 
109
+ setCurrentProjectKey(initialUrl.settingsSection ? null : initialUrl.projectSlug);
109
110
  if (initialUrl.decodedWorkspace) {
110
111
  restoreWorkspace(initialUrl.decodedWorkspace);
111
112
  }
@@ -196,6 +197,8 @@ export function getSidebarStore(): Store<SidebarState> {
196
197
  }
197
198
 
198
199
  export function setActiveProjectSlug(slug: string | null): void {
200
+ let prevSlug = sidebarStore.state.activeProjectSlug;
201
+ switchProjectWorkspace(prevSlug, slug);
199
202
  sidebarStore.setState(function (state) {
200
203
  return {
201
204
  ...state,
@@ -416,6 +416,77 @@ export function restoreWorkspace(data: DecodedWorkspace): void {
416
416
  urlSyncSuppressed = false;
417
417
  }
418
418
 
419
+ var currentProjectKey: string = "__global__";
420
+
421
+ export function setCurrentProjectKey(slug: string | null): void {
422
+ currentProjectKey = slug || "__global__";
423
+ }
424
+
425
+ function storageKey(projectSlug: string | null): string {
426
+ return "lattice:workspace:" + (projectSlug || "__global__");
427
+ }
428
+
429
+ export function saveWorkspaceForProject(projectSlug: string | null): void {
430
+ let key = storageKey(projectSlug);
431
+ let state = workspaceStore.state;
432
+ try {
433
+ localStorage.setItem(key, JSON.stringify({
434
+ tabs: state.tabs,
435
+ panes: state.panes,
436
+ activePaneId: state.activePaneId,
437
+ splitDirection: state.splitDirection,
438
+ splitRatio: state.splitRatio,
439
+ }));
440
+ } catch {}
441
+ }
442
+
443
+ export function loadWorkspaceForProject(projectSlug: string | null): void {
444
+ let key = storageKey(projectSlug);
445
+ currentProjectKey = projectSlug || "__global__";
446
+
447
+ try {
448
+ let raw = localStorage.getItem(key);
449
+ if (raw) {
450
+ let saved = JSON.parse(raw) as WorkspaceState;
451
+ if (saved.tabs && saved.panes && saved.tabs.length > 0 && saved.panes.length > 0) {
452
+ urlSyncSuppressed = true;
453
+ workspaceStore.setState(function () {
454
+ return {
455
+ tabs: saved.tabs,
456
+ panes: saved.panes,
457
+ activePaneId: saved.activePaneId || saved.panes[0].id,
458
+ splitDirection: saved.splitDirection || null,
459
+ splitRatio: saved.splitRatio || 0.5,
460
+ };
461
+ });
462
+ urlSyncSuppressed = false;
463
+ return;
464
+ }
465
+ }
466
+ } catch {}
467
+
468
+ urlSyncSuppressed = true;
469
+ workspaceStore.setState(function () {
470
+ return {
471
+ tabs: [{ id: "chat", type: "chat" as TabType, label: "Chat", closeable: false }],
472
+ panes: [{ id: "pane-1", tabIds: ["chat"], activeTabId: "chat" }],
473
+ activePaneId: "pane-1",
474
+ splitDirection: null,
475
+ splitRatio: 0.5,
476
+ };
477
+ });
478
+ urlSyncSuppressed = false;
479
+ }
480
+
481
+ export function switchProjectWorkspace(fromSlug: string | null, toSlug: string | null): void {
482
+ saveWorkspaceForProject(fromSlug);
483
+ loadWorkspaceForProject(toSlug);
484
+ }
485
+
419
486
  workspaceStore.subscribe(function () {
420
487
  notifyUrlSync();
488
+ // Auto-save on every state change
489
+ try {
490
+ saveWorkspaceForProject(currentProjectKey === "__global__" ? null : currentProjectKey);
491
+ } catch {}
421
492
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cryptiklemur/lattice",
3
- "version": "1.24.5",
3
+ "version": "1.25.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>",