CD WP Base + Post-Type Plugins — Audit & Improvement Inventory

Generated 2026-04-29. Audit of cd-wp-base v1.6.0 and the three associated post-type plugins (cd-news, cd-events, cd-people) at their current main-branch HEAD. Each item has a plain-English summary up front and technical details below. Use the scope tabs to focus on one codebase at a time.

Scope

Filter by type

Filter by section

Claim status

0 of 0 items shown ·
BASE-A1 Base Theme Bugs BUG cd/tabs block crashes on PHP 8 with count(null) TypeError when no tabs configured

In plain terms: The Tabbed Content block crashes the page it’s on (fatal PHP error) the moment an editor adds the block to a page, because it tries to count tabs before any have been added.

  • Where: cd-wp-base/blocks/tabs/tabs.php:3
  • Cause: count(get_field('content_tabs')) with no is_array() check. get_field() returns null on an unconfigured repeater.
  • Fix: Guard with is_array() before counting, or coerce to (array).
BASE-A2 Base Theme Bugs BUG Breadcrumb block only walks the page-ancestor tree; renders nothing on singles, taxonomies, and archives

In plain terms: The breadcrumb trail only works on regular pages with a parent-child structure. On single posts of any custom post type, on category/tag pages, or on archive listings, the trail shows nothing useful — it doesn’t recognize those contexts.

  • Where: cd-wp-base/templates/blocks/breadcrumb/breadcrumb.php:2
  • Cause: Walks only get_post_ancestors(get_the_ID()). No branches for is_singular(), is_tax(), is_category(), is_tag(), or is_post_type_archive().
  • Fix: Add explicit handling for each context. Pair with a configurable post-type → parent-page mapping (see BASE-C2) so the trail can place a single news article under, say, the “News” page.
BASE-A3 Base Theme Bugs BUG Title block renders nothing on taxonomy and post-type-archive pages

In plain terms: The page-title block knows how to say “About Us” on a regular page but doesn’t know what to say on a category page, tag page, or post-type archive — so those pages show a blank title.

  • Where: cd-wp-base/templates/blocks/title/title.php
  • Cause: Branches only handle is_page(), is_single(), is_search(), is_404(), plus FSE editor placeholders.
  • Fix: Add single_term_title(), post_type_archive_title(), and the_archive_title() branches.
BASE-A4 Base Theme Bugs BUG Section nav uses get_the_ID(), returning 0 on non-page queries — nav disappears on singles & tax pages

In plain terms: The sidebar section navigation is built around the current page’s ID. On single posts of a custom post type or on taxonomy pages, that ID comes back as zero, so the nav silently produces nothing.

  • Where: cd-wp-base/functions/section-nav.php:9 ($current_page_id = get_the_ID();)
  • Symptom: Section nav shows on pages but vanishes on singles, taxonomy archives, and post-type archives.
  • Fix: When the current context isn’t a page, map back to a configured parent page via the missing post-type → parent-page mechanism (BASE-C2).
BASE-A5 Base Theme Bugs BUG Sidebar block templates ship “Mussum Ipsum” lorem text by default

In plain terms: The default content of the top and bottom sidebar template parts is novelty lorem-ipsum joke text. Any editor who forgets to clear the placeholder before saving will publish gibberish to the live site.

  • Where: cd-wp-base/parts/sidebar-top.html, cd-wp-base/parts/sidebar-bottom.html, and the same lorem hardcoded at cd-wp-base/templates/blocks/sidebar-top/sidebar-top.php:18 & sidebar-bottom.php:18.
  • Fix: Replace with neutral placeholder text or empty InnerBlocks templates. If a fallback is desired, document it.
BASE-A6 Base Theme Bugs BUG Manual Card block uses ACF’s mode: preview, requiring click-through to edit each field

In plain terms: To edit anything on a Manual Card, the editor has to first click into the block before any field becomes editable — an extra step on every interaction.

  • Where: cd-wp-base/blocks/manual-card/block.json:17 ("mode": "preview")
  • Fix: Switch to "mode": "auto" (matches the Aside block’s configuration) OR expose a filter to toggle.
BASE-A7 Base Theme Bugs BUG Tabbed Content floats leak past tab boundaries and wrap subsequent blocks

In plain terms: If a tab contains a floated element (an image hugging one side, for instance), the float spills out below the tabs and disturbs whatever block follows.

  • Where: cd-wp-base/blocks/tabs/tabs.php markup & .ui-tabs .tab-inner styling.
  • Fix: Apply display: flow-root on .ui-tabs .tab-inner to contain floats inside each panel.
BASE-A8 Base Theme Bugs DEBT Stray error_log() debug statement left in accordion.php

In plain terms: A debug log line was left in the Accordion block’s render template. Every accordion render writes the JSON-encoded default-blocks array to the PHP error log, polluting logs and adding (small) overhead.

  • Where: cd-wp-base/blocks/accordion/accordion.php:9error_log($accordion_default_blocks);
  • Fix: Remove the line.
BASE-B1 Base Theme Editor & CSS BUG base.css leaks into the WordPress admin via enqueue_block_assets

In plain terms: The theme’s front-end stylesheet (a large CSS file with broad selectors on body, h2, form inputs, etc.) is enqueued through a hook that fires on both the front end and the admin. The admin UI inherits front-end rules it was never designed for — metabox headers, form fields, and buttons all become visually broken.

  • Where: cd-wp-base/functions/styles-and-scripts.php:10add_action('enqueue_block_assets', 'cd_wp_base_enqueue_cds_required') enqueues assets/cds/css/base.css for both contexts.
  • Fix: Use wp_enqueue_scripts for front-end assets and enqueue_block_editor_assets for editor-iframe styles. Reserve enqueue_block_assets for genuinely shared block CSS.
BASE-B2 Base Theme Editor & CSS BUG base.css blanket width:100%; max-width:100% blocks Wide and Full alignment

In plain terms: WordPress’s “Wide” and “Full” alignment options let blocks visually break out of the main content column. A blanket width rule in the theme’s base CSS prevents any block from ever exceeding its container, so those alignment options silently do nothing.

  • Symptom: .alignwide / .alignfull have no effect on block width.
  • Fix: Remove the unscoped width rule from base.css, or scope it to non-Gutenberg contexts using :not(.is-layout-constrained > *) or class-based exclusions.
BASE-B3 Base Theme Editor & CSS BUG Core .wp-block-post-featured-image:where(.alignright){width:100%} defeats float layout

In plain terms: When an editor sets a featured image to float right beside text, WordPress core’s own stylesheet (loaded after the theme’s) forces the image to 100% width — so the float does nothing and the image takes the entire column.

  • Cause: Core’s low-specificity :where() rule beats the theme’s, then is itself overridden by core’s alignment normalizers.
  • Fix: Theme should emit a higher-specificity rule preserving image floats, or unregister the core block style and provide its own.
BASE-B4 Base Theme Editor & CSS BUG Aside block’s editor wrapper uses display: contents, neutralizing float and width settings

In plain terms: The Aside block has settings for width and floating so it can sit alongside body text. In “Show Template” mode the wrapper is made invisible to layout via display: contents, which means the Aside’s float and width settings have no effect on positioning.

  • Cause: cd-wp-base/assets/cds/sass/utility/_cms.scss applies display: contents to every .block-editor-block-list__block inside .region-sidebar.
  • Fix: Target the inner <aside> element directly rather than the editor wrapper, or scope the rule to specific block types only.
BASE-B5 Base Theme Editor & CSS BUG core/spacer height collapses (or balloons) inside the editor in Show Template mode

In plain terms: The Spacer block (used to add vertical gap between blocks) renders incorrectly inside the editor — either compressing to zero or expanding to thousands of pixels — depending on context, because Gutenberg’s height: 100% resolves against an unmeasured parent.

  • Cause: Gutenberg’s .block-library-spacer__resize-container:not(.is-resizing){ height: 100% !important } meets a parent with no defined height.
  • Fix: Theme’s editor.scss should ensure the spacer’s outer wrapper preserves its inline height attribute. May also warrant a Gutenberg core ticket.
BASE-B6 Base Theme Editor & CSS BUG Editor’s .page-template-* body class isn’t carried into the Gutenberg iframe

In plain terms: The theme registers editor styles like “on pages using the Full-Bleed template, do X.” Modern Gutenberg loads the editor canvas inside an iframe whose <body> doesn’t inherit the page-template body class — so those template-specific editor styles never apply.

  • Where: Body classes are added at cd-wp-base/functions/editor-functions.php (cd_wp_base_editor_body_class) but only on the outer admin shell, not the editor iframe.
  • Fix: Either inject the body class into the editor iframe via JS, or rewrite the affected selectors to match what’s actually present in the iframe DOM.
BASE-B7 Base Theme Editor & CSS BUG Editor wide alignment is hardcoded to 886px instead of theme.json wideSize

In plain terms: In the block editor, anything set to “Wide” alignment is capped at a magic 886 pixel value defined in the editor stylesheet. The cap should be sourced from theme.json so it matches the front end and adapts to project-level overrides.

  • Where: cd-wp-base/assets/sass/editor.scss hardcodes 886px.
  • Fix: Drive the value from settings.layout.wideSize in theme.json.
BASE-B8 Base Theme Editor & CSS BUG Full-bleed and no-sidebar templates appear constrained in the editor due to Gutenberg constrained-layout defaults

In plain terms: Templates designed to be edge-to-edge (no sidebar, full browser width) still look like they have a narrow centered column inside the editor because Gutenberg’s default constrained-layout squeezes them. WYSIWYG breaks down for those templates.

  • Fix: Emit editor-side overrides keyed off the Full-Bleed body class (see BASE-B6) so templates that declare full-bleed actually render full-bleed in the editor too.
BASE-B9 Base Theme Editor & CSS BUG Core separator block styles override theme HR styles like accent1

In plain terms: The theme defines branded horizontal-line styles (Blue-Green, Cornell Icon, Flourish, etc.). When an editor picks one, WordPress’s core separator styles win the cascade, so the picked style doesn’t appear.

  • Cause: Core classes .wp-block-separator and .has-alpha-channel-opacity outweigh the theme’s plain hr.accent1, etc.
  • Fix: Unregister core’s separator block styles and re-register the theme’s own set, or raise specificity on the theme’s rules.
BASE-B10 Base Theme Editor & CSS BUG ACF radio & checkbox inputs are blanked out in the admin by base.css stripping native appearance

In plain terms: Radio buttons and checkboxes inside ACF fields appear empty in admin (the user can’t tell which is selected) because front-end styles set appearance: none with a custom SVG background that doesn’t apply correctly in admin.

  • Cause: Symptom of BASE-B1 (front-end CSS leaking into admin).
  • Fix: Scope appearance: none / SVG-background styling to front-end-only contexts.
BASE-B11 Base Theme Editor & CSS BUG ACF select dropdowns are mis-styled in admin by front-end select styling

In plain terms: ACF dropdown menus look broken in admin for the same root cause as BASE-B10 — front-end select styling leaks into admin contexts.

  • Fix: Scope select-element styling in base.css to front-end only.
BASE-B12 Base Theme Editor & CSS BUG Aside block’s alignment field includes a “Full-width” choice that visibly breaks layout

In plain terms: The Aside block’s alignment dropdown offers a “Full-width” option, but selecting it produces broken visual output. The choice should either be removed or its render path fixed.

  • Where: ACF field for the Aside block’s alignment (in cd-wp-base/blocks/custom-fields/aside-block.json).
  • Fix: Remove the broken choice from the field options, or fix the render path so the option works as labeled.
BASE-B13 Base Theme Editor & CSS BUG Block editor panels show duplicate ACF field descriptions in repeater fields

In plain terms: In editor side panels for blocks that include ACF repeater fields, each field’s help text is displayed twice.

  • Fix: Investigate whether the duplicate originates in theme markup or in the ACF / ACF Extended UI; file an upstream report if it’s in ACF.
BASE-B14 Base Theme Editor & CSS OBSERVATION .wp-block-cd-content-section and .wp-block-cd-card-container apply editor width constraints by default

In plain terms: Two of the theme’s container blocks impose a narrow editor width that doesn’t reflect the block’s actual front-end behavior. The constraint is on by default and isn’t exposed as a configurable option.

  • Fix: Make the editor-width behavior opt-in via a block attribute or block style, or remove the default constraint entirely.
BASE-C1 Base Theme Architecture FILTER “Disable Posts” behavior is a stack of function_exists() guards instead of an opt-out filter

In plain terms: The theme turns off WordPress’s built-in Posts feature by default. The only way to keep Posts on a project that wants them is to pre-define empty versions of the theme’s helper functions so they never run — a brittle workaround. A simple opt-in filter would replace the function-shadowing pattern.

  • Where: cd-wp-base/functions/disable-posts-comments.php — every helper is wrapped in if (!function_exists(...)).
  • Fix: Expose cd_wp_base_disable_posts (default true) and gate the action/filter registrations on it, so projects can opt out without function-shadowing.
BASE-C2 Base Theme Architecture FILTER IMPROVE No first-class concept of “post type → parent page” mapping

In plain terms: Most sites want to treat custom post type singles as logical children of a real page (e.g., “a news article belongs under the News page”) so that breadcrumbs, the active main-nav highlight, and the section nav all behave intuitively. WordPress doesn’t have this concept built in, and the theme doesn’t add one. As a result, the breadcrumb (BASE-A2), title (BASE-A3), and section nav (BASE-A4) bugs don’t have a clean fix path.

  • Fix: Add an Archive Settings field group that maps each public post type and taxonomy to a parent page. Feed that mapping into the breadcrumb, title, and section-nav blocks. Filter nav_menu_css_class so the parent page’s nav item gets current-menu-ancestor when on a related single.
BASE-C3 Base Theme Architecture FILTER Site Owner FSE access uses a hardcoded inline MutationObserver with no extension point

In plain terms: “Site Owner” is a restricted role. The theme controls what they can see in the Full Site Editor with a chunk of inline JavaScript that hides nav items and patterns by string match. There’s no way to add or remove items from the hide list without forking the whole action.

  • Where: cd-wp-base/functions/user-roles.php:172–234 (cd_wp_base_customize_template_pattern_edit_page_per_users) — arrays of hiddenNavItems and hiddenPatterns are baked into the inline script.
  • Fix: Expose filters such as cd_wp_base_site_owner_hidden_nav_items and cd_wp_base_site_owner_hidden_patterns, and pass the resulting arrays into the inline script via wp_localize_script or wp_add_inline_script.
BASE-C4 Base Theme Architecture FILTER cd_normalized_card_defaults filter only supports a single taxonomy per card

In plain terms: Card grids can show small “tag” chips on each card sourced from a taxonomy. The current data flow accepts only one taxonomy. Showing tags from multiple taxonomies on the same card requires either side-channel globals or forking the resolution path.

  • Fix: Accept an array of taxonomy slugs as the canonical card-tags input, with the existing string form maintained as a backward-compat path.
BASE-C5 Base Theme Architecture IMPROVE Allowed-blocks list includes core/freeform (Classic) and core/nextpage (Page Break) by default

In plain terms: Most modern sites don’t want the legacy Classic block or the Page Break block in the inserter. The theme leaves both enabled by default. The default should arguably flip, with an opt-in path for sites that need them.

  • Where: cd-wp-base/functions/block-functions.php:72 & :79 (core/freeform, core/nextpage).
  • Tension: The theme actively invests in supporting Classic-block content (TinyMCE button rows, format dropdown, plugin loader at editor-functions.php:413–476). Decision: continue first-classing Classic, or treat it as a deprecation path?
  • Fix: Default to excluding the two blocks; expose a filter to add them back when needed.
BASE-C6 Base Theme Architecture FILTER IMPROVE Wholesale dequeue of global-styles on the front end loses Gutenberg layout-support rules

In plain terms: WordPress generates a “global styles” stylesheet that includes (among other things) the layout-support rules that make Row, Stack, and Grid variants of the Group block work. The theme dequeues this stylesheet wholesale on the front end — presumably to avoid a few specific conflicts — and unintentionally throws out those layout rules.

  • Where: cd-wp-base/functions/templating-functions.php:479wp_dequeue_style('global-styles');
  • Symptom: .is-layout-flex, .is-layout-grid, and similar selectors disappear; Row/Stack/Grid variants of Group don’t lay out as expected on the front end.
  • Fix: Don’t dequeue global-styles. Instead, emit higher-specificity overrides for the parts that conflict with the theme’s own CSS.
BASE-C7 Base Theme Architecture OBSERVATION No documented helper for child themes to register block categories

In plain terms: The theme registers its own block categories (cd-general, cd-full-bleed, cd-templates) via block_categories_all. A child theme can use the same WP filter, but there’s no documented helper or canonical pattern, so projects often re-discover priorities and the array_unshift/array_merge dance.

  • Fix: Either expose a helper like cd_wp_base_register_block_category() or document the recommended priority and merge approach in CONTRIBUTING.md.
BASE-C8 Base Theme Architecture OBSERVATION Card-container backend loader has its has_block guard commented out

In plain terms: The function that loads the card-container helpers in admin has its “only load when needed” check disabled, so the helpers are always loaded for every admin request even on screens that don’t use the block. Probably a leftover from debugging.

  • Where: cd-wp-base/functions/block-functions.php:53–58 — the if ($post && has_block('cd/card-container', $post)) guard is commented out.
  • Fix: Decide whether the guard is correct (and re-enable it) or whether it was disabled intentionally (and remove the dead comments).
BASE-D1 Base Theme Missing features IMPROVE BUG Banner images use wp_get_attachment_image_url() with size large — no srcset, no high-DPI

In plain terms: Page banners load only the 1024-pixel version of the image, with no high-resolution alternates. On Retina/4K screens they look fuzzy. There’s no srcset, so browsers can’t pick a sharper variant.

  • Where: cd-wp-base/functions/templating-functions.php:178wp_get_attachment_image_url($banner_image, 'large')
  • Fix: Register dedicated 2:1 banner image sizes (e.g., cd-header-sm/md/lg), use wp_get_attachment_image() with a sizes attribute so WordPress emits srcset, and use the largest registered size as the base URL.
BASE-D2 Base Theme Missing features IMPROVE No automatic WebP conversion on upload

In plain terms: Modern best practice is to serve images as WebP by default for substantial file-size savings. The theme has no built-in conversion path; uploads stay in their original format.

  • Fix: Add an upload filter that uses imagewebp() (GD) or Imagick to convert PNG/JPG uploads to WebP at a configurable quality level. Expose cd_wp_base_webp_quality (default ~80) and cd_wp_base_webp_skip_post_types filters.
BASE-D3 Base Theme Missing features IMPROVE Card grids have no native “View All” button

In plain terms: A card grid (e.g., “Latest News” with three cards) usually wants a “View All” link to the full archive. The card-container block doesn’t expose one.

  • Fix: Add ACF fields to the card-container for URL, label, color, and position (top/bottom). Render conditionally in cd-wp-base/blocks/card-container/.
BASE-D4 Base Theme Missing features IMPROVE No card layout that inlines each post’s full content (“blog index” mode)

In plain terms: Cards always show short summaries. Some sites want a layout that inlines each post’s full body content on the listing page — the classic blog index pattern. No such layout exists.

  • Fix: Add a card-container layout option that walks each post’s blocks and renders relevant ones inline. Likely needs a helper to filter to article-body blocks (excluding navigation, breadcrumb, etc.).
BASE-D5 Base Theme Missing features IMPROVE No toggle to suppress card description text

In plain terms: Cards always show a description below each card. There’s no built-in way to hide it for a tighter, image-focused grid.

  • Fix: Add a card-container ACF toggle “Hide descriptions.” Suppress the summary in cd-wp-base/blocks/card-container/ output when set.
BASE-D6 Base Theme Missing features IMPROVE Custom-excerpt builder only walks core/paragraph blocks

In plain terms: The theme already overrides get_the_excerpt with a block-aware extractor — but the extractor only looks at paragraph blocks. List items, freeform/Classic content, and text inside nested group blocks are silently skipped.

  • Where: cd-wp-base/functions/templating-functions.php:619–654 (cd_wp_base_custom_excerpt, cd_wp_base_extract_paragraph_blocks).
  • Fix: Extend the extractor to also pick up core/list, core/list-item, core/freeform, and to recurse through nested group/columns blocks regardless of blockName.
BASE-D7 Base Theme Missing features IMPROVE Accordion blocks default to core/freeform (Classic) and require JS to open/close

In plain terms: The Accordion block ships with the legacy Classic editor as its default inner content type and uses jQuery to handle expand/collapse. Modern HTML <details>/<summary> would deliver the same UX with no JS, better accessibility, and better mobile behavior.

  • Where: cd-wp-base/blocks/accordion/accordion.php:5 — default inner block is core/freeform; allowed list is core/freeform + cd/accordion.
  • Fix: Switch the default and allowed list to standard editorial blocks (core/heading, core/paragraph, core/list, etc.), and let the existing <details class="classic"> markup carry the open/close behavior natively.
BASE-D8 Base Theme Missing features IMPROVE No native publication-date block (post-date with consistent formatting)

In plain terms: A small block that shows a post’s publication date with consistent formatting and an optional separator would be useful in single-post templates and could replace the inline <p>-as-date pattern that the post-type plugins currently use.

  • Fix: Add cd/post-publication-date as a small render-template block. Use a <time datetime> element for semantic correctness.
BASE-D9 Base Theme Missing features IMPROVE No native taxonomy-terms block to display all of a post’s terms as chips

In plain terms: A reusable block that renders a row of term-link chips for a post’s associated taxonomies (tags, categories, custom) would be a small but generally useful addition.

  • Fix: Add cd/post-taxonomy-terms with an ACF option to select which taxonomies to include.
BASE-D10 Base Theme Missing features IMPROVE Block-quote and panel styles only available via the legacy Classic block

In plain terms: The theme defines “Block Quote (offset)”, “Block Quote (impact)”, and a family of “Panel” styles — but only inside the TinyMCE Formats dropdown for the Classic block. They’re not available as block styles on the modern Paragraph block. Editors who’ve moved off Classic lose access.

  • Where: cd-wp-base/functions/editor-functions.php:167–184 (Block Quotes) and :307–353 (Panels).
  • Fix: Register equivalents as paragraph block styles. For semantic correctness, wrap the rendered <p> in <blockquote> / <div class="panel"> via render_block filters when the corresponding style class is present.
BASE-D11 Base Theme Missing features IMPROVE Order (menu_order) field is exposed only on Pages

In plain terms: WordPress’s Order field — used to manually sequence posts — appears in Quick Edit and the editor’s kebab (⋮) menu only on Pages. Every other public post type (news, events, people, custom types, etc.) is missing it, so editors can’t hand-order them without an extra plugin or per-site shim.

  • Fix: On init (priority 20, after plugin post types register), iterate get_post_types(['public' => true]) and call add_post_type_support($pt, 'page-attributes') for each, skipping attachment and page. This surfaces Order in the list-table Quick Edit row and in the block editor’s kebab menu for every public post type by default.
  • Optional polish: Suppress the “Page Attributes” sidebar panel for non-hierarchical types so the kebab menu is the sole Edit-screen access point — wp_add_inline_script('wp-edit-post', ...removeEditorPanel('page-attributes')...) on enqueue_block_editor_assets.
BASE-E1 Base Theme Tech debt OBSERVATION core/pattern appears in the allowed-blocks list with no documentation

In plain terms: The theme allows the WordPress core Pattern block in the editor, but doesn’t document why or how editors are expected to use it. Worth confirming whether it’s intentional or vestigial.

  • Where: cd-wp-base/functions/block-functions.php:81
BASE-E2 Base Theme Tech debt OBSERVATION Deactivating an ACF field group cleanly requires both acf_remove_local_field_group() AND a filter on acf/load_field_group

In plain terms: ACF stores field groups in two places — local JSON (read at boot) and the database (read for the admin UI). To fully suppress a group, both sources need to be intercepted. There’s no single “deactivate this group” helper.

  • Fix: A small helper (cd_wp_base_deactivate_acf_group($key)) that wires both mechanisms would reduce the risk of a project deactivating only one and being confused by partial behavior. Worth filing upstream with ACF Extended.
BASE-E3 Base Theme Tech debt OBSERVATION The number of !important overrides hints at unscoped parent-theme rules

In plain terms: A pattern of !important rules in projects built on this theme indicates that the theme’s own CSS is using too-broad selectors that downstream code must escalate against. Tightening the theme’s rule scoping would let downstream code drop those flags.

  • Fix: Audit base.css for body-level and bare-tag selectors (body, h2, input, img) and rescope them to specific structural classes.
BASE-E4 Base Theme Tech debt DEBT Heavy investment in TinyMCE / Classic-block customization vs. modern block styles

In plain terms: A large portion of editor-functions.php (TinyMCE button rows, format dropdown, table plugin, paste-transform suppression) is dedicated to making the legacy Classic block work well. The same investment hasn’t been made on equivalent modern-block features (paragraph styles, list styles, custom-format buttons).

  • Where: cd-wp-base/functions/editor-functions.php:33–476 (TinyMCE), vs. limited modern-block style registration.
  • Fix: Decide whether Classic remains a first-class authoring path or a deprecation path, and align the codebase with that decision. If deprecation, pair with BASE-C5 to remove core/freeform from the default allowed list.
NEWS-A1 cd-news Bugs BUG Imported news_publication_date is stored as Y-m-d instead of ACF’s required Ymd

In plain terms: The plugin imports each news article’s publication date and writes it to an ACF date_picker field. ACF’s date_picker expects the string 20260315, but the import writes 2026-03-15. The mismatch means ACF’s date formatting and queries don’t work correctly downstream.

  • Where: cd-news/functions/cu-news-cron.php:146,164$post_date = date('Y-m-d', $timestamp); … update_field('news_publication_date', $post_date, $post_id);
  • Note: The file does define a cd_news_normalize_date() helper (line 117) that returns Ymd, but it is never actually called.
  • Fix: Either pass cd_news_normalize_date($date_published) into update_field(), or change the field to display-format Y-m-d in its ACF JSON.
NEWS-A2 cd-news Bugs BUG Imports populate the “Page Links To” field with the external source URL, redirecting clicks off-site

In plain terms: On every import, the plugin sets the “Page Links To” field (whose job is to redirect a post’s permalink elsewhere) to the original external URL. Result: every imported news article’s permalink redirects users to the source site instead of showing the locally-imported version.

  • Where: cd-news/functions/cu-news-cron.php:167–172
  • Fix: Stop populating field_68dd3d5b8b325 on import. If a per-import opt-in is desired, gate it behind a setting on the plugin’s admin page.
NEWS-A3 cd-news Bugs BUG Single-news template renders the publication date as a non-semantic <p>

In plain terms: The publication date for a news article is the article’s most important piece of metadata, but the template renders it as a generic paragraph. A <time datetime> element (or a heading) would be more semantically meaningful and improves accessibility/SEO.

  • Where: cd-news/templates/blocks/single-news-fields/single-news-fields.php:23
  • Fix: Wrap the date in <time datetime="..."> with an ISO date attribute. Optionally expose an ACF option for the wrapper tag.
NEWS-A4 cd-news Bugs BUG Featured image uses a raw <img> tag instead of the_post_thumbnail()

In plain terms: The single-news template builds an <img> tag by hand, which means no srcset, no lazy loading, and no alt text. Using WordPress’s built-in helper would deliver all three and use the correct registered image size.

  • Where: cd-news/templates/blocks/single-news-fields/single-news-fields.php:30
  • Fix: Replace with the_post_thumbnail('large', ['class' => 'align-left']), or pick an appropriate registered size with a sizes attribute.
NEWS-A5 cd-news Bugs BUG Single-news template uses a copy-paste variable name $event_today for the news fallback date

In plain terms: The variable that holds today’s date as a placeholder for unsaved drafts is named $event_today — clearly copy-pasted from the events plugin. Cosmetic, but a sign that the template was forked rather than refactored.

  • Where: cd-news/templates/blocks/single-news-fields/single-news-fields.php:13,21
  • Fix: Rename to $news_today_fallback (or similar) and consider using wp_date() so the timestamp respects the WordPress timezone instead of the server’s.
NEWS-B1 cd-news Editor & CSS OBSERVATION Featured-image visibility logic treats null as “Yes”

In plain terms: The flag controlling whether the featured image renders defaults to “show” when the field is unset (is_null). It works in practice but is surprising — an editor reading the JSON wouldn’t expect “not set” to mean “yes, show.”

  • Where: cd-news/templates/blocks/single-news-fields/single-news-fields.php:15$has_news_image = ($news_featured_image === 'Yes' || is_null($news_featured_image));
  • Fix: Set the ACF field’s default value explicitly to 'Yes', then the is_null branch becomes unnecessary.
NEWS-D1 cd-news Missing features IMPROVE Single-news InnerBlocks allowed list is too restrictive (heading / paragraph / buttons only)

In plain terms: The article body inside a single-news block can only contain headings, paragraphs, and buttons. Editors can’t add lists, images, embeds, quotes, columns, or any of the other ordinary blocks one would expect inside an article. That makes the plugin unusable for any site with non-trivial article content.

  • Where: cd-news/templates/blocks/single-news-fields/single-news-fields.php:34
  • Fix: Either omit allowedBlocks entirely (defaults to all) or expand to a documented full editorial set: core/heading, core/paragraph, core/list, core/image, core/embed, core/quote, core/buttons, core/columns, core/separator, core/table, core/file.
NEWS-E1 cd-news Tech debt DEBT Production cron writes success-path messages to the PHP error log

In plain terms: The import cron logs “Successfully imported news: …” to the error log on every successful import. Error logs should be reserved for errors. On a busy site this floods the log with noise that masks real issues.

  • Where: cd-news/functions/cu-news-cron.php:177
  • Fix: Remove or gate behind a WP_DEBUG_LOG check.
NEWS-E2 cd-news Tech debt DEBT Helper cd_news_normalize_date() defined but never called

In plain terms: A helper that would normalize date strings to ACF’s Ymd format exists in the file but isn’t used anywhere. This is dead code AND the missing call site is the cause of NEWS-A1.

  • Where: cd-news/functions/cu-news-cron.php:117–121
  • Fix: Either delete the unused function, or wire it into the news_publication_date update (which also resolves NEWS-A1).
NEWS-E3 cd-news Tech debt OBSERVATION Remote JSON fetched with file_get_contents() — no timeout, retries, or User-Agent

In plain terms: The cron pulls remote feeds with PHP’s naive file_get_contents(). If the remote endpoint is slow, the cron blocks indefinitely. There’s no retry, no User-Agent, no timeout.

  • Where: cd-news/functions/cu-news-cron.php:33,48
  • Fix: Use wp_remote_get() with an explicit timeout (10–15 sec) and proper error handling. Bonus: it goes through WordPress’s HTTP API filters.
EVENTS-A1 cd-events Bugs BUG Single-event template uses esc_html() on URL href attributes (XSS / data-corruption risk)

In plain terms: When the single-event template builds the “Website” and “Zoom Link” output, it passes the URL through esc_html() for the href attribute. esc_html() escapes HTML entities for displaying text — it doesn’t validate URL schemes, so it permits javascript: and other risky schemes that esc_url() would strip. This is the wrong escaper for the role.

  • Where: cd-events/templates/blocks/single-event-fields/single-event-fields.php:106,115,124
  • Fix: Use esc_url($event_website) for the href attribute and esc_html() only for the visible link text. Same applies to the Zoom and email anchors.
EVENTS-A2 cd-events Bugs BUG Imports populate the “Page Links To” field with the Localist URL

In plain terms: Same root cause as NEWS-A2: every imported event has its “Page Links To” redirect set to the source Localist URL, so clicking an event in your local site bounces visitors out to Localist instead of showing the locally-imported page.

  • Where: cd-events/functions/localist-cron.php:201–206
  • Fix: Stop populating field_68dd3d5b8b325 on import. Treat redirect-to-source as opt-in.
EVENTS-A3 cd-events Bugs BUG Single-event template uses raw date('F j, Y') ignoring WordPress’s configured timezone

In plain terms: The fallback “today’s date” placeholder in the editor uses PHP’s native date() function, which uses the server timezone — not the timezone configured in WordPress’s admin Settings. Sites whose server timezone differs from their site timezone will see the wrong date.

  • Where: cd-events/templates/blocks/single-event-fields/single-event-fields.php:17
  • Fix: Replace with wp_date(get_option('date_format')).
EVENTS-A4 cd-events Bugs BUG Card subtitle date format ignores the site’s date format setting

In plain terms: When an event card’s subtitle resolves to the event date, the format is hardcoded to F j, Y (e.g., “March 15, 2026”). It ignores whatever the site has set in Settings → General.

  • Where: cd-events/templates/blocks/event-cards/event-cards-functions.php:113
  • Fix: Replace date('F j, Y', $timestamp) with wp_date(get_option('date_format'), $timestamp).
EVENTS-C1 cd-events Architecture OBSERVATION DEBT Imported event meta is saved field-by-field after wp_insert_post() returns

In plain terms: Each imported event runs update_field() seven times after the post has already been created and wp_insert_post has returned. Code that wants to react to a new event (via save_post or wp_insert_post hooks) can’t see the fields yet — they don’t exist when the “post created” signal fires.

  • Where: cd-events/functions/localist-cron.php:191–198
  • Fix: Pass meta atomically at insert time using wp_insert_post(['meta_input' => [...]]), the way the news plugin does for cu_news_id. Reduces hook ordering surprises.
EVENTS-D1 cd-events Missing features IMPROVE No visual indicator (e.g., calendar icon) distinguishes an event page from any other single-post page

In plain terms: Event pages look visually identical to news pages and ordinary posts. There’s nothing at a glance that signals “this is an event with a date and location.”

  • Where: cd-events/templates/blocks/single-event-fields/single-event-fields.php
  • Fix: Add a calendar icon next to the date heading by default; expose an ACF toggle to hide it.
EVENTS-D2 cd-events Missing features IMPROVE Single-event InnerBlocks allowed list is too restrictive (heading / paragraph / buttons only)

In plain terms: Same constraint as NEWS-D1: editors can only place headings, paragraphs, and buttons inside an event’s description. Lists, images, embeds, and quotes are blocked.

  • Where: cd-events/templates/blocks/single-event-fields/single-event-fields.php:52
  • Fix: Expand to the documented full editorial set (or omit allowedBlocks to default to all).
EVENTS-D3 cd-events Missing features IMPROVE the_post_thumbnail('full') serves the original-size image — no responsive variants

In plain terms: The single-event template renders the featured image at the largest registered size (full), with no sizes attribute. Browsers always download the largest variant, even on mobile.

  • Where: cd-events/templates/blocks/single-event-fields/single-event-fields.php:49
  • Fix: Use a registered intermediate size (e.g., large) with an explicit sizes attribute, or pick by context.
EVENTS-D4 cd-events Missing features IMPROVE Hardcoded English labels in single-event template not run through __()

In plain terms: The labels “Date:”, “Time:”, “Location:”, “Room:”, “Website:”, “Zoom Link:”, “Contact Email:”, and “Details:” are output as plain English literals. They’re not localizable and not customizable by the project.

  • Where: cd-events/templates/blocks/single-event-fields/single-event-fields.php:58–128
  • Fix: Wrap each in __('Date:', 'cd-events'), etc.; consider exposing them as filterable strings.
EVENTS-D5 cd-events Missing features IMPROVE Hardcoded English “Events” heading in the registered archive template

In plain terms: The archive template registered for the events post type contains a hardcoded <h1>Events</h1>. It can’t be customized without forking the template, and it’s not localizable.

  • Where: cd-events/functions/templating-functions.php:84
  • Fix: Drive the heading from the registered post-type label, or use the cd/title block which already handles archive titles (after BASE-A3 is fixed).
EVENTS-D6 cd-events Missing features IMPROVE Registered single-event and archive-event templates always use sidebar-left with no flexibility

In plain terms: The archive and single templates registered by the plugin force a left-sidebar layout. A site that uses no sidebar (or right sidebar) has to fork the template entirely.

  • Where: cd-events/functions/templating-functions.php:53–98field_68a35548a985c hardcoded to sidebar-left.
  • Fix: Either expose layout choice as a plugin setting, or omit the layout attribute and let the theme’s default field handling pick.
EVENTS-E1 cd-events Tech debt DEBT Production cron writes informational and success-path messages to the PHP error log

In plain terms: The cron logs “Importing N events after filtering,” “No events URL configured,” “Successfully imported event,” etc. to the error log. Most of these are not errors. They flood the log on busy sites.

  • Where: cd-events/functions/localist-cron.php:37,45,64,69,72,211,215
  • Fix: Remove or gate behind WP_DEBUG_LOG. Admin-visible status reporting belongs on the plugin’s admin page, not in the error log.
EVENTS-E2 cd-events Tech debt OBSERVATION Remote JSON fetched with file_get_contents() — no timeout, retries, or User-Agent

In plain terms: Same as NEWS-E3 but for the events plugin: file_get_contents() on a remote URL without a timeout means a slow Localist endpoint can stall the cron.

  • Where: cd-events/functions/localist-cron.php:32
  • Fix: Use wp_remote_get() with an explicit timeout.
PEOPLE-A1 cd-people Bugs BUG Single-person preview placeholder emits invalid HTML (<a><p>…</p></a> with no href)

In plain terms: When the editor preview shows a placeholder for the contact section, it emits an anchor tag without an href wrapping a paragraph element. Anchors without href aren’t links; the markup also nests a block element inside an inline anchor in a way that some browsers reflow oddly.

  • Where: cd-people/templates/blocks/single-person-fields/single-person-fields.php:68,93
  • Fix: Replace the <a><p>example@email.com</p></a> with a plain <p><a href="mailto:example@email.com">example@email.com</a></p>.
PEOPLE-A2 cd-people Bugs BUG Single-person template emits an inline <h1>, risking a duplicate page h1

In plain terms: The single-person fields block renders the post title as an <h1> inside its own markup. If a project also uses the theme’s page-title block (which also emits an <h1>), the page ends up with two top-level headings — bad for accessibility and SEO.

  • Where: cd-people/templates/blocks/single-person-fields/single-person-fields.php:41
  • Fix: Either omit the in-block heading and let the title block carry the h1, or expose an ACF setting for “include heading.” The plugin’s registered single template (templating-functions.php:53–70) already includes the cd/title block, so the inline h1 is structurally redundant in default usage.
PEOPLE-A3 cd-people Bugs BUG Featured-image rendered with raw <img> — no srcset, no lazy-load, alt is the post title

In plain terms: The single-person template builds an <img> tag by hand. No srcset, no loading="lazy", and the alt attribute defaults to the post title (which is a person’s name) — the WCAG-recommended pattern is empty alt for a decorative portrait next to the name.

  • Where: cd-people/templates/blocks/single-person-fields/single-person-fields.php:82
  • Fix: Replace with the_post_thumbnail('large', ['alt' => '']) or expose an ACF field for custom alt text.
PEOPLE-C1 cd-people Architecture BUG cd_person_sync_block_meta() deletes meta whenever the single-person block isn’t present in post content

In plain terms: On every save, the plugin walks the block tree looking for the single-person-fields block. For any field not found there, it calls delete_post_meta(). This means: importing a person from an external feed (which doesn’t put the block in the content) will silently wipe out every meta field the import just wrote. Same risk for any project that wants to manage these fields via custom blocks or REST.

  • Where: cd-people/functions/save-post-meta.php:42–48
  • Fix: Only sync when the block is actually present (early-return if !has_block('cd/single-person-fields', $post)); or change “not found” semantics from delete to leave-alone.
PEOPLE-C2 cd-people Architecture DEBT Variable-variable assignment (${$field} = get_field($field)) for dynamic field loading

In plain terms: The template uses PHP’s “variable variable” syntax to populate a dozen named variables from a list of field names. It works, but it’s indirection that hides the variable names from static analysis tools (linters, IDE jump-to-definition, autocomplete) and obscures intent.

  • Where: cd-people/templates/blocks/single-person-fields/single-person-fields.php:20–22
  • Fix: Replace with explicit assignments: $person_last_name = get_field('person_last_name'); etc. The verbosity is worth the tooling support.
PEOPLE-C3 cd-people Architecture OBSERVATION No admin/settings page for the plugin (cd-news and cd-events both have one)

In plain terms: Both sibling plugins (cd-news, cd-events) ship an admin/ directory with a settings page. cd-people has none. If the plugin ever adds an import path or per-site configuration, this gap will become a dependency.

  • Where: compare cd-people/ structure with cd-events/admin/cd-events-admin.php and cd-news/admin/cd-news-admin.php.
  • Fix: Decide whether the parity is worth filling now (e.g., to expose card-display settings) or whether the plugin is intentionally minimal.
PEOPLE-D1 cd-people Missing features IMPROVE Single-person InnerBlocks allowed list is too restrictive (heading / paragraph / buttons only)

In plain terms: Same constraint as NEWS-D1 / EVENTS-D2: editors can only place headings, paragraphs, and buttons inside a person’s bio. No lists, embeds, images, quotes.

  • Where: cd-people/templates/blocks/single-person-fields/single-person-fields.php:75
  • Fix: Expand to a documented full editorial set, or omit allowedBlocks.
PEOPLE-D2 cd-people Missing features IMPROVE Hardcoded English labels (“Contact”, “Title”, “Department”) not run through __()

In plain terms: The labels rendered by the contact-info helper and the placeholder text in preview mode are hardcoded English literals.

  • Where: cd-people/functions/templating-functions.php:138 (“Contact”), single-person-fields.php:51 (“Title”), single-person-fields.php:57 (“Department”).
  • Fix: Wrap each in __('Contact', 'cd-people'), etc.
PEOPLE-D3 cd-people Missing features OBSERVATION Contact-info helper inlines style="white-space: pre-wrap;" on the address paragraph

In plain terms: The contact-info renderer mixes inline styling with structural HTML to preserve line breaks in the address. Tiny detail, but inline style attributes bypass the theme’s CSS and can’t be themed.

  • Where: cd-people/functions/templating-functions.php:151
  • Fix: Add a class (e.g., address-block) and put the rule in a stylesheet, or use nl2br() on the escaped address.
PEOPLE-E1 cd-people Tech debt OBSERVATION $has_featured_image defaults to true when the field is unset

In plain terms: Same surprise as NEWS-B1: $has_featured_image = ($person_featured_image === 'Yes' || is_null($person_featured_image)); means the absence of a value is treated as “Yes.”

  • Where: cd-people/templates/blocks/single-person-fields/single-person-fields.php:28
  • Fix: Set the ACF field’s default to 'Yes' and drop the is_null branch.
CROSS-A1 Cross-plugin Bugs BUG Both cd-news and cd-events populate “Page Links To” on import — permalinks redirect off-site by default

In plain terms: Listed individually as NEWS-A2 and EVENTS-A2, but worth flagging at the cross-plugin level: the “import populates Page Links To with the source URL” pattern is shared between the two plugins. The unified fix is to stop populating that field on import in both, and route any opt-in redirect-to-source behavior through the same setting.

  • Where: cd-news/functions/cu-news-cron.php:167–172 and cd-events/functions/localist-cron.php:201–206 — both reference the same field key (field_68dd3d5b8b325).
  • Fix: Remove the call from both. If a redirect-to-source path is required, consolidate it as a per-plugin admin setting and apply on demand rather than on import.
CROSS-C1 Cross-plugin Architecture OBSERVATION Each plugin registers its own archive-X block template, which collides when the active theme provides one

In plain terms: All three plugins register their own archive templates via register_block_template. If the active theme also provides an archive template for the same post type, both register and the result is unpredictable. There’s no opt-out filter.

  • Where: cd-events/functions/templating-functions.php:42, cd-news/functions/templating-functions.php, cd-people/functions/templating-functions.php:42.
  • Fix: Each plugin should expose an opt-out filter (e.g., cd_events_register_archive_template, default true), OR check whether the active theme provides one before registering.
CROSS-C2 Cross-plugin Architecture OBSERVATION Each plugin ships an Archive Settings “Cards” ACF group that may be unused, polluting the admin page

In plain terms: All three plugins register their own ACF field groups against the theme’s Archive Settings options page. If a project doesn’t use a given plugin’s archive cards block, that field group still loads and clutters the Archive Settings page with fields that have no effect.

  • Where: Each plugin’s custom-fields/*-cards.json field group, registered in the plugin’s acf-config.php.
  • Fix: Gate registration on whether the plugin’s cards block is actually used on the site (or expose a per-plugin filter to opt out). Alternatively, register the groups conditionally on the post type being public + visited.
CROSS-D1 Cross-plugin Missing features IMPROVE Single-fields blocks share a too-restrictive InnerBlocks allowlist (heading / paragraph / buttons only)

In plain terms: NEWS-D1, EVENTS-D2, and PEOPLE-D1 are the same restriction repeated three times. Worth fixing as a coordinated change across the three plugins so the allowed editorial blocks list is identical and documented in one place.

  • Fix: Define a shared list of editorial blocks (likely in the base theme as cd_wp_base_editorial_inner_blocks()), and have each plugin’s block reference that list. This also creates a single point of change when the editorial guidelines evolve.
CROSS-E1 Cross-plugin Tech debt DEBT Substantial duplication between cu-news-cron.php and localist-cron.php

In plain terms: The two cron files duplicate the same logic: schedule an hourly job, fetch a remote JSON, dedupe by ID, filter, loop, wp_insert_post, then wrap_imported_* the content. About 70–80% of the code is identical. Bug fixes (NEWS-A1, EVENTS-A1, etc.) have to be made twice and can drift.

  • Where: cd-news/functions/cu-news-cron.php and cd-events/functions/localist-cron.php.
  • Fix: Extract a shared importer base (in cd-wp-base or a common library) that handles the boilerplate — scheduling, dedupe, fetch, retry, error reporting — and lets each plugin pass in just the per-feed mapping logic.
CROSS-E2 Cross-plugin Tech debt DEBT No coordinated story for “import wins” vs “author edits win” on imported posts

In plain terms: The three plugins (most directly cd-people via cd_person_sync_block_meta, see PEOPLE-C1) take a stance that the block’s data is canonical and any meta not present in the block should be deleted. That’s plausible for sync, but combined with imports that bypass the block (cd-news, cd-events) it creates conflicting behaviors. There’s no documented contract.

  • Fix: Pick one rule (block-is-canonical OR meta-is-canonical) and apply it consistently across the three plugins. Document the trade-off. If both behaviors are needed, expose them as opt-in modes.