Gutenberg Block Reference
Reference for AI agents and automation working on id86.net content. It documents every block type the site uses, the exact serialized markup WordPress stores, how Markdown becomes blocks, and the front-end behavior that depends on that markup. Use it whenever you author, edit, or debug block content for id86.net.
The companion automation page is Content Sync Pipeline — this page is the data reference; that page is the pipeline reference.
Live Status
All 19 published posts were migrated to serialized blocks on 2026-08-13 via id86sync --force. Every post now opens directly in the block editor — no Classic blocks, no manual Convert-to-blocks, no freeform fragments. Verified per post with WP_Block_Parser (0 non-whitespace fragments) and do_blocks() (edge-case elements — nested lists, footnotes, blockquote+cite, separators, task checklists — all render). The pre-migration backup lives at ~/backups/id86-blockify-20260813-134036/ (DB dump + working-tree tarballs + manifest). Rollback is id86sync --legacy-html --force.
Pipeline Position
Markdown (.md)
-> sync.py: markdown2 (HTML) -> transform_code_blocks (CBP blocks) -> blockify.html_to_blocks (blocks)
-> wp-sync-bridge.php (wp_insert_post, post_content = serialized blocks)
-> front-end: do_blocks() + render_callbacks + theme JS/CSS
The generated post_content is serialized Gutenberg block markup (not raw HTML). This means posts open directly in the block editor — no "Classic" block, no manual Convert to blocks step. The serializer is tools/blockify.py in the canonical repo; the Code Block Pro conversion is tools/code_block_pro/blocks.py (see Code Block Rendering).
Block Inventory
| Block | Name | When it appears | Server render |
|---|---|---|---|
| Heading | core/heading | ##/###/#### | stored HTML |
| Paragraph | core/paragraph | blank-line text | stored HTML |
| Table | core/table | pipe table | stored HTML |
| List | core/list + core/list-item | -/1. lists, task checklists | stored HTML |
| Quote | core/quote | > blockquote | stored HTML |
| Separator | core/separator | --- horizontal rule | stored HTML |
| Custom HTML | core/html | .footnotes div (safe fallback) | stored HTML |
| Code Block Pro | kevinbatdorf/code-block-pro | fenced code blocks | plugin render + front.js copy button |
| Admonition | opshell/admonition | :::note|tip|info|warning|danger | plugin render_callback |
| (legacy) Classic | core/freeform | only pre-blockify posts | stored HTML |
Block Capabilities
core/heading
Attributes: anchor (slug), level (default 2, emitted for H3/H4). The stored <h> carries id + class="wp-block-heading".
<!-- wp:heading {"anchor":"start-and-stop"} -->
<h2 id="start-and-stop" class="wp-block-heading">Start and stop</h2>
<!-- /wp:heading -->
<!-- wp:heading {"level":3,"anchor":"level-three-heading"} -->
<h3 id="level-three-heading" class="wp-block-heading">Level three heading</h3>
<!-- /wp:heading -->
The heading anchor doubles as the on-page deep link (#start-and-stop), used by article-content.js to add hover anchor links.
core/paragraph
<!-- wp:paragraph -->
<p>Prefix every binding with the default prefix <code>Ctrl-b</code> unless noted.</p>
<!-- /wp:paragraph -->
Inline HTML (<code>, <strong>, <em>, <a>, <s>) is preserved. Empty/whitespace-only paragraphs are never emitted.
core/table
Tables are wrapped in <figure class="wp-block-table"> and the <table> gains class="has-fixed-layout". This is exactly what the editor's Convert-to-blocks action produces.
<!-- wp:table -->
<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Action</th><th>Command / key</th></tr></thead><tbody><tr><td>Start a named session</td><td><code>tmux new -s work</code></td></tr></tbody></table></figure>
<!-- /wp:table -->
core/list + core/list-item
Flat list (including task checklists — the literal [ ] / [x] marker stays in the <li> text so the theme JS can convert it to a checkbox):
<!-- wp:list -->
<ul class="wp-block-list"><!-- wp:list-item -->
<li>[ ] Install tmux from your package manager.</li>
<!-- /wp:list-item -->
<!-- wp:list-item -->
<li>[ ] Confirm the version with <code>tmux -V</code>.</li>
<!-- /wp:list-item --></ul>
<!-- /wp:list -->
Ordered list adds {"ordered":true} and uses <ol class="wp-block-list">. Nested lists recurse: a nested core/list block is serialized inside the parent <li>. Checklist UX depends on literal [ ] markers — keep them when editing task lists in the editor or the checkboxes/progress stop working (see Task Checklists).
core/quote
<!-- wp:quote -->
<blockquote class="wp-block-quote"><p>Markdown is the pre-publication source; Gutenberg is authoritative after publication.</p><cite>— ID86 Content Model</cite></blockquote>
<!-- /wp:quote -->
A trailing <cite> is pulled out of the last paragraph to match the block's saved markup.
core/separator
<!-- wp:separator -->
<hr class="wp-block-separator has-alpha-opacity"/>
<!-- /wp:separator -->
core/html (safe fallback)
Used for the .footnotes block so footnote styling (#fn-*, backrefs) survives untouched. Also the fallback for any element that cannot be reliably blockified.
<!-- wp:html -->
<div class="footnotes">...</div>
<!-- /wp:html -->
kevinbatdorf/code-block-pro
Fenced code blocks are pre-rendered by Shiki and stored as a full block. Full attribute reference lives in Code Block Rendering. Key facts for automation: code = raw source, codeHTML = Shiki-highlighted <pre> HTML, language, theme: "github-dark", bgColor "#24292e", copyButton true. The block comment contains the whole attribute JSON — use Gutenberg for edits after publication; do not expect a Markdown re-sync to overwrite the live block.
opshell/admonition
<!-- wp:opshell/admonition {"type":"tip","title":"Custom tip title"} -->
<!-- wp:paragraph -->
<p>Use <code>tools/sync-docs.sh --dry-run</code> before every real sync.</p>
<!-- /wp:paragraph -->
<!-- /wp:opshell/admonition -->
| Attribute | Values | Default when omitted |
|---|---|---|
type | note / tip / info / warning / danger | note |
title | any string | ucfirst(type) (e.g. Tip) |
Rendered server-side by opshell-docusaurus-admonitions (render_callback) into <aside class="opshell-admonition opshell-admonition--{type}">. Admonitions can have multiple inner paragraph blocks. The plugin ALSO has an editor subscribe auto-convert and a the_content raw filter for literal ::: text — both become no-ops once content is block-serialized.
Design — disclosure card (theme v1.23+)
The child theme overrides the plugin stylesheet (css/article-content-v2.css) so admonitions render as disclosure-style cards modeled on the .id86-affiliate-disclosure box on the Recommendations page: solid tinted surface, 1px border + 4px left accent, no icon (.opshell-admonition__badge hidden via display:none), no gradient, no box-shadow, 0.92rem/1.6 body text, bold accent-colored title. CSS-only — no JS.
Per-type accents (light / dark mode):
| Type | Light accent | Surface | Border | Dark accent | Dark surface |
|---|---|---|---|---|---|
note | #7c5cbf (violet) | #f8f6fd | #e6def6 | #a78bfa | #1d1733 |
tip | #2f9e44 | #f7fcf8 | #dff0e3 | #4cc46a | #14291a |
info | #0d9488 | #f3fbfa | #d7efeb | #2fc3b3 | #0f2a27 |
warning | #b7791f | #fbf8f0 | #f0e5cc | #e0a83c | #2b2110 |
danger | #c24141 | #fff7f7 | #e8b8b8 | #e05a5a | #311414 |
Rules: keep the icon badge hidden via CSS (never reintroduce icons); the review posts' :::note Affiliate disclosure admonitions render as violet cards under this design. Override selectors are scoped .single-post .entry-content .opshell-admonition (higher specificity than the plugin's admonitions-v2.css).
Markdown → Block Mapping (what blockify implements)
| Markdown | Block | Notes |
|---|---|---|
##/###/#### | core/heading | anchor from markdown2 header-ids |
| paragraph | core/paragraph | empties skipped |
| pipe table | core/table | has-fixed-layout, figure wrapper |
- item / 1. item | core/list + core/list-item | nested lists recurse |
- [ ] / - [x] | core/list (task list) | literal marker preserved for JS |
```lang | kevinbatdorf/code-block-pro | Shiki github-dark |
:::type [title] | opshell/admonition | inner paragraphs |
> quote | core/quote | trailing <cite> extracted |
--- | core/separator | |
.footnotes | core/html | preserves #fn-* styling |
 | core/image | figure wrapper |
Editor Behaviors to Know
- Convert to blocks / rawHandler is a client-side editor action (
@wordpress/blocksrawHandler). It is what previously converted Classic content. The pipeline now produces the same serialized output server-side, so the manual step is unnecessary. - Block parser tolerance: WordPress is lenient about whitespace/newlines between
<!-- wp:... -->comments, so blockify output (blank-line-separated blocks) parses cleanly. - Block validation / "attempt block recovery": if stored inner HTML doesn't match a block's
save()output, the editor warns and offers recovery. This is why the serialization formats above are fixed and verified against the live editor. - Revisions pitfall: clicking Convert to blocks inside the editor saves a revision, not the published post. The manual conversion also produced corrupt artifacts (whole admonition text dumped into the
titleattribute, stray empty paragraphs). The pipeline output avoids both. - Partial conversion pitfall: a manual Convert-to-blocks can leave raw
<h2>/<ul>fragments outside any block (seen in stored tmux posts). The pipeline wraps everything, so no freeform fragments remain.
Front-End Coupling (do not break)
| Feature | Depends on |
|---|---|
| Wide-table horizontal scroll | article-content.js wraps <table> in .id86-table-scroll |
| Heading deep links | id="..." + wp-block-heading on h2/h3/h4 |
| Task checkboxes + "X of Y done" + Reset | literal [ ]/[x] as the <li> text node start |
| Copy button | Code Block Pro front.js (block present ⇒ assets auto-enqueued) |
| Admonition styling | opshell/admonition block render + theme disclosure-card override in css/article-content-v2.css (plugin opshell-admonitions style auto-enqueued) |
Reading time (N min read) | id86_reading_time() strips <!--...--> comments before counting words |
| Fuse.js search | excerpt/title/category/tags (not body) — unaffected by block markup |
Automation Integration
- Where serialization happens:
sync.py(parse_document) andid86_publish_scheduled.py(render_body) both callblockify.html_to_blocks(transform_code_blocks(markdown2.markdown(...))). Keep the two paths identical. - Publication protection:
_id86_source_hashis the SHA-256 of the pre-publication.md; published posts are protected from later Markdown syncs, including--force. Gutenberg is authoritative after publication. --legacy-html: stores classic HTML instead of blocks (pre-blockify behavior) — the escape hatch for rollbacks.--only <source_path>: sync a single document (used for the pilot/migration staging).- Sync report: unchanged —
created/updated/skipped/errorsper document. - Categories/tags/SEO: driven by frontmatter; block content does not change
_seopress_titles_title/_seopress_titles_descor excerpt behavior.
Rules for AI Editing Block Content
- Before publication, edit Markdown and re-sync; after publication, edit blocks in Gutenberg and do not re-sync the post from Markdown.
- Keep literal
[ ]/[x]markers in task-list<li>text. - Keep heading
id/anchorin sync (both live in the stored markup). - Don't emit empty paragraphs; join blocks with blank lines.
- Admonitions: always set a valid
type;titleis optional (defaults toTip/Note/…). - After any pipeline change, verify both
sync.pyand the publish engine produce byte-identical block output, and runid86sync --dry-runbefore a real sync. - Rollback safety:
--legacy-html --forcerestores classic HTML; the pre-migration backup is a DB dump + working-tree tarballs.