# Nodeterm Project File Model (V3): Workspace Index and Project Structure

> Explore Nodeterm's v3 project file model. Understand the workspace index and project structure, separating node data from project references for efficient organization.

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

---

**Nodeterm's v3 project file model splits workspace data into a central index ([`workspace.json`](https://github.com/eneskirca/nodeterm/blob/main/workspace.json)) stored in the user data directory and individual project files ([`.nodeterm/project.json`](https://github.com/eneskirca/nodeterm/blob/main/.nodeterm/project.json)) that contain serialized canvas state, separating project references from node data.**

The `eneskirca/nodeterm` repository uses a sophisticated two-tier storage system in its v3 architecture. This design decouples the workspace index—which tracks project locations—from the actual project content, enabling efficient remote-SSH support and atomic saves. Understanding this structure is essential for developers integrating with nodeterm's storage layer or troubleshooting workspace issues.

## The V3 Workspace Index ([`workspace.json`](https://github.com/eneskirca/nodeterm/blob/main/workspace.json))

The workspace index acts as the central registry for all projects without storing actual node data. According to the source code in [`src/core/workspace-files.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/core/workspace-files.ts), this file splits an in-memory workspace into the v3 index plus local project files for writing.

### Index Location and Format

The index resides at `app.getPath('userData')/workspace.json` and follows this strict schema:

```json
{
  "version": 3,
  "entries": [
    {
      "projectId": "project-1",
      "cwd": "/home/user/my-repo"
    },
    {
      "projectId": "project-2",
      "ssh": {
        "host": "example.com",
        "user": "alice",
        "port": 22
      }
    }
  ]
}

```

Each entry contains only the **project identifier** and connection details. The `version: 3` field indicates the schema revision, as noted in [`src/main/index.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/main/index.ts) at line 564.

### Local vs. Remote References

The v3 index supports two distinct reference types:

- **Local projects**: Use the `cwd` field containing the absolute path to the project directory
- **Remote SSH projects**: Use the `ssh` object with `host`, `user`, and `port` properties

Neither entry type stores canvas data; both reference external [`.nodeterm/project.json`](https://github.com/eneskirca/nodeterm/blob/main/.nodeterm/project.json) files.

## Project File Structure ([`.nodeterm/project.json`](https://github.com/eneskirca/nodeterm/blob/main/.nodeterm/project.json))

Individual project files contain the complete canvas definition. The `Project` interface defined in [`src/shared/types.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/shared/types.ts) (lines 613-682) dictates the schema for these files.

### Core Fields

| Field | Type | Purpose |
|-------|------|---------|
| `id` | `string` | Stable UUID linking the project to its index entry |
| `title` | `string` | Human-readable name displayed in the tab bar |
| `color` | `string` | Hex color code for UI accents |
| `nodes` | `NodeState[]` | Serialized React Flow nodes (terminals, stickers, groups) |
| `viewport` | `Viewport` | Camera position and zoom level |

### Optional Configuration

Project files may include additional metadata fields:

- **`icon`**: Custom icon definition (see [`src/shared/project-icon.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/shared/project-icon.ts))
- **`collapsed`**: Boolean indicating sidebar tab state
- **`kanban`**: Kanban board configuration with columns and labels
- **`defaultPermissionMode`**: Agent permission handling settings
- **`defaultAccountId`**: Default Claude account for agent nodes
- **`defaultProjectView`**: Enum value `'canvas'` or `'kanban'` for default view
- **`settings`**: Additional per-project settings (see [`project-settings.ts`](https://github.com/eneskirca/nodeterm/blob/main/project-settings.ts))

## Data Flow: Loading, Saving, and Migration

The `WorkspaceStore` class in [`src/core/workspace-store.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/core/workspace-store.ts) manages all v3 file operations, including the v2 to v3 migration logic at line 98.

### Loading the Workspace

`WorkspaceStore.load()` executes a two-phase loading process:

1. Reads [`workspace.json`](https://github.com/eneskirca/nodeterm/blob/main/workspace.json) into memory
2. Iterates through `entries` and loads each [`.nodeterm/project.json`](https://github.com/eneskirca/nodeterm/blob/main/.nodeterm/project.json) file referenced by `projectId`

This separation allows nodeterm to load individual projects on demand rather than parsing a monolithic file.

### Persisting Changes

The v3 model uses differential saving to minimize disk I/O:

- **`WorkspaceStore.writeProject()`**: Updates only the specific [`.nodeterm/project.json`](https://github.com/eneskirca/nodeterm/blob/main/.nodeterm/project.json) file when canvas data changes
- **`WorkspaceStore.saveIndex()`**: Rewrites [`workspace.json`](https://github.com/eneskirca/nodeterm/blob/main/workspace.json) only when the project list changes (add, remove, or reorder)

### V2 to V3 Migration

On first run after upgrading, nodeterm executes an automatic migration:

1. Assembles a v2 file from the legacy layout
2. Writes the new v3 index to [`workspace.json`](https://github.com/eneskirca/nodeterm/blob/main/workspace.json)
3. Creates individual project files in their respective directories
4. Backs up the original file with a `*.bak` extension

The test suite in [`src/core/workspace-store.test.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/core/workspace-store.test.ts) validates this round-trip conversion and migration flow.

## Programmatic Usage Examples

Interact with the v3 model using the `WorkspaceStore` API:

```typescript
import { WorkspaceStore } from './core/workspace-store';

// Load the complete workspace (index + all projects)
const ws = await WorkspaceStore.load();

// Retrieve a specific project
const project = await WorkspaceStore.readProject('project-1');

// Modify canvas data
project.nodes.push({ 
  id: 'term-5', 
  kind: 'terminal', 
  data: { /* terminal config */ } 
});

// Persist changes to the project file only
await WorkspaceStore.writeProject(project);

```

Inspect the index directly for debugging:

```typescript
// Read workspace.json without loading projects
const index = await WorkspaceStore.readIndex();
console.log(index.version);  // → 3
console.log(index.entries);  // Array of project references

```

Create new projects programmatically:

```typescript
const newProject = {
  id: 'proj-new',
  title: 'My New Canvas',
  color: '#4A90E2',
  nodes: [],
  viewport: { x: 0, y: 0, zoom: 1 }
};

// Write the project file
await WorkspaceStore.writeProject(newProject);

// Register in the index
await WorkspaceStore.addIndexEntry({ 
  projectId: newProject.id, 
  cwd: '/home/me/new-proj' 
});

```

## Key Source Files

- **[`src/shared/types.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/shared/types.ts)** (lines 613-682): Defines the `Project` interface and field types
- **[`src/core/workspace-files.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/core/workspace-files.ts)**: Contains the split logic separating index from project data
- **[`src/core/workspace-store.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/core/workspace-store.ts)** (line 98): Implements loading, saving, and v2→v3 migration
- **[`src/core/workspace-store.test.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/core/workspace-store.test.ts)**: Unit tests for the v3 save/load round-trip
- **[`src/main/index.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/main/index.ts)** (line 564): Documents the v3 index format

## Summary

- The v3 model uses a split architecture: [`workspace.json`](https://github.com/eneskirca/nodeterm/blob/main/workspace.json) stores references while [`.nodeterm/project.json`](https://github.com/eneskirca/nodeterm/blob/main/.nodeterm/project.json) files store canvas data
- The workspace index supports both local (`cwd`) and remote (`ssh`) project references via the `entries` array
- Project files contain React Flow nodes, viewport states, and optional kanban configurations according to the `Project` interface
- `WorkspaceStore` provides atomic operations for reading individual projects without loading the entire workspace
- Automatic migration converts v2 workspaces to v3 format, backing up legacy files before transformation

## Frequently Asked Questions

### Where is the nodeterm workspace index stored?

The v3 workspace index is stored at `app.getPath('userData')/workspace.json`, which resolves to the operating system's user data directory followed by the nodeterm application folder. This location ensures the index persists across application restarts while remaining separate from project-specific data.

### What is the difference between the workspace index and project files in v3?

The workspace index ([`workspace.json`](https://github.com/eneskirca/nodeterm/blob/main/workspace.json)) contains only lightweight metadata: the version number (3) and an array of entries mapping `projectId` values to either local directories (`cwd`) or SSH connections. Project files ([`.nodeterm/project.json`](https://github.com/eneskirca/nodeterm/blob/main/.nodeterm/project.json)) contain the heavy data: React Flow node states, viewport coordinates, kanban boards, and project settings. This separation enables nodeterm to load project lists instantly while deferring heavy canvas data loading until needed.

### How does nodeterm handle remote SSH projects in the v3 model?

Remote projects use the `ssh` field in the index entry instead of `cwd`, specifying `host`, `user`, and `port`. The project file itself resides on the remote filesystem at [`.nodeterm/project.json`](https://github.com/eneskirca/nodeterm/blob/main/.nodeterm/project.json) relative to the SSH user's home directory or specified path. The v3 index tracks the connection parameters, while the remote filesystem stores the canvas data, allowing seamless switching between local and remote workspaces.

### What happens during the v2 to v3 migration?

When nodetect detects a legacy v2 workspace, it automatically migrates data by parsing the old monolithic file, extracting individual projects, and writing them to separate [`.nodeterm/project.json`](https://github.com/eneskirca/nodeterm/blob/main/.nodeterm/project.json) files in their respective directories. It then creates the new v3 [`workspace.json`](https://github.com/eneskirca/nodeterm/blob/main/workspace.json) index and backs up the original file with a `.bak` extension. This process is idempotent and preserves all project data while optimizing the storage structure for the new architecture.