Guide for working with workspace tabs, tab management, and duplicate tab prevention. Use when fixing tab bugs, adding tab features, or working with openTab/addNewTab functions.
This skill covers the workspace tab system in Nomendex, including how tabs are created, managed, and how duplicate detection works.
src/hooks/useWorkspace.tsx - Core tab management logicsrc/components/NotesCommandMenu.tsx - CMD+P quick file openersrc/contexts/WorkspaceContext.tsx - Provides workspace functions to componentssrc/types/Workspace.ts - Tab and workspace type definitionsaddNewTab() - Always Creates New TabaddNewTab({ pluginMeta, view, props })
openTab() - Smart Tab Opening (Preferred)openTab({ pluginMeta, view, props })
openTab() matches tabs based on:
| Plugin | Props Matched |
|---|---|
| notes | noteFileName |
| todos | project (for browser/kanban views) |
| tags | tagName |
| others | Empty props match empty props |
The duplicate check MUST happen inside the updateWorkspace callback to access latest state:
// WRONG - uses stale closure, won't detect recent tabs
const openTab = useCallback(({ pluginMeta, view, props }) => {
const existingTab = workspace.tabs.find(tab => /* ... */); // STALE!
if (existingTab) {
updateWorkspace({ activeTabId: existingTab.id });
return existingTab;
}
// ...
}, [workspace.tabs]); // Even with dependency, still stale between renders
// CORRECT - uses prev.tabs from callback for latest state
const openTab = useCallback(({ pluginMeta, view, props }) => {
let resultTab = null;
updateWorkspace((prev) => {
const existingTab = prev.tabs.find(tab => /* ... */); // FRESH!
if (existingTab) {
resultTab = existingTab;
return { ...prev, activeTabId: existingTab.id };
}
// Create new tab...
resultTab = newTab;
return { ...prev, tabs: [...prev.tabs, newTab], activeTabId: newTab.id };
});
return resultTab;
}, [createPluginInstance, updateWorkspace]);
const { openTab } = useWorkspaceContext();
openTab({
pluginMeta: notesPluginSerial,
view: "editor",
props: { noteFileName: "my-note.md" },
});
openTab({
pluginMeta: todosPluginSerial,
view: "browser",
props: { project: "work" },
});
openTab({
pluginMeta: chatPluginSerial,
view: "default",
props: {},
});
addNewTab vs openTabopenTab, verify the duplicate check uses prev.tabs not workspace.tabsupdateWorkspace callback to see actual tab statenoteFileName not noteId)interface WorkspaceTab {
id: string; // Unique tab ID
title: string; // Display title
pluginInstance: {
instanceId: string;
plugin: SerializablePlugin;
instanceProps: Record<string, unknown>;
viewId: string;
};
}