[Canonical page](<https://docs.dhtmlx.com/planning-engine/wasm/how-to/preview-leveling/>)
[Full documentation index](<https://docs.dhtmlx.com/planning-engine/llms.txt>)


# Preview leveling

Show the user what leveling would change before it touches the project, then keep the leveled plan or throw it away.

[`level`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-level.md>) commits. It writes `leveling_delay` and [`pauses`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/common.md#pause>) into the project and accepts the leveled dates as the new baseline. To get a proposal instead, level a [`clone()`](<https://docs.dhtmlx.com/planning-engine/wasm/api/json-project.md#clone>). A clone is cheap because the data stays shared until one copy is edited. It's a separate handle in wasm memory, though, so free it like any other.

## Prerequisites

- A solved [`JsonProject`](<https://docs.dhtmlx.com/planning-engine/wasm/api/json-project.md>): [`solve`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-solve.md>) has run with your data.
- The data has [`resources`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/common.md#resourcedata>) and [`assignments`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/assignment-data.md>), so there's overallocation to level.
- The project is forward-scheduled. [`level`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-level.md>) refuses [`config.schedule_from_end: true`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/scheduling-config.md>) with a `validation` error at the path `config.schedule_from_end`.

## Steps

1. [Clone](<https://docs.dhtmlx.com/planning-engine/wasm/api/json-project.md#clone>) the solved project:

   ```js
   const draft = project.clone();
   ```

2. [Level](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-level.md>) the clone. The original keeps its dates, its baseline and its overallocation:

   ```js
   const proposal = unwrap(draft.level("{}"));
   ```

   `unwrap` parses the envelope and throws on `ok: false`. It's defined in the full example below. If the call fails, free `draft` before you rethrow.

3. Show the proposal. [`proposal.updates`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-level.md#levelingresult>) lists the tasks leveling moved, with their new dates, `leveling_delay` and, when they changed, `pauses`. The diff is relative to the state the call started from. That state matches the original, so you can apply the entries to the model you already hold.

   ```js
   for (const update of proposal.updates) {
     console.log(
       `${update.id}: start ${update.start_date}, delay ${update.leveling_delay}, pauses ${JSON.stringify(update.pauses)}`,
     );
   }
   ```

4. Show what leveling couldn't fix. [`proposal.overallocations`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/overallocation.md>) holds the [windows](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/overallocation.md#window-fields>) left after leveling. Each window has a `reason`, such as `fixed-task` or `priority-1000`. Both `overallocations` and `windows` can be absent, so default them:

   ```js
   for (const over of proposal.overallocations ?? []) {
     for (const window of over.windows ?? []) {
       console.log(`${over.resource}: ${window.from} .. ${window.to}, ${window.reason}`);
     }
   }
   ```

5. Apply the user's choice:
   - Accept: [free](<https://docs.dhtmlx.com/planning-engine/wasm/api/json-project.md#free>) the original and keep the clone in its place. The clone holds the leveled baseline.
   - Reject: free `draft`. Nothing else needs undoing.

   ```js
   if (userAccepts) {
     project.free();
     project = draft;
   } else {
     draft.free();
   }
   ```

   Swap only if the original hasn't been edited since you cloned it. Otherwise the proposal was computed from an older state, so level a new clone.

The full example levels two tasks that book the same developer full time on Monday and Tuesday:

```js
const { JsonProject } = require("./pkg-node/plen_wasm.js");

function unwrap(text) {
  const response = JSON.parse(text);
  if (!response.ok) {
    const { kind, code, message, path } = response.error;
    throw new Error(`${kind} ${code ?? ""}: ${message} (at ${path})`);
  }
  return response.result;
}

// Two 2-day tasks, both booked full time on dev1 from Monday, August 10, 2026,
// on the default calendar.
const data = {
  tasks: [
    { id: "A", start_date: "2026-08-10 08:00", duration: 2 },
    { id: "B", start_date: "2026-08-10 08:00", duration: 2 },
  ],
  resources: [{ id: "dev1" }],
  assignments: [
    { task: "A", resource: "dev1", value: 1 },
    { task: "B", resource: "dev1", value: 1 },
  ],
};

let project = new JsonProject("{}");
try {
  unwrap(project.solve(JSON.stringify(data), "{}"));

  const draft = project.clone();
  let proposal;
  try {
    proposal = unwrap(draft.level("{}"));
  } catch (error) {
    draft.free();
    throw error;
  }

  for (const update of proposal.updates) {
    console.log(`${update.id}: ${update.start_date} .. ${update.end_date}, delay ${update.leveling_delay}`);
  }
  const left = (proposal.overallocations ?? []).filter((o) => (o.windows ?? []).length > 0).length;
  console.log(`overallocated resources left: ${left}`);

  const userAccepts = true;
  if (userAccepts) {
    project.free();
    project = draft;
  } else {
    draft.free();
  }

  const plan = unwrap(project.solve(undefined, "{}"));
  console.log(`baseline matches proposal: ${plan.baselineId === proposal.baselineId}`);
} finally {
  project.free();
}
```

## Result

Run the example with `node`. It prints the one task leveling moved, and nothing is left overallocated:

```text
B: 2026-08-12 08:00 .. 2026-08-14 08:00, delay 2
overallocated resources left: 0
baseline matches proposal: true
```

`B` waits two days for `A`, so its `leveling_delay` is `2`. Until you swap, the original still reports the Monday-Tuesday window on `dev1`, and a [`solve`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-solve.md>) without data on it returns no updates. After the swap, `baselineId` is the token from the leveling result.

The clone is a full solved project, so you can also call [`loadProfile`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-load-profile.md>) on it to draw the leveled load. See [Resource load](<https://docs.dhtmlx.com/planning-engine/wasm/resources-and-assignment/resource-load.md>). To compare leveling across several alternative change sets with scores, use [`evaluate`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-evaluate.md>) with the `level` option of [`EvaluateOptions`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-evaluate.md#evaluateoptions>) instead. See [Evaluating alternatives](<https://docs.dhtmlx.com/planning-engine/wasm/resources-and-assignment/evaluating-alternatives.md>). For options, reasons and splits, see [Leveling resources](<https://docs.dhtmlx.com/planning-engine/wasm/resources-and-assignment/leveling-resources.md>).
