---
name: editing-preview-workflow
description: >
  Load when implementing management-screen editing and preview updates with
  walkBody, findBlockPathById, TraversalPath, getBlockAtPath,
  insertBlockAtRoot, insertBlockAtPath, updateBlockAtPath,
  replaceBlockAtPath, removeBlockAtPath, moveBlock, updateRichTextAtPath,
  mapBody, or BodyEditError.
type: lifecycle
library: "@ryhrm-gz/xincodo-lib"
library_version: "0.1.0"
requires:
  - building-and-parsing-body
  - validating-body
sources:
  - "ryhrm-gz/xincodo-lib:README.md"
  - "ryhrm-gz/xincodo-lib:src/editing.ts"
  - "ryhrm-gz/xincodo-lib:src/traversal.ts"
  - "ryhrm-gz/xincodo-lib:tests/editing.test.ts"
  - "ryhrm-gz/xincodo-lib:tests/traversal.test.ts"
---

# Editing Preview Workflow

This skill builds on `building-and-parsing-body` and `validating-body`. Use
stable ids to locate blocks, resolve a fresh `TraversalPath` immediately
before editing, and use immutable helpers to produce the next `Body`.

## Setup

```ts
import { findBlockPathById, insertBlockAtPath, paragraph, type Body } from "@ryhrm-gz/xincodo-lib";

export function insertNoteAfterIntro(body: Body): Body {
  const introPath = findBlockPathById(body, "intro");

  if (!introPath) {
    return body;
  }

  return insertBlockAtPath(body, introPath, paragraph("Preview note"), {
    position: "after",
  });
}
```

## Core Patterns

### Resolve a fresh path from a stable id

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

const path = findBlockPathById(body, "summary");
const block = path ? getBlockAtPath(body, path) : undefined;
```

Paths are position-based. Recompute them from stable ids after edits that may
move or insert blocks.

### Replace or update a block immutably

```ts
import { heading, replaceBlockAtPath, type Body, type TraversalPath } from "@ryhrm-gz/xincodo-lib";

export function promoteIntro(body: Body, path: TraversalPath) {
  return replaceBlockAtPath(body, path, heading(2, "Introduction", { id: "intro" }));
}
```

The returned `Body` is the value to store in editor state.

### Update rich text from traversal context

```ts
import {
  richText,
  updateRichTextAtPath,
  walkBody,
  type Body,
  type TraversalPath,
} from "@ryhrm-gz/xincodo-lib";

export function renameIntro(body: Body): Body {
  let richTextPath: TraversalPath | undefined;

  walkBody(body, {
    richText(_, context) {
      if (
        "type" in context.parent &&
        context.parent.type === "text" &&
        context.parent.id === "intro"
      ) {
        richTextPath = context.path;
      }
    },
  });

  return richTextPath
    ? updateRichTextAtPath(body, richTextPath, () => richText("Updated intro"))
    : body;
}
```

`updateRichTextAtPath` needs a path ending at `richText`, `caption`, or
`title`, not just the block path.

### Move blocks with descendant safety

```ts
import { findBlockPathById, moveBlock, type Body } from "@ryhrm-gz/xincodo-lib";

export function moveIntroAfterSummary(body: Body): Body {
  const introPath = findBlockPathById(body, "intro");
  const summaryPath = findBlockPathById(body, "summary");

  if (!introPath || !summaryPath) {
    return body;
  }

  return moveBlock(body, introPath, summaryPath, { position: "after" });
}
```

`moveBlock` throws `BodyEditError` if the target is inside the moved block.

## Common Mistakes

### HIGH Mutating nested content directly

Wrong:

```ts
import { paragraph, type Body } from "@ryhrm-gz/xincodo-lib";

function addNote(body: Body): Body {
  const first = body.content[0];
  if (first?.type === "text") {
    first.children?.push(paragraph("Inserted"));
  }
  return body;
}
```

Correct:

```ts
import { findBlockPathById, insertBlockAtPath, paragraph, type Body } from "@ryhrm-gz/xincodo-lib";

function addNote(body: Body): Body {
  const path = findBlockPathById(body, "intro");
  return path ? insertBlockAtPath(body, path, paragraph("Inserted")) : body;
}
```

Editing helpers return new `Body` values so preview state can update
predictably.

Source: `README.md`; `tests/editing.test.ts`; `tests/traversal.test.ts`

### HIGH Passing a block path to rich text updater

Wrong:

```ts
import {
  findBlockPathById,
  richText,
  updateRichTextAtPath,
  type Body,
} from "@ryhrm-gz/xincodo-lib";

function updateIntro(body: Body): Body {
  const path = findBlockPathById(body, "intro");
  return path ? updateRichTextAtPath(body, path, () => richText("Updated")) : body;
}
```

Correct:

```ts
import {
  richText,
  updateRichTextAtPath,
  walkBody,
  type Body,
  type TraversalPath,
} from "@ryhrm-gz/xincodo-lib";

function updateIntro(body: Body): Body {
  let richTextPath: TraversalPath | undefined;

  walkBody(body, {
    richText(_, context) {
      if (
        "type" in context.parent &&
        context.parent.type === "text" &&
        context.parent.id === "intro"
      ) {
        richTextPath = context.path;
      }
    },
  });

  return richTextPath ? updateRichTextAtPath(body, richTextPath, () => richText("Updated")) : body;
}
```

Rich text updates require a field path, usually captured from `walkBody`.

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

### MEDIUM Moving a block into its descendant

Wrong:

```ts
import { findBlockPathById, moveBlock, type Body } from "@ryhrm-gz/xincodo-lib";

function moveParentIntoChild(body: Body): Body {
  return moveBlock(body, findBlockPathById(body, "parent")!, findBlockPathById(body, "child")!);
}
```

Correct:

```ts
import { findBlockPathById, moveBlock, type Body } from "@ryhrm-gz/xincodo-lib";

function moveParentAfterSibling(body: Body): Body {
  const parentPath = findBlockPathById(body, "parent");
  const siblingPath = findBlockPathById(body, "sibling");

  return parentPath && siblingPath
    ? moveBlock(body, parentPath, siblingPath, { position: "after" })
    : body;
}
```

`moveBlock` prevents moving a block relative to its own descendant because
that would create an invalid nesting operation.

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

## References

- [Traversal paths](references/traversal-paths.md)
- [Rich text field paths](references/rich-text-field-paths.md)

See also: `validating-body/SKILL.md` — edited preview state should be linted
before save or publish.
