JsonProject.preview#

Applies a change set to a copy of the solved baseline of a JsonProject and schedules the copy. The project isn't modified. Use it to show what an edit would do before committing it with apply. Needs a solved project with no edits applied since the last solve or level.

Signature#

preview(changes_json: string, options_json: string): string

Synchronous and read-only. Bad input comes back as an error envelope. The call doesn't throw.

Request#

Argument Payload Required Description
changes_json SchedulingChanges yes The edits to try. "{}" previews no change
options_json PreviewOptions yes "{}" for defaults

PreviewOptions#

The options object for preview. Every field can be omitted. Unknown fields, such as includeUnchanged, are rejected with a parse error.

Field Type Required Default Description
subtree Subtree no absent (whole project) Previews only one branch. Same shape as subtree in SolveOptions
analytics AnalyticsOptions no none requested Adds analytics for the previewed state. Same shape as analytics in SolveOptions
reasons boolean no false Adds a reason to each update
baselineId string no absent (not checked) Must name the current baseline, or the call fails with stale-baseline
profile ProfileQuery no absent Adds profile, the resource load of the previewed dates. Takes the fields of LoadProfileOptions except baselineId, which is an unknown field here

null on any field reads as its default.

Behavior#

  • updates are relative to the baseline. An empty change set gives empty updates.
  • The result's baselineId is the baseline the preview read, so a following preview accepts it.
  • The change set isn't partially accepted. One rejected operation fails the whole call.
  • options.baselineId and changes.baselineId are checked separately. An absent token skips its check.
  • The result stays sparse: there's no option to report unchanged tasks.

Response#

The success envelope carries a ScheduleResult for the previewed state. profile is present only when options.profile is set. Envelope shape: Wire format.

Errors#

Kind Code When
project no-baseline The project has never been solved, or apply ran since the last solve
project stale-baseline options.baselineId or changes.baselineId doesn't name the current baseline
validation null An operation in changes_json is rejected (the error reports the first one, with its path, e.g. tasks.update[0]), or the profile query is invalid (e.g. to not after from, at to)
parse null Malformed JSON, or an unknown field in the change set or the options
Note

A start date that falls on non-working time is kept unsnapped when the task has no predecessor. The end is computed forward from it on the working calendar.

Example#

Preview a resize of task a on a solved project, with reasons, guarded by the baseline token.

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

const data = {
  config: { use_calendars: false },
  tasks: [
    { id: "a", start_date: "2026-01-01", duration: 2 },
    { id: "b", start_date: "2026-01-01", duration: 1 },
  ],
  links: [{ id: "ab", source: "a", target: "b" }],
};

const project = new JsonProject("{}");
try {
  const solved = JSON.parse(project.solve(JSON.stringify(data), "{}")).result;

  const changes = { tasks: { update: [{ id: "a", duration: 3 }] } };
  const options = { baselineId: solved.baselineId, reasons: true };
  const response = JSON.parse(
    project.preview(JSON.stringify(changes), JSON.stringify(options)),
  );

  if (response.ok) {
    for (const update of response.result.updates) {
      console.log(`${update.id}: ${update.start_date} .. ${update.end_date}`);
    }
    // a: 2026-01-01 00:00 .. 2026-01-04 00:00
    // b: 2026-01-04 00:00 .. 2026-01-05 00:00
  } else {
    const { kind, code, message, path } = response.error;
    console.error(kind, code, message, path);
  }
} finally {
  project.free();
}

changes_json:

{ "tasks": { "update": [{ "id": "a", "duration": 3 }] } }

options_json (the token comes from the solve result):

{ "baselineId": "1-b0605af8cba3d7c9", "reasons": true }

Response:

{
  "ok": true,
  "result": {
    "baselineId": "1-b0605af8cba3d7c9",
    "conflicts": [],
    "converged": true,
    "cycles": [],
    "notes": [],
    "updates": [
      {
        "duration": 3,
        "end_date": "2026-01-04 00:00",
        "id": "a",
        "kind": "task",
        "reason": { "kind": "preferred" },
        "start_date": "2026-01-01 00:00"
      },
      {
        "end_date": "2026-01-05 00:00",
        "id": "b",
        "kind": "task",
        "reason": { "kind": "asap", "linkId": "ab", "sourceId": "a" },
        "start_date": "2026-01-04 00:00"
      }
    ]
  }
}

The project keeps its solved dates: project.preview("{}", "{}") still returns empty updates.