# How Sidebar Disclosure State is Persisted in Nodeterm

> Discover how Nodeterm persists sidebar disclosure state using a global settings store and local settings.json file, ensuring efficient and clean data management.

- Repository: [eneskirca/nodeterm](https://github.com/eneskirca/nodeterm)
- Tags: internals
- Published: 2026-08-26

---

**Nodeterm persists sidebar disclosure state using a `sidebarCollapsedItems` map stored in a Zustand-based global settings store that serializes to [`settings.json`](https://github.com/eneskirca/nodeterm/blob/main/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`](https://github.com/eneskirca/nodeterm/blob/main/src/shared/types.ts)

The foundation of sidebar state persistence is the `Settings` type defined in **[`src/shared/types.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/shared/types.ts)**. This interface declares a `sidebarCollapsedItems` property that acts as the single source of truth for all disclosure states.

```typescript
// 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`](https://github.com/eneskirca/nodeterm/blob/main/src/core/settings-store.ts)** – Implements the Zustand store and handles read/write operations to [`settings.json`](https://github.com/eneskirca/nodeterm/blob/main/settings.json) on disk
- **[`src/renderer/state/settings.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/renderer/state/settings.ts)** – Exposes the `useSettings` hook 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`](https://github.com/eneskirca/nodeterm/blob/main/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:

```typescript
// 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`](https://github.com/eneskirca/nodeterm/blob/main/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:

```typescript
// 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`](https://github.com/eneskirca/nodeterm/blob/main/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:

```typescript
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:

```typescript
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:

```typescript
import { pruneSidebarCollapsedItems } from '@/renderer/lib/sessionList'

// Called on each render pass or specific cleanup event
pruneSidebarCollapsedItems()

```

## Summary

- **Nodeterm** uses a centralized `sidebarCollapsedItems` map in the global `Settings` type to track disclosure states
- The **Zustand** store in [`src/core/settings-store.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/core/settings-store.ts) persists this data to [`settings.json`](https://github.com/eneskirca/nodeterm/blob/main/settings.json) automatically on every change
- UI components in [`src/renderer/components/SessionsSidebar.tsx`](https://github.com/eneskirca/nodeterm/blob/main/src/renderer/components/SessionsSidebar.tsx) toggle states using the `useSettings` hook with unique string keys
- The system **prunes stale keys** via [`src/renderer/lib/sessionList.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/renderer/lib/sessionList.ts) to prevent orphaned entries from accumulating
- Boolean logic uses `true` for collapsed and `false` for expanded, with `false` as 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`](https://github.com/eneskirca/nodeterm/blob/main/settings.json) file on disk, managed by the Zustand store implementation in [`src/core/settings-store.ts`](https://github.com/eneskirca/nodeterm/blob/main/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`](https://github.com/eneskirca/nodeterm/blob/main/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`](https://github.com/eneskirca/nodeterm/blob/main/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`](https://github.com/eneskirca/nodeterm/blob/main/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.