Skip to main content

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

BlockNameWhen it appearsServer render
Headingcore/heading##/###/####stored HTML
Paragraphcore/paragraphblank-line textstored HTML
Tablecore/tablepipe tablestored HTML
Listcore/list + core/list-item-/1. lists, task checklistsstored HTML
Quotecore/quote> blockquotestored HTML
Separatorcore/separator--- horizontal rulestored HTML
Custom HTMLcore/html.footnotes div (safe fallback)stored HTML
Code Block Prokevinbatdorf/code-block-profenced code blocksplugin render + front.js copy button
Admonitionopshell/admonition:::note|tip|info|warning|dangerplugin render_callback
(legacy) Classiccore/freeformonly pre-blockify postsstored 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 -->
AttributeValuesDefault when omitted
typenote / tip / info / warning / dangernote
titleany stringucfirst(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):

TypeLight accentSurfaceBorderDark accentDark 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)

MarkdownBlockNotes
##/###/####core/headinganchor from markdown2 header-ids
paragraphcore/paragraphempties skipped
pipe tablecore/tablehas-fixed-layout, figure wrapper
- item / 1. itemcore/list + core/list-itemnested lists recurse
- [ ] / - [x]core/list (task list)literal marker preserved for JS
```langkevinbatdorf/code-block-proShiki github-dark
:::type [title]opshell/admonitioninner paragraphs
> quotecore/quotetrailing <cite> extracted
---core/separator
.footnotescore/htmlpreserves #fn-* styling
![alt](url)core/imagefigure wrapper

Editor Behaviors to Know

  • Convert to blocks / rawHandler is a client-side editor action (@wordpress/blocks rawHandler). 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 title attribute, 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)

FeatureDepends on
Wide-table horizontal scrollarticle-content.js wraps <table> in .id86-table-scroll
Heading deep linksid="..." + wp-block-heading on h2/h3/h4
Task checkboxes + "X of Y done" + Resetliteral [ ]/[x] as the <li> text node start
Copy buttonCode Block Pro front.js (block present ⇒ assets auto-enqueued)
Admonition stylingopshell/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 searchexcerpt/title/category/tags (not body) — unaffected by block markup

Automation Integration

  • Where serialization happens: sync.py (parse_document) and id86_publish_scheduled.py (render_body) both call blockify.html_to_blocks(transform_code_blocks(markdown2.markdown(...))). Keep the two paths identical.
  • Publication protection: _id86_source_hash is 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/errors per document.
  • Categories/tags/SEO: driven by frontmatter; block content does not change _seopress_titles_title / _seopress_titles_desc or excerpt behavior.

Rules for AI Editing Block Content

  1. Before publication, edit Markdown and re-sync; after publication, edit blocks in Gutenberg and do not re-sync the post from Markdown.
  2. Keep literal [ ]/[x] markers in task-list <li> text.
  3. Keep heading id/anchor in sync (both live in the stored markup).
  4. Don't emit empty paragraphs; join blocks with blank lines.
  5. Admonitions: always set a valid type; title is optional (defaults to Tip/Note/…).
  6. After any pipeline change, verify both sync.py and the publish engine produce byte-identical block output, and run id86sync --dry-run before a real sync.
  7. Rollback safety: --legacy-html --force restores classic HTML; the pre-migration backup is a DB dump + working-tree tarballs.