# Blog Content Edit Workflow

**Last Updated:** 2026-04-04

Canonical workflow for editing blog post content. **Never edit `content.html` directly in JSON files** – use the scripts below to avoid JSON escaping, Unicode corruption, and sync issues.

**Speed / which command to run:** See [BLOG_WORKFLOW_EFFICIENCY.md](BLOG_WORKFLOW_EFFICIENCY.md) (`make blog-apply-validate`, FAQ-only `make blog-post-validate-faq`, pre-publish strict).

## Rule: No Direct JSON Editing

**Never use `search_replace` or direct text editing on `v2/data/blog/posts/**/*.json`.**

Direct edits fail because of:

- JSON escaping (`\/` vs `/`)
- Unicode (German quotes `„` vs `"`, umlauts)
- Newline handling in embedded HTML strings

## Canonical working file (single source in repo)

- **Tracked file name:** `content-draft.html` under `docs/content/blog/posts/{category}/{slug}/` (or rarely `body.html` if a post already standardized on that name—pick **one** per slug).
- **Iterations:** Use **Git** (commits, branches, `git diff`), not additional filenames such as `content-expanded.html`, `content-draft-expanded.html`, `*-comprehensive.html`, or `*-improved.html` in the repo.
- **Scratch / agents / paste sessions:** Use `update-post-content.php --stdin`, or a **local** path such as `/tmp/{slug}-body.html`. Copy into `content-draft.html` only when the change is ready to commit.
- **Legacy duplicates:** See [CONTENT_DRAFT_LEGACY_INVENTORY.md](CONTENT_DRAFT_LEGACY_INVENTORY.md) for known non-canonical files to reconcile.

**One-command apply + validate:** From repo root (after editing your HTML file):

```bash
make blog-apply-validate POST=slug CAT=lexikon HTML=docs/content/blog/posts/lexikon/slug/content-draft.html
make blog-apply-validate-strict POST=slug CAT=lexikon HTML=docs/content/blog/posts/lexikon/slug/content-draft.html
# Equivalent shell:
./v2/scripts/blog/apply-and-validate-post.sh --post=slug --category=lexikon --html=docs/content/blog/posts/lexikon/slug/content-draft.html
./v2/scripts/blog/apply-and-validate-post.sh --post=slug --category=lexikon --html=... --strict
```

## Blog vs template content (same ideas, different scripts)

| Aspect | Blog posts | Template pages (SEO blocks) |
|--------|------------|-----------------------------|
| **Working source** | `content-draft.html` (HTML) | `docs/systems/templates/template-data/{id}/content/content.md` (blocks + HTML bodies) |
| **Apply / sync** | `update-post-content.php` | `sync-template-content-blocks.php` |
| **Runtime JSON** | `v2/data/blog/posts/{cat}/{slug}.json` | `content-blocks.json` per template |
| **Do not edit** | Post JSON `content.html` by hand | `content-blocks.json` by hand |
| **Docs** | This file | [TEMPLATE_CONTENT_EDIT_WORKFLOW.md](../systems/templates/TEMPLATE_CONTENT_EDIT_WORKFLOW.md) |

## Content creation method

**Prefer outline-first comprehensive drafting.** See [CONTENT_CREATION_WORKFLOW_2026.md](CONTENT_CREATION_WORKFLOW_2026.md).

- **Working file:** `content-draft.html`; apply when ready via `update-post-content.php`.
- **Prohibited:** Incremental word-count chasing; adding filler to reach target. Use section-by-section drafting from outline.
- **Styling:** When adding formulas, comparison tables, or callouts, use [CONTENT_FORMAT_PATTERNS.md](CONTENT_FORMAT_PATTERNS.md) § *Visual format decision guide* so `formula-block`, `table-breakout-wrapper`, and `blog-note` are applied deliberately—not on every paragraph.

## How to edit content

### Replace full HTML

1. Write or update content in `content-draft.html` (or stdin/`/tmp` until ready).
2. Apply via `update-post-content.php`:

   ```bash
   # From file
   php v2/scripts/blog/update-post-content.php --post=slug --category=lexikon --html=path/to/content.html

   # From stdin (paste or pipe)
   php v2/scripts/blog/update-post-content.php --post=slug --category=lexikon --stdin

   # Dry-run (validate only, no write)
   php v2/scripts/blog/update-post-content.php --post=slug --category=lexikon --html=... --dry-run

   # With backup before write
   php v2/scripts/blog/update-post-content.php --post=slug --category=lexikon --html=... --backup
   ```

3. The script regenerates `content.text`, `word_count`, and `modified_date`.

### Add FAQs only

Use `add-faqs-to-post.php` – it updates the `faqs` array only (load→modify→dump); no direct JSON edit.

### Other metadata

Use existing scripts (e.g. `add-links-to-json.php`, `add-pillar-links.php`) that follow the load→modify→dump pattern.

### Data files (keywords-sistrix, faq-research, etc.)

Use collection scripts that follow [JSON encoding standards](../../development/JSON_ENCODING_STANDARDS.md). Scripts must write with `JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES` so `post_url` and German text (ü, ä, „) are stored correctly. If files were corrupted: `php v2/scripts/blog/remediate-json-encoding.php --backup`.

### Featured image alt

Featured image alt must match `{Post Title} | Ordio` (max 150 chars). Use `fix-featured-image-alt.php --post=slug --category=lexikon` to fix. See [BLOG_IMAGE_ALT_GUIDELINES.md](BLOG_IMAGE_ALT_GUIDELINES.md).

### SEO meta (title, description)

SEO titles and descriptions live in `v2/data/blog/seo-meta.json` and are synced to post JSON via `sync-meta-to-posts.php`. Follow [BLOG_SEO_TITLE_STANDARDS.md](BLOG_SEO_TITLE_STANDARDS.md): no brand suffix, no `| Lexikon` or `| Ratgeber`. Use `normalize-seo-titles.php` for batch updates.

## Sync after external edits

If HTML was edited **outside** `update-post-content.php` (e.g. unavoidable manual JSON edit):

```bash
php v2/scripts/blog/sync-post-content-text.php --post=slug --category=lexikon
php v2/scripts/blog/sync-post-content-text.php --all   # all posts
php v2/scripts/blog/sync-post-content-text.php --post=slug --category=lexikon --dry-run
```

## Validation after content changes

### Standard chain (fast feedback)

```bash
make blog-post-validate POST=slug CAT=lexikon
```

Runs: compare-content (non-strict), section-depth, completeness, geo-citability, aeo-capsules, FAQ quality, H2-FAQ overlap, `validate-new-post.php --strict`.

### Pre-publish strict chain (recommended before deploy)

Stricter competitor compare, plus internal links and content-flow:

```bash
make blog-post-validate-strict POST=slug CAT=lexikon
```

See [BLOG_SCRIPTS_USAGE_GUIDE.md](BLOG_SCRIPTS_USAGE_GUIDE.md) for script details.

### Single-script checks

```bash
php v2/scripts/blog/validate-new-post.php --post=slug --category=lexikon
python3 v2/scripts/blog/audit-content-structure.py   # optional structure audit
```

## Checklist

### Pre-edit

- [ ] Backup (major edits): `python3 scripts/blog/backup-blog-content.py --manual`
- [ ] Content applied via `update-post-content.php` (not direct JSON edit)

### Edit

- [ ] One canonical `content-draft.html` (or stdin/`/tmp` until commit-ready)
- [ ] `php v2/scripts/blog/update-post-content.php --post=... --category=... --html=...` or `apply-and-validate-post.sh`
- [ ] FAQ-only changes: `add-faqs-to-post.php`

### Post-edit

- [ ] `sync-post-content-text.php` if HTML was changed outside `update-post-content.php`
- [ ] `make blog-post-validate` or `apply-and-validate-post.sh`; use **`blog-post-validate-strict`** before publish
- [ ] Browser preview on `localhost:8003` (layout, FAQ, links)

## Decision flow

```
Content edit needed
        │
        ▼
Replace full HTML? ─── Yes ──► update-post-content.php (or apply-and-validate-post.sh)
        │
        No
        ▼
Add FAQs only? ─── Yes ──► add-faqs-to-post.php
        │
        No
        ▼
Other metadata? ─── Yes ──► Existing script (load→modify→dump)
        │
        No
        ▼
Direct JSON edit? ─── PROHIBITED – use scripts above
```

## Related documentation

- [CONTENT_SYSTEM_INDEX.md](CONTENT_SYSTEM_INDEX.md) – Hub (new vs iterative edit)
- [CONTENT_CREATION_WORKFLOW_2026.md](CONTENT_CREATION_WORKFLOW_2026.md) – Outline-first drafting
- [BLOG_SCRIPTS_USAGE_GUIDE.md](BLOG_SCRIPTS_USAGE_GUIDE.md) – Script reference
- [CONTENT_DRAFT_LEGACY_INVENTORY.md](CONTENT_DRAFT_LEGACY_INVENTORY.md) – Non-canonical draft files
- [CONTENT_TOOLING_ROADMAP.md](CONTENT_TOOLING_ROADMAP.md) – Phase C tooling decision (section patch / MD / blocks)
- [CI_BLOG_CONTENT_GUARDRAILS.md](CI_BLOG_CONTENT_GUARDRAILS.md) – CI scope and trade-offs
- [BLOG_POST_IMPROVEMENT_PROCESS.md](BLOG_POST_IMPROVEMENT_PROCESS.md) – Full improvement workflow
- `.cursor/rules/blog-backup.mdc` – Backup procedures
