Best Practices
Create the WorkspaceClient outside React
The client holds the panel registry, initial layout, and imperative API. Create it as a module-level singleton or in a stable context — not inside a component body.
// ✅ Module-level singleton — stable across renders
export const client = new WorkspaceClient({
panels: {
map: { component: MapPanel },
editor: { component: EditorPanel },
},
initialState: localStorage.getItem('layout'),
});// ❌ Inside a component — recreated on every render
function App() {
const client = new WorkspaceClient({ panels: { ... } }); // Wrong!
return <WindowManagerProvider client={client}>...</WindowManagerProvider>;
}Use isOpen before opening a panel
Avoid duplicate panels by checking first:
if (!client.isOpen('my-map')) {
client.openPanel('my-map', 'map', { title: 'Map' });
} else {
client.focusPanel('my-map');
}Prefer focusPanel over re-opening
openPanel(id, key) on an already-open panel re-focuses it, but using focusPanel(id) is more explicit and semantically correct when you just want to bring a panel to the user's attention.
Persist layouts with beforeunload
window.addEventListener('beforeunload', () => {
localStorage.setItem('workspace-layout', client.saveLayout());
});Keep panel component keys stable
Component keys are stored inside saveLayout() JSON. Renaming a key breaks all saved layouts. If you must rename a key, add a migration step in loadLayout before passing the JSON to the client.
Use dirty state for important editors
const actions = useWindowManagerActions();
// Mark dirty when the user edits
actions.setPanelDirty('editor-1', true);
// Clear when saved
actions.setPanelDirty('editor-1', false);The built-in close guard will automatically prompt the user before closing a dirty panel.
Keep panel components pure of layout concerns
Panel components should focus on content, not layout. Use the imperative API (client.*) or the useWindowManagerActions() hook for layout operations triggered by user interaction inside a panel.
function MyPanel({ panelId }: { panelId: string }) {
const actions = useWindowManagerActions();
return (
<button onClick={() => actions.floatPanel(panelId)}>
Pop out
</button>
);
}