Skip to content

Commit c972f29

Browse files
MarkoVcodeclaude
andcommitted
Add localStorage persistence for workbench window arrangement
Implements persistent state for instrument windows and mosaic layout across page reloads. Changes: - WorkbenchLayout: Save/restore activeInstruments array from localStorage - EditorArea: Save/restore mosaic layout configuration from localStorage - Layout is restored only if it matches currently active instruments - Graceful error handling for localStorage failures Fixes user request to maintain window arrangement between reloads. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 54db5f7 commit c972f29

File tree

2 files changed

+62
-2
lines changed

2 files changed

+62
-2
lines changed

benchmesh-serial-service/frontend/src/ui/workbench/EditorArea/EditorArea.tsx

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,30 @@ export const EditorArea: React.FC<EditorAreaProps> = ({
3434
React.useEffect(() => {
3535
if (activeInstruments.length === 0) {
3636
setMosaicLayout(null);
37+
localStorage.removeItem('benchmesh:mosaicLayout');
3738
return;
3839
}
3940

41+
// Try to restore saved layout if it matches current instruments
42+
try {
43+
const savedLayout = localStorage.getItem('benchmesh:mosaicLayout');
44+
if (savedLayout) {
45+
const parsed = JSON.parse(savedLayout);
46+
// Verify saved layout contains only current active instruments
47+
const layoutIds = extractLayoutIds(parsed);
48+
const sameInstruments =
49+
layoutIds.length === activeInstruments.length &&
50+
layoutIds.every((id: string) => activeInstruments.includes(id));
51+
52+
if (sameInstruments) {
53+
setMosaicLayout(parsed);
54+
return;
55+
}
56+
}
57+
} catch (e) {
58+
console.error('Failed to restore mosaic layout:', e);
59+
}
60+
4061
if (activeInstruments.length === 1) {
4162
setMosaicLayout(activeInstruments[0]);
4263
return;
@@ -66,6 +87,26 @@ export const EditorArea: React.FC<EditorAreaProps> = ({
6687
setMosaicLayout(buildLayout(activeInstruments));
6788
}, [activeInstruments]);
6889

90+
// Persist mosaic layout when it changes (user rearranges windows)
91+
const handleMosaicChange = React.useCallback((newLayout: MosaicNode<string> | null) => {
92+
setMosaicLayout(newLayout);
93+
if (newLayout) {
94+
try {
95+
localStorage.setItem('benchmesh:mosaicLayout', JSON.stringify(newLayout));
96+
} catch (e) {
97+
console.error('Failed to save mosaic layout:', e);
98+
}
99+
}
100+
}, []);
101+
102+
// Helper to extract all instrument IDs from a mosaic layout
103+
const extractLayoutIds = (node: MosaicNode<string>): string[] => {
104+
if (typeof node === 'string') {
105+
return [node];
106+
}
107+
return [...extractLayoutIds(node.first), ...extractLayoutIds(node.second)];
108+
};
109+
69110
const renderTile = (id: string, path: MosaicBranch[]) => {
70111
// Parse class-specific ID format: ${instrument.id}:${classCode}
71112
const [instrumentId, classCode] = id.includes(':') ? id.split(':') : [id, undefined];
@@ -110,7 +151,7 @@ export const EditorArea: React.FC<EditorAreaProps> = ({
110151
<Mosaic<string>
111152
renderTile={renderTile}
112153
value={mosaicLayout}
113-
onChange={setMosaicLayout}
154+
onChange={handleMosaicChange}
114155
className="editor__mosaic"
115156
/>
116157
</div>

benchmesh-serial-service/frontend/src/ui/workbench/WorkbenchLayout.tsx

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,26 @@ export const WorkbenchLayout: React.FC<WorkbenchLayoutProps> = ({
6767
const [bottomPanelCollapsed, setBottomPanelCollapsed] = useState(true);
6868
const [rightPanelCollapsed, setRightPanelCollapsed] = useState(true); // Right panel not yet implemented
6969
const [activeView, setActiveView] = useState<ViewType>('instruments');
70-
const [activeInstruments, setActiveInstruments] = useState<string[]>([]);
70+
71+
// Initialize activeInstruments from localStorage
72+
const [activeInstruments, setActiveInstruments] = useState<string[]>(() => {
73+
try {
74+
const saved = localStorage.getItem('benchmesh:activeInstruments');
75+
return saved ? JSON.parse(saved) : [];
76+
} catch (e) {
77+
console.error('Failed to load active instruments from localStorage:', e);
78+
return [];
79+
}
80+
});
81+
82+
// Persist activeInstruments to localStorage whenever it changes
83+
React.useEffect(() => {
84+
try {
85+
localStorage.setItem('benchmesh:activeInstruments', JSON.stringify(activeInstruments));
86+
} catch (e) {
87+
console.error('Failed to save active instruments to localStorage:', e);
88+
}
89+
}, [activeInstruments]);
7190

7291
const handleInstrumentClick = (instrumentId: string) => {
7392
setActiveInstruments((prev) => {

0 commit comments

Comments
 (0)