# Editing, Validation, Diagnostics
URL: https://pro.rv-grid.com/guides/gantt/features/editing-validation-diagnostics/
Source: src/content/docs/guides/gantt/features/editing-validation-diagnostics.mdx
Description: Wire Gantt permission checks, editor forms, popovers, validation, and diagnostics panels.
These APIs are presentation-free. They make decisions and shape data, while your application owns forms, popovers, panels, toasts, and persistence.

```ts
import {
  GANTT_BEFORE_ASSIGNMENT_CHANGE_EVENT,
  GANTT_BEFORE_DEPENDENCY_CHANGE_EVENT,
  GANTT_BEFORE_TASK_CHANGE_EVENT,
  TASK_EDITOR_FIELD_SCHEMA,
  buildDependencyValidationSummary,
  buildResourceOverallocationSummary,
  createApprovalGateValidator,
  createForbiddenDateRangeValidator,
  createGanttBeforeTaskChangeValidationHandler,
  createGanttRoleEditabilityHelpers,
  createLockedPhaseValidator,
  createRoleGuardValidator,
  createTaskDetailPopoverModel,
  createTaskEditorFormValues,
  normalizeTaskEditorSubmit,
} from '@revolist/revogrid-enterprise';
```

## Role-Based Editability

Before-change events are cancelable. Use role helpers to decide whether a task, dependency, or assignment mutation should proceed. Call `event.preventDefault()` to stop the packaged mutation before the Gantt store changes; timeline previews are cleared and the next render keeps the previous task, dependency, or assignment data.

```ts
const policy = {
  roles: {
    viewer: [],
    planner: ['task:*', 'dependency:create', 'dependency:delete'],
    staffing: ['assignment:edit'],
    admin: ['*'],
  },
  fallback: 'deny',
} as const;

const editability = createGanttRoleEditabilityHelpers(policy);
const getRoleContext = () => ({ roles: currentUser.roles });

grid.addEventListener(GANTT_BEFORE_TASK_CHANGE_EVENT, (event) => {
  if (!editability.canEditTask(event.detail, getRoleContext())) {
    event.preventDefault();
  }
});

grid.addEventListener(GANTT_BEFORE_DEPENDENCY_CHANGE_EVENT, (event) => {
  if (!editability.canEditDependency(event.detail, getRoleContext())) {
    event.preventDefault();
  }
});

grid.addEventListener(GANTT_BEFORE_ASSIGNMENT_CHANGE_EVENT, (event) => {
  if (!editability.canEditAssignment(event.detail, getRoleContext())) {
    event.preventDefault();
  }
});
```

Task before-change actions cover `move`, `resize`, `create`, `edit`, `progress`, `split`, `indent`, `outdent`, and `delete`. Dependency before-change actions cover `create`, `delete`, `edit`, and `replace`; `replace` is emitted by predecessor/successor inline field edits and includes `previousDependencies` plus proposed `dependencies` because one cell edit can add, update, and remove multiple links. Assignment edits emit proposed `assignments` and `previousAssignments`.

For task edits, `event.detail.sourcePatch` contains the flat source-row fields that the packaged Gantt store will write. Use it when persisting custom row fields or when your backend uses public source names such as `duration`, `percentDone`, and `taskMode`.

```ts
grid.addEventListener(GANTT_BEFORE_TASK_CHANGE_EVENT, (event) => {
  const { sourceIndex, sourcePatch, previousSourceValues } = event.detail;

  if (sourceIndex === undefined || !sourcePatch) {
    return;
  }

  queueTaskRowPersistence({
    rowIndex: sourceIndex,
    patch: sourcePatch,
    previousValues: previousSourceValues ?? {},
  });
});
```

## Custom Task Editor Forms

Use the field schema and initial value builder to render a framework form, then normalize submitted values into a `TaskUpdate` patch.

For a complete standalone UI example, see [Task Editing](../../interaction/task-editing/).

```ts
const fields = TASK_EDITOR_FIELD_SCHEMA.filter((field) =>
  ['name', 'startDate', 'endDate', 'progressPercent', 'resourceLabels'].includes(field.id),
);

const values = createTaskEditorFormValues(task, resources, assignments);

renderTaskEditor({
  fields,
  values,
  onSubmit(nextValues) {
    const result = normalizeTaskEditorSubmit(task, nextValues);

    if (!result.ok) {
      renderErrors(result.errors);
      return;
    }

    updateTask(task.id, result.patch);
  },
});
```

## Task Detail Popovers

`createTaskDetailPopoverModel()` returns sections, fields, warnings, and resource labels. Your app handles hover/focus, positioning, and rendering.

```ts
const model = createTaskDetailPopoverModel({
  row,
  task,
  resources,
  assignments,
});

renderPopover({
  title: model.title,
  subtitle: model.subtitle,
  sections: model.sections,
  resources: model.resourceAssignments.map((assignment) => assignment.displayValue),
});
```

## Dependency Diagnostics

Use dependency summaries to power badges, side panels, or task focus commands.

```ts
const summary = buildDependencyValidationSummary({
  conflicts: schedulerResult.conflicts,
  issues: schedulerResult.issues,
  rows: projectedRows,
});

renderDiagnosticsPanel({
  count: summary.totalCount,
  groups: summary.groups,
});
```

## Resource Over-Allocation Diagnostics

Use resource summaries when staffing dashboards need to show who is overloaded and when.

```ts
const overloads = buildResourceOverallocationSummary({
  rows: resourcePlanningRows,
  loadSummaries: resourceLoadSummaries,
  conflicts: schedulerResult.conflicts,
});

renderResourceHealth({
  count: overloads.totalCount,
  peakOverloadUnits: overloads.peakOverloadUnits,
  groups: overloads.groups,
});
```

## Custom Validation Recipes

Compose validators and attach the generated handler to `gantt-before-task-change`.

```ts
const validators = [
  createRoleGuardValidator({ policy }),
  createForbiddenDateRangeValidator({
    ranges: [{ startDate: '2026-05-20', endDate: '2026-05-27', label: 'release freeze' }],
  }),
  createLockedPhaseValidator({
    lockedPhaseIds: ['closed'],
    getPhaseId: (detail) => detail.taskId ? phaseByTaskId[detail.taskId] : null,
  }),
  createApprovalGateValidator({
    actions: ['move', 'resize', 'progress'],
    requiresApproval: (detail) => detail.changes.progressPercent === 100,
  }),
];

grid.addEventListener(
  GANTT_BEFORE_TASK_CHANGE_EVENT,
  createGanttBeforeTaskChangeValidationHandler({
    context: () => validationContext,
    validators,
    onReject: ({ decision }) => showValidationToast(decision.message ?? 'Change rejected'),
  }),
);
```
