Theatrum Blocks
30 custom Gutenberg blocks (48 registered types) for a professional theatre site — production metadata, query filtering, carousels, tabs, and tables. PHP + React, WordPress Coding Standards enforced.
by Anna Jennings (Theatrum Mundi) · github.com/abananaj/theatrum-blocks
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/abananaj/theatrum-blocks/archive/refs/heads/main.zipCustom Gutenberg block plugin for Chance Theater. 30 top-level blocks (48 registered block types counting nested children — includes deprecated blocks kept for existing content) for production management, metadata display, carousels, tables, tabs, and frontend filtering.
Version: 0.1.1 | Requires: WordPress 6.8+ / PHP 7.4+ | License: GPL-2.0-or-later
📋 CLAUDE.md · AGENTS.md · CHANGELOG.md
Block Inventory
Rebuilt 2026-08-04 directly against the $custom_blocks registration array in theatrum-blocks.php (30 top-level blocks, 48 total including nested children, as of 2026-08-31's removal of cover-card, chance-card, and meta-icon — see BLOCK_CLEANUP_PLAN.md) — the previous version of this table had drifted significantly (documented blocks that were deleted per BLOCK_CLEANUP_PLAN.md, and was missing several that were added since).
🎭 Production Blocks
| Block | Status | Notes |
|---|---|---|
production-performances |
✅ | Var of meta-repeater; filters to upcoming only, shows next 5 |
production-quotes |
✅ | Var of meta-repeater; responds to font-size |
🔗 Meta Blocks (Block Bindings)
Variation blocks backed by the theatrum/post-meta binding source (WP 6.5+). Existing instances migrate via "Transform to" in the block toolbar.
| Block | Status | Notes |
|---|---|---|
meta-button |
✅ | Var of core/button |
meta-date |
✅ | Var of core/paragraph; date format arg |
meta-embed |
✅ | Var of core/embed |
meta-field |
✅ | Var of core/paragraph |
meta-file |
✅ | Var of core/file |
meta-image |
✅ | Var of core/image; binds id attribute |
meta-gallery |
✅ | Kept as custom block (too many custom controls) |
meta-repeater |
✅ | Variations: bylines, awards, producers, performances, quotes, notes, events |
meta-related |
⏭️ | Skip — no suitable core block target |
meta-time |
⚠️ | Actively used in existing content — kept for now, revisit with a migration later |
📋 Table-Advanced
Hierarchical table block system.
| Block | Status |
|---|---|
table-advanced |
⚠️ table-layout: auto default not yet configurable |
table-advanced/table-caption |
✅ |
table-advanced/table-header |
✅ |
table-advanced/table-body |
✅ |
table-advanced/table-footer |
✅ |
table-advanced/table-row |
✅ |
table-advanced/table-heading-cell |
✅ responds to color settings |
table-advanced/table-cell |
✅ responds to color settings |
🖼️ Display Blocks
| Block | Status | Notes |
|---|---|---|
breadcrumbs |
✅ | |
carousel |
✅ | Child: carousel/carousel-item |
slider |
✅ | Child: slider/slider-item |
blockquote-advanced |
✅ | Children: blockquote-text, blockquote-source |
list-icons |
⚠️ | Needs list-item as nested block |
list-icons/list-item-icon |
✅ | Child of list-icons |
list-thumbnail |
✅ | Refactored to nested list-item-thumbnail blocks (model after list-icons); flip-card hover animation fixed |
list-thumbnail/list-item-thumbnail |
✅ | Child of list-thumbnail |
popover |
✅ | Trigger and content are separate nested blocks (popover/popover-trigger, popover/popover-content), each accepting any blocks |
popup |
✅ | |
tabs |
✅ | Renamed from production-tabs (2026-08-06) — no longer production-specific; recategorized to design. Children: tab, tab-heading, tab-content |
page-nav |
✅ | |
title-advanced |
✅ |
🔍 Query & Data Blocks
| Block | Status | Notes |
|---|---|---|
query-filter |
✅ | Frontend filter/sort for query loops via Interactivity API |
query-loop |
✅ | Variations by main post type. credit-loop variation deprecated (2026-08-06) — hidden from the inserter |
site-option |
✅ | Shows option value + meta value in .site-option-meta span. staff/board variations deprecated (2026-08-06) — hidden from the inserter; use the generic block for new content |
term-meta |
⭐ | season-producer variation deprecated (2026-08-06) — hidden from the inserter; use the generic block for new content |
table-of-contents |
⚠️ | Renamed from core/table-of-contents to theatrum/table-of-contents (was squatting on core's namespace); auto-generation from headings not yet wired |
Architecture
theatrum-blocks/
├── src/blocks/[block-name]/
│ ├── block.json # metadata, attributes, supports, context
│ ├── edit.js # editor React component
│ ├── render.php # server-side render (dynamic blocks)
│ └── index.js # optional: icons, registration
├── inc/
│ ├── helpers.php # date parsing, production queries, query-loop-by-term filter
│ ├── rest-endpoints.php # /theatrum/v1/* routes for block editor data
│ └── block-bindings.php # theatrum/post-meta binding source (WP 6.5+)
├── build/ # compiled output (gitignored)
├── theatrum-blocks.php # plugin entry: block registration, category, devMode attr, style-book
└── package.json
Key Systems
- Block Bindings —
theatrum/post-metasource ininc/block-bindings.phppowers all meta-variation blocks. Reads ACFget_field()with rawget_post_meta()fallback. Handles date formatting, URL/href, and attachment ID attributes. - REST API — 15 endpoints under
/wp-json/theatrum/v1/serve block editor previews. All requireedit_postscapability. - Date parsing —
theatrum_parse_flexible_date()handles Unix timestamps, YYYYMMDD, YYYY-MM-DD, MM/DD/YYYY, text dates; results cached 1h inct_datesgroup. - Query loop by term —
theatrum_filter_query_loop_by_term()constrains nested query loops to theirterm-templatecontext (supports WP 6.9+core/term-template). - Core-block styles — the plugin styles a few core blocks rather than forking them:
is-style-ct-carousel/is-style-ct-slideroncore/query/core/gallery(src/formats/), andis-style-tm-accordion-icononcore/accordion(src/accordion-icon/), which also ships a Icon Accordion variation with a pre-built five-card stack. Cards colour themselves from the nomenclature ramp by position (primary → quinary, then muted-light), each with its matching--ct-on-*text colour, unless an author paints one; each card also gets an Icon panel (src/accordion-icon/controls.js, acore/accordion-itemextension) for its rail icon — any image from the media library — with the rail's background colour a menu item away in the same ToolsPanel. A card with no icon shows the accordion's+, drawn dark on every card and rotated into an × on open. A card painted a light colour by hand still needsis-text-dark, since the accordion family has nocolor.textsupport. A small front-end script (src/accordion-icon/view.js, enqueued by arender_blocksniff for pages that render the style) holds the clicked card still while the stack reflows, so anautoclosecollapse above it cannot yank the page. - devMode —
theatrum_add_dev_mode_attributeinjects adevModeboolean attribute to everytheatrum/*block viablock_type_metadatafilter. Onlybreadcrumbscurrently wires up the inspector toggle/indicator (seeDEV_MODE.md).
Development
npm run start # webpack watch + hot reload
npm run build # production build (minified), one-time
npm run build:watch # production build, watch mode
npm run deploy # same as build
npm run format # WordPress code standards
npm run lint:js # JS lint
npm run lint:css # CSS lint
npm run packages-update # update @wordpress/* dependencies
npm run plugin-zip # create a distributable plugin zip
Next Steps (by severity)
🔴 Security
— fixed. It's still public (the home page widget needs anonymous access), but now checks/cover-cardendpoint is unauthenticatedis_post_publicly_viewable()so drafts/private/pending posts of any type can no longer be enumerated.board-member/staff-member/site-option allow reading arbitrary— fixed. Option names are now gated towp_optionsoptions_/option_-prefixed ACF-options-page values viatheatrum_is_allowed_settings_option().
🟠 Bugs / Correctness
Wrong text domain in— fixed (production-details/render.phptheatrum-blocks).— fixed in cover-card, copyright-date-block, anddate()instead ofwp_date()theatrum_format_production_date().— fixed, now falls back tocover-cardignores block contextpostId$block->context['postId'].Mixed— fixed; production queries now parse viaopening/closingmeta_query formatstheatrum_parse_flexible_date()rather than SQL DATE/DATETIME casts, since stored values are a genuine mix ofYmdandY-m-d H:i:s.
🟡 Technical Debt
Unprefixed REST callback functions:— fixed; all REST endpoint functions inget_board_member_rest_callback,get_staff_member_rest_callback,get_meta_date_rest_callback,get_meta_time_rest_callback,get_meta_related_rest_callback,get_production_performances_rest_callback,get_site_option_rest_callback— should usetheatrum_prefix to avoid collisions.inc/rest-endpoints.phpnow use thetheatrum_prefix (along with the whole plugin'schance/→theatrum/block-namespace unification).board-member/staff-memberREST callbacks are ~90% duplicate code — extract shared person-list logic into a helper. (Low priority now that bothsite-optionvariations are deprecated.)theatrum_get_next_production()callstheatrum_get_current_production()internally — two pages showing both blocks run multiple uncached DB queries; considerwp_cache_get/set.package.jsonstill has scaffolding defaults:description= "Example block scaffolded with Create Block tool." andauthor= "The WordPress Contributors".
🗑️ Cleanup / Removal
meta-timeis actively used in existing content — keeping it for now; revisit removal alongside a content migration.— removed 2026-08-31; deprecated (2026-08-06) then confirmed 0 live instances and fully deleted (registration, source, REST endpoint).meta-icon's folder is unregistered dead code- Fold
meta-relatedintoterm-meta(marked Skip). Evaluate— it's theseason-producerterm-metablock'sseason-producervariation (not a separate block); deprecated (2026-08-06), hidden from the inserter,term-meta's generic display is the supported path.Evaluate— removed 2026-07-06; confirmed unused in any published content (only referenced in a docs/catalog page and a trashed test page).production-detailsDecide fate of— they're theboard-memberandstaff-membersite-optionblock'sstaff/boardvariations (not separate blocks); deprecated (2026-08-06), hidden from the inserter,site-option's generic display is the supported path.— removed 2026-08-31; confirmed 0 live instances (only a trashed page and an unreferenced reusable block still contained the markup) and fully deleted (registration, source,cover-cardandchance-carddeprecated (2026-08-06)cover-card's REST endpoint, theme-side style refs).query-loop'scredit-loopvariation deprecated (2026-08-06) — hidden from the inserter; the other post-type-specific query-loop variations are unaffected.
🔧 Improvements
table-advanced: addtable-layout-fixedtoggle.list-icons: refactor to use nestedlist-item-iconblock (model aftercore/list+core/list-item).—title-subtitle: addcore/post-titleto allowed inner blocks.title-subtitleno longer exists;title-advancedis the current registered block.— neither is registered anymore;card-carousel/cover-carousel: resolve save-media and nav issues.carousel/sliderare the current blocks.
Security Posture
Overall: Good. Input is consistently sanitized with sanitize_text_field, sanitize_key, esc_html, esc_url, wp_kses_post. Tag injection is blocked via allowlists in meta-repeater and site-option. Serialized data uses unserialize(['allowed_classes' => false]). The board/staff/site-option wp_options exposure (see Next Steps) has been addressed; the /cover-card public endpoint was addressed and later removed entirely along with the block.