---
name: validating-body
description: >
  Load when validating Xincodo Body data before save or publish with
  safeMigrateBody and lintBody; handling BodyLintReport, errors, warnings,
  infos, BodyLintOptions, URL validation, image alt checks, duplicate ids,
  heading jumps, table consistency, empty content, or maxDepth.
type: core
library: "@ryhrm-gz/xincodo-lib"
library_version: "0.1.0"
requires:
  - building-and-parsing-body
sources:
  - "ryhrm-gz/xincodo-lib:README.md"
  - "ryhrm-gz/xincodo-lib:src/lint.ts"
  - "ryhrm-gz/xincodo-lib:src/migration.ts"
  - "ryhrm-gz/xincodo-lib:tests/lint.test.ts"
  - "ryhrm-gz/xincodo-lib:tests/migration.test.ts"
---

# Validating Body

This skill builds on `building-and-parsing-body`. Read it first for Body
construction and persisted JSON boundaries.

## Setup

```ts
import { lintBody, safeMigrateBody } from "@ryhrm-gz/xincodo-lib";

export function validateBodyForSave(value: unknown) {
  const migrated = safeMigrateBody(value);

  if (!migrated.success) {
    return { ok: false as const, kind: "migration", issue: migrated.issue };
  }

  const report = lintBody(migrated.output);

  if (!report.valid) {
    return { ok: false as const, kind: "lint", report };
  }

  return { ok: true as const, body: migrated.output, warnings: report.warnings };
}
```

The default blocking policy is errors only. Warnings and infos are surfaced
for the consuming application to decide.

## Core Patterns

### Gate saved input with migration then lint

```ts
import { lintBody, safeMigrateBody } from "@ryhrm-gz/xincodo-lib";

const migrated = safeMigrateBody(savedArticle.body);
if (!migrated.success) {
  throw new Error(migrated.issue.message);
}

const lint = lintBody(migrated.output);
if (!lint.valid) {
  throw new Error(lint.errors.map((issue) => issue.message).join("\n"));
}
```

`safeMigrateBody` verifies the persisted value and version. `lintBody` checks
quality and render-risk issues after a valid current Body exists.

### Surface warnings without blocking by default

```ts
import { lintBody } from "@ryhrm-gz/xincodo-lib";

const report = lintBody(body);

if (!report.valid) {
  throw new Error("Body has blocking errors.");
}

for (const warning of report.warnings) {
  console.warn(warning.code, warning.message);
}
```

`report.valid` is false only when `errors.length > 0`.

### Configure checks for the product context

```ts
import { lintBody } from "@ryhrm-gz/xincodo-lib";

const report = lintBody(body, {
  maxDepth: 3,
  requireImageAlt: true,
  validateUrls: true,
});

if (!report.valid) {
  throw new Error("Body cannot be saved.");
}
```

Use options deliberately. Disabling URL or image alt checks is an application
policy decision, not the safe default.

## Common Mistakes

### CRITICAL Using migration as the only quality gate

Wrong:

```ts
import { safeMigrateBody } from "@ryhrm-gz/xincodo-lib";

const result = safeMigrateBody(rawBody);
if (result.success) {
  await save(result.output);
}
```

Correct:

```ts
import { lintBody, safeMigrateBody } from "@ryhrm-gz/xincodo-lib";

const result = safeMigrateBody(rawBody);
if (!result.success) {
  throw new Error(result.issue.message);
}

const report = lintBody(result.output);
if (!report.valid) {
  throw new Error("Body has blocking lint errors.");
}

await save(result.output);
```

Migration and schema validation accept structurally valid but editorially
risky content; `lintBody` reports issues such as duplicate ids, empty tables,
invalid URLs, missing alt text, and heading jumps.

Source: maintainer interview; `README.md`; `tests/migration.test.ts`;
`tests/lint.test.ts`

### MEDIUM Treating all lint issues as blocking

Wrong:

```ts
import { lintBody } from "@ryhrm-gz/xincodo-lib";

const report = lintBody(body);

if (report.issues.length > 0) {
  throw new Error("Cannot save body.");
}
```

Correct:

```ts
import { lintBody } from "@ryhrm-gz/xincodo-lib";

const report = lintBody(body);

if (!report.valid) {
  throw new Error("Cannot save body.");
}

const nonBlockingWarnings = report.warnings;
```

`lintBody` separates errors, warnings, and infos. By default, only errors
block save or publish; warning policy belongs to the consuming application.

Source: maintainer interview; `src/lint.ts`; `tests/lint.test.ts`

### HIGH Using local paths for external images

Wrong:

```ts
import { createBody, image, lintBody } from "@ryhrm-gz/xincodo-lib";

const body = createBody([image({ type: "external", url: "/uploads/cover.jpg" })]);

const report = lintBody(body);
```

Correct:

```ts
import { createBody, image, lintBody } from "@ryhrm-gz/xincodo-lib";

const body = createBody([image({ type: "file", url: "/uploads/cover.jpg" }, { alt: "Cover" })]);

const report = lintBody(body);
```

With URL validation enabled, `external` image sources must be absolute
`http` or `https` URLs; local upload paths belong to `file` image sources.

Source: `src/lint.ts`; `tests/lint.test.ts`

## References

- [Lint codes and severities](references/lint-codes-and-severities.md)

See also: `save-before-publish-checks/SKILL.md` — pre-save workflows combine
migration results with lint severity handling.
