# How Project Order is Managed in Nodeterm: Zustand Array-Based State

> Discover how Nodeterm manages project order using a Zustand array. Learn how the reorderProject method manipulates the visual tab sequence for seamless workflow management.

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

---

**Project order in Nodeterm is managed through a Zustand store that maintains a `projects` array, where the visual tab sequence directly mirrors the array index order, manipulated via the `reorderProject()` method.**

The open-source terminal emulator **nodeterm** uses a deterministic, array-based approach to handle project sequencing. According to the eneskirca/nodeterm source code, the application stores all open projects in a centralized Zustand store, ensuring that the **tab bar**, **sidebar**, and **canvas** all render projects in the exact same sequence.

## The Zustand Store Architecture

Nodeterm implements its project state management in [`src/renderer/state/projects.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/renderer/state/projects.ts). This file defines a `ProjectsState` store containing a `projects: Project[]` array that serves as the single source of truth for project ordering.

The visual layout of the UI layers—whether the horizontal tab bar or vertical sidebar—directly reflects the indices of this array. Index `0` represents the leftmost tab, while the final index represents the rightmost position. Because all components read from the same Zustand store, any modification to the array order instantly propagates across the entire interface.

## How the reorderProject Method Works

The core logic for reordering resides in the `reorderProject(draggedId, beforeId)` method. This function accepts two string parameters: the ID of the project being moved and the ID of the project it should precede (or `null` to append to the end).

The implementation follows a three-step algorithm:

1. **Filter** the dragged project from the array to create a `without` array.
2. **Locate** the insertion index by finding the position of `beforeId` in the filtered array. If `beforeId` is `null` or not found, the index defaults to `-1`.
3. **Splice** the dragged project into the computed position, appending it to the end if the index is `-1`.

```typescript
// src/renderer/state/projects.ts (lines 49-59)
reorderProject(draggedId, beforeId) {
  set((s) => {
    if (draggedId === beforeId) return s
    const dragged = s.projects.find((p) => p.id === draggedId)
    if (!dragged) return s
    const without = s.projects.filter((p) => p.id !== draggedId)
    const idx = beforeId ? without.findIndex((p) => p.id === beforeId) : -1
    // Unknown/null target → append (the "drop at the end" zone).
    const at = idx === -1 ? without.length : idx
    return { projects: [...without.slice(0, at), dragged, ...without.slice(at)] }
  })
}

```

This immutable update pattern ensures React re-renders trigger correctly while preserving reference stability for the unchanged project objects.

## Adding, Closing, and Deleting Projects

Project lifecycle operations affect the array differently depending on the intended persistence behavior.

### Adding New Projects

When users create a project via `addProject(name, cwd, ssh)`, the method pushes the new `Project` object to the **end** of the `projects` array. This automatically positions the new tab at the far right of the tab bar.

```typescript
import { useProjects } from '@/renderer/state/projects'

// Adds "My New Project" to the rightmost position
const newProj = useProjects.getState().addProject('My New Project', '/path/to/folder')

```

### Closing vs. Deleting Projects

**Closing** a project sets a `closed: true` flag on the project object but **retains its position** in the array. This preserves the slot for later reopening without disturbing the order of remaining active tabs.

**Deleting** a project mutates the array by filtering out the project entirely. When deletion occurs, the next active project is selected based on the nearest neighbor in the current array order.

## Key Implementation Files

The project ordering system spans three primary locations in the codebase:

- **[`src/renderer/state/projects.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/renderer/state/projects.ts)** – Contains the `ProjectsState` Zustand store, the `projects` array definition, and the `reorderProject` implementation.
- **[`src/renderer/state/workspace.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/renderer/state/workspace.ts)** – Provides utilities such as `createProject` that generate new `Project` objects before they enter the store.
- **[`src/shared/project-id.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/shared/project-id.ts)** – Supplies deterministic ID generation functions (`derivedProjectId`, `collisionSeed`) used during project creation and collision resolution.

## Practical Code Examples

The following patterns demonstrate common reordering operations against the live store:

```typescript
import { useProjects } from '@/renderer/state/projects'

// Move "proj-2" immediately before "proj-1"
useProjects.getState().reorderProject('proj-2', 'proj-1')

// Move "proj-3" to the end of the tab bar (null target)
useProjects.getState().reorderProject('proj-3', null)

```

These calls trigger the immutable splicing logic and immediately update the UI without requiring explicit refresh calls.

## Summary

- **Array-based sequencing**: The `projects` array in [`src/renderer/state/projects.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/renderer/state/projects.ts) determines tab order by index.
- **Centralized state**: Zustand ensures the tab bar, sidebar, and canvas remain synchronized.
- **Immutable reordering**: The `reorderProject(draggedId, beforeId)` method filters, calculates insertion points, and reconstructs the array without mutating the original.
- **Append-only addition**: New projects added via `addProject()` always initialize at the array's end (rightmost tab).
- **Soft close mechanic**: Closing preserves array position via a boolean flag; deleting removes the element and triggers nearest-neighbor selection.

## Frequently Asked Questions

### How does Nodeterm store the order of open projects?

Nodeterm maintains the order in a Zustand store defined in [`src/renderer/state/projects.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/renderer/state/projects.ts). The store contains a `projects` array where each element's index corresponds to its visual position in the tab bar and sidebar.

### What happens when I close a project in Nodeterm?

Closing a project toggles a `closed: true` property on the project object but leaves it in the `projects` array. This preserves the project's slot and ordering information, allowing you to reopen it later without affecting the sequence of other tabs.

### How do I move a project to the end of the tab bar programmatically?

Call `reorderProject(draggedId, null)` on the store. The `null` value for the second parameter signals the method to calculate the insertion index as the array length, effectively appending the project to the final position.

### Where is the project state defined in the codebase?

The primary state definition lives in [`src/renderer/state/projects.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/renderer/state/projects.ts). This file exports the `useProjects` hook and contains the `ProjectsState` interface, the `projects` array, and all mutation methods including `reorderProject` and `addProject`.