How Sidebar Disclosure State is Persisted in Nodeterm
Nodeterm persists sidebar disclosure state using a sidebarCollapsedItems map stored in a Zustand-based global settings store that serializes to settings.json on disk, with automatic pruning of stale keys to prevent bloating.
The nodeterm terminal manager remembers which folders and groups you've collapsed or expanded across application restarts. This persistence mechanism relies on a centralized settings architecture that tracks the open/closed state of every tree node using unique string keys and boolean flags managed through the application's global state layer.
The Core Data Structure in src/shared/types.ts
The foundation of sidebar state persistence is the Settings type defined in src/shared/types.ts. This interface declares a sidebarCollapsedItems property that acts as the single source of truth for all disclosure states.
// src/shared/types.ts
export type Settings = {
// ... other settings
/** Explicit toggles live in `sidebarCollapsedItems` and always win. */
sidebarCollapsedItems: Record<string, boolean>
// ... other settings
}
In this Record, each key uniquely identifies a disclosure element (such as a project or group), while the boolean value indicates the collapsed state: true means collapsed (closed) and false means expanded (open).
State Management and Persistence Layer
The settings store is implemented as a Zustand store that handles both state management and disk persistence. Two files work together to provide this functionality:
src/core/settings-store.ts– Implements the Zustand store and handles read/write operations tosettings.jsonon disksrc/renderer/state/settings.ts– Exposes theuseSettingshook that React components use to access and modify the store
When any component updates sidebarCollapsedItems, the Zustand store automatically persists the entire Settings object to disk. This ensures that collapse/expand actions survive application restarts and can synchronize across machines when the workspace is shared via version control.
Toggling Disclosure in the UI
The src/renderer/components/SessionsSidebar.tsx component handles user interactions with the sidebar tree. When a user clicks to toggle a disclosure item, the component constructs a unique key and updates the global settings:
// src/renderer/components/SessionsSidebar.tsx
const collapsedItems = useSettings((s) => s.settings.sidebarCollapsedItems)
// Toggle a specific element
const toggle = (key: string) => {
const currentlyCollapsed = collapsedItems[key] ?? false
useSettings.getState().setSettings({
sidebarCollapsedItems: { ...collapsedItems, [key]: !currentlyCollapsed }
})
}
Keys follow a structured format to ensure uniqueness. For projects, the key might be project:abc123, while groups use a composite format like ${projectId}:group:${groupId}. This namespacing prevents collisions between different entity types.
Pruning Stale Entries
Over time, the sidebarCollapsedItems map can accumulate orphaned keys when projects or groups are deleted. To prevent the persisted state from bloating, the codebase implements a pruning mechanism in src/renderer/lib/sessionList.ts.
Before persisting changes, the system filters the map to remove entries whose keys no longer correspond to live projects or groups:
// src/renderer/lib/sessionList.ts
const pruned = Object.fromEntries(
Object.entries(sidebarCollapsedItems).filter(([k]) => isKeyLive(k))
)
This pruning logic runs during render cycles, ensuring that the settings.json file always reflects the current workspace hierarchy without retaining dead references.
Practical Implementation Examples
Reading the Collapsed State for a Project
To check whether a specific project is currently collapsed in the sidebar, access the settings store directly:
import { useSettings } from '@/renderer/state/settings'
const projectId = 'project:abc123'
const isCollapsed = useSettings.getState().settings.sidebarCollapsedItems[projectId] ?? false
Toggling a Group's Disclosure
Programmatically toggle a group's expand/collapse state by constructing the proper key format and updating the settings:
import { useSettings } from '@/renderer/state/settings'
function toggleGroup(projectId: string, groupId: string) {
const key = `${projectId}:group:${groupId}`
const current = useSettings.getState().settings.sidebarCollapsedItems[key] ?? false
useSettings.getState().setSettings({
sidebarCollapsedItems: {
...useSettings.getState().settings.sidebarCollapsedItems,
[key]: !current,
},
})
}
Triggering Automatic Pruning
To clean up stale entries manually or during specific lifecycle events, invoke the pruning utility:
import { pruneSidebarCollapsedItems } from '@/renderer/lib/sessionList'
// Called on each render pass or specific cleanup event
pruneSidebarCollapsedItems()
Summary
- Nodeterm uses a centralized
sidebarCollapsedItemsmap in the globalSettingstype to track disclosure states - The Zustand store in
src/core/settings-store.tspersists this data tosettings.jsonautomatically on every change - UI components in
src/renderer/components/SessionsSidebar.tsxtoggle states using theuseSettingshook with unique string keys - The system prunes stale keys via
src/renderer/lib/sessionList.tsto prevent orphaned entries from accumulating - Boolean logic uses
truefor collapsed andfalsefor expanded, withfalseas the default when a key is absent
Frequently Asked Questions
Where is the sidebar disclosure state physically stored?
The state is stored in a settings.json file on disk, managed by the Zustand store implementation in src/core/settings-store.ts. The actual data structure is a Record<string, boolean> called sidebarCollapsedItems that lives inside the global Settings object defined in src/shared/types.ts.
How does Nodeterm handle sidebar state when projects are deleted?
The system automatically prunes stale entries using logic in src/renderer/lib/sessionList.ts. This code filters the sidebarCollapsedItems map before persistence, removing any keys that no longer match live projects or groups in the current workspace, preventing the settings file from accumulating dead references.
Can sidebar disclosure state be synchronized across multiple machines?
Yes. Because the state lives in settings.json at the workspace level, it can be committed to version control along with other project files. When the workspace is cloned or pulled on another machine, the sidebar disclosure preferences transfer automatically, providing a consistent UI state across different development environments.
What is the key format used for sidebar items?
The key format varies by entity type. Projects typically use a prefix like project:${id}, while groups use a composite format: ${projectId}:group:${groupId}. This string-based namespacing ensures that categories, projects, and groups maintain unique identifiers within the flat sidebarCollapsedItems map.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →