Skip to content
Historical Feature1 min read

Hello world!

Welcome to the Beto foundation journal — a starter post you can edit or replace. This deck sits under the title in the Single Post hero.
Historical Feature

Welcome to WordPress. This is your first post. Edit or delete it, then start writing!

Beto — Build Guidelines & Standards

This file is the single source of truth for how New Urban Media builds and maintains sites on the Beto foundation theme.

It is read by two audiences:

  1. AI agents (Cursor, Claude, Codex, and any other platform). This file lives at the theme root as AGENTS.md so it is auto-discovered. Point any agent here first, before building.
  2. The NUM team, via the backend at Beto → Guidelines in the WordPress admin, which renders this exact file read-only.

To change the guidelines, edit this file (wp-content/themes/beto/AGENTS.md). The backend always reflects whatever is in this file — there is no second copy to keep in sync.


The One Standard Above All: Build for Manageability by Non-Coders

Every site we build must be fully manageable by a non-coding client or a maintenance teammate. This is not one guideline among many — it is the reason all the other guidelines exist. Read this section first, apply it always, and never let it get buried under technical detail.

The single test for every build decision:

Can the next person who opens this site — a non-coding client, or a teammate who has never seen it — find what they need and change it safely, in the WordPress/Elementor UI, without touching code?

If the answer is no, the work is built wrong — no matter how good it looks on the front end, and no matter how clever the implementation is.

What “manageable by non-coders” concretely requires

  • Everything a client would reasonably want to change is editable in the UI. Text, images, links, colors, buttons, section content, menus — all editable through Elementor / WordPress admin, never locked inside code, custom HTML, or a developer-only file.
  • Content is where a normal person would look for it. Editable content lives in the page/Elementor editor or in clearly-labeled JetEngine fields — not hidden in shortcodes, template PHP, or CSS.
  • No hidden dependencies or fragile tricks. A client editing a heading, swapping an image, or duplicating a section must not be able to silently break layout or functionality.
  • Labels are human. Every custom field, CPT, template, menu, and setting has an intuitive label and (where applicable) a description, so a non-technical editor understands what it does.
  • If it can be built with a native control, it is. Reaching for code is the exception, always justified, and always documented — because code is the thing a non-coder cannot manage.

When in doubt, optimize for the editor’s clarity and safety over the developer’s convenience. A slightly longer build that a client can manage forever beats a fast build only we can touch.


Always-On Standards (never traded off)

Manageability is the standard above all. These three sit directly beneath it and are equally non-negotiable — every agent (Cursor, Claude Code, or any other) evaluates them on every decision, not as a cleanup pass at the end. The goal is secure, stable, well-organized WordPress environments that are future-proof and manageable for both our team and our clients.

1. Security & Stability

  • Least privilege, always. Roles and capabilities grant the minimum needed. Security is owned by code (num-core), never by a plugin UI that can be removed. See Client Access, Roles & Handoff below.
  • Never expose secrets. No credentials, API tokens, or license keys in code or the repo — they live in server config or GitHub Secrets. Per-site config is never committed.
  • No fragile hacks. Prefer supported hooks, filters, and APIs over patching core/plugin files. Everything must survive a plugin/theme/core update.
  • Change safely on live sites. Audit → report → plan → approve → remediate. Back up before consequential changes, announce before any site-modifying action, and get explicit approval before anything irreversible.
  • Deterministic, reversible, documented. Every module ships escape-hatch filters and a header explaining intent. Nothing silently mutates data on activation without a version gate.
  • Keep the surface small. Fewer plugins, fewer moving parts, one labeled home for custom code. Every dependency is a liability to justify.
  • PHP 8.2 floor, forward-compatible. Never force a higher floor; verify with the bundled .phpcs.xml.

2. Accessibility & Mobile Responsiveness — at the forefront

Accessibility and mobile are not a QA pass at the end; they shape each decision as it is made.

  • Accessibility (WCAG 2.1 AA as the working target). Semantic HTML and landmarks; every control keyboard-reachable with a visible focus state; sufficient color contrast; real labels and alt text; ARIA only to fill genuine gaps, never as a band-aid; form fields with associated labels and clear error text; never convey meaning by color alone; honor reduced-motion.
  • Mobile responsiveness. Design mobile deliberately, not as a shrunk desktop. Larger mobile type (including nav); full-width layouts with ~20px side padding (10px nested); full-viewport mobile nav; button labels ≤3–4 words; smart wrapping (balance for headings, pretty for body). Verify at every breakpoint by measurement.
  • The test: if a keyboard-only user, a screen-reader user, or a phone user can’t complete the task comfortably, it isn’t done.

Client Access, Roles & Handoff (the security model)

Baked into num-core, independent of Admin Menu Editor (AME is cosmetic only — remove it and nothing sensitive becomes exposed).

Roles

  • Owner Admin (owner_admin) — the client’s “admin”: full content control (posts, pages, documents, media), page deletion, clamped user management (can add Editor-and-below, never Owner Admin / Administrator), and Tools → Export / Export Personal Data. No settings, no plugins, no design editing by default (Elementor design is granted per client on request). Defined in roles/roles.php.
  • Content Manager (content_manager) — a rename-friendly Editor clone for third parties, so granting access never alters the base Editor role.

Enforcement (code, in roles/role-guards.php): editable_roles ceiling; map_meta_cap blocks editing/deleting/promoting anyone at or above Owner Admin even by direct URL; privileged users are hidden from the Users list.

Notices (admin-experience/notices-policy.php): standard admin notices → Administrators only; NUM operational notices → nu_manage_options (agency) only. Clients never see dev/security notices.

Dashboard (admin-experience/dashboard-widgets.php): deny-by-default for Owner Admin & below; allowlist keeps At-a-Glance / Activity / Quick-Draft + Gravity Forms + Rank Math; hides Site Health, Events & News, and surprise plugin widgets.

Admin menu (3-zone): Client zone / Limited zone / Agency zone — structure and security in code; cosmetics in AME.

Handoff protocol: deactivate agency-paid licenses (Elementor Pro, Crocoblock/JetEngine, Gravity Forms, Admin Columns Pro, AME Pro, Rank Math Pro); delete num-team; run num-core “handoff mode” to neutralize agency-only bits; remove central-hub API keys; promote the client’s Owner Admin → Administrator; remove agency accounts; re-enable indexing. num-core = client keeps; num-team = agency removes (one-click, DB-clean).

Documents source of truth: global standards (this guide, the access standard, the handoff protocol) live versioned in GitHub; per-client operational data (domain registrar, DNS host, contacts, IT manager) lives in Airtable — off-site by design, reachable even when a client’s site is down.


Front-end build standards (how manageability is achieved in practice)

These are the concrete rules that deliver the manageability standard above. They are non-negotiable.

  • Elementor-native styling over custom CSS. If a result can be achieved with an Elementor widget setting, it must be. Custom CSS that duplicates or overrides a widget control is a defect — it moves control out of the client’s reach. Custom CSS is allowed only for behavior no widget setting can produce, and only in the single documented location for it.
  • Right widget for the job. Use the semantically correct widget — a Button widget for a button, a navigation widget for navigation. Never a styled Heading dressed as a button. The correct widget is the one a client will recognize and be able to edit.
  • Global tokens over local overrides. Use global colors and fonts so a brand change happens in one place. Do not hardcode values a global token already defines.
  • Native widgets over custom code. Reach for code only when no native control or configurable widget setting can do the job. Custom code is the least manageable option and the last resort.
  • JetEngine-native for dynamic content. Custom post types, fields, and listings are defined and managed in JetEngine, not hand-coded. Every dynamic/composite block (breadcrumb, byline, related posts, search results, document meta, sidebar lists) is built with JetEngine widgets, Elementor Pro widgets (including theme/dynamic tags), or another real Elementor widget with native panel controls. HTML widgets carrying shortcodes are never an accepted pattern for dynamic content. If something truly cannot be built that way, stop and get explicit approval before taking an alternate path.
  • Spacing is explicit and intentional. Set every container’s padding explicitly, including 0 where none is wanted. Never rely on inherited/default padding (Elementor’s invisible inherited defaults cause doubled, missing, or phantom spacing). Match spacing to the design system and verify by measurement, not by glancing at a render.
  • Build for mobile deliberately. Mobile is not an afterthought to desktop. Increase font sizes on mobile across the whole site including navigation; use full mobile screen width with ~20px side padding (10px for nested containers) and copy that fills the space; expand the mobile nav to full viewport height; keep button labels to 3 words ideal / 4 maximum; and apply smart text wrapping (balance for headings/subheadings, pretty for paragraphs/lists) to prevent orphans and awkward breaks. Verify at every breakpoint.
  • Do not publish email addresses. No email in copy, as a mailto:, or in obfuscated form. Route contact through the form with a subject/routing parameter (e.g. a department-labeled link to the form). Email goes on a site only with explicit approval.

Manageability anti-patterns (real violations to never repeat)

These are concrete failures found in an audit of a real prior build. Each one breaks the manageability standard. If you catch yourself about to do any of these, stop — there is a manageable way to build it.

  • Never hardcode client-facing copy in a theme PHP file. Headlines, body paragraphs, eyebrows, button labels, list items — if a visitor reads it, a client must be able to edit it in the UI. (Audit found an entire homepage hero — headline, lede paragraph, address — plus section headings like “Visit the Museum” and “Featured Exhibition” written as literal HTML inside a .php file. The client cannot change a single word without a developer.)
  • Never hardcode operational facts. Hours, admission prices, addresses, phone numbers, and similar details change over time and must live in editable fields/settings, never in code. (Audit found “9:00 AM – 5:00 PM”, “$18 Adults”, and the street address hardcoded in PHP — so a price change requires editing code.)
  • Never duplicate CPT/database content as hardcoded text. If the content exists as a post, CPT entry, or field, render it dynamically from that source — never retype it into the theme. Duplicated content silently goes stale: the client edits the real record and the page never updates. (Audit found exhibition titles like “A Culture of Resistance” and “The Year They Walked” hardcoded on the homepage while those exact exhibitions already existed as editable exhibition CPT posts. The homepage ignores the client’s edits.)
  • Never render page sections through theme shortcodes injected into the builder. Building a whole section (hero, events grid, exhibition list) as add_shortcode() output and dropping it into an Elementor HTML/shortcode widget is forbidden. In the editor the client sees one opaque block, not editable content. Build sections with real Elementor widgets and JetEngine listings. (Audit found [ncrm_site_home], [ncrm_site_header], — entire pages rendered as PHP shortcodes, ~96 hardcoded heading/paragraph/list tags across the section modules.)
  • Never hardcode URLs the client controls. Social profile links, shop links, map links, embedded video IDs, and phone/tel: values belong in editable settings or widget controls — not baked into PHP. (Audit found Instagram, Facebook, YouTube, TikTok, shop, Google Maps, a specific YouTube video ID, and a tel: number all hardcoded across the section modules.)
  • Never use inline style="..." attributes for styling. Styling belongs in Elementor widget controls (or, only when unavoidable, the single documented CSS location). (Audit found 27 inline style="..." attributes in the section modules.)
  • Never load third-party assets from external CDNs in the theme. External CDN references (fonts, map libraries, scripts) are a maintainability, privacy, and reliability risk — host or enqueue them properly, or use a native widget. (Audit found Google Fonts and a Leaflet map library loaded from unpkg.com directly in theme PHP.)

The common thread: every one of these moved control out of the client’s hands and into code. The correct version of each keeps the content and settings in the WordPress/Elementor UI, where a non-coder can manage them.


Custom code: where it lives (the theme-vs-plugin rule)

The dividing line — decided independent of any specific theme:

  • Presentation and site chrome → the theme (Beto). Templates, enqueues, per-context styling, login branding, block/editor styling. This is supposed to change when a site is re-themed.
  • Data, security, admin, and content-embedded functionality → the num-core mu-plugin. Custom post types/fields (via JetEngine), content shortcodes, security hardening, admin/permission lockdown, REST bridges, SVG governance. This must survive a theme switch. If it would break when the theme changes, it does not belong in the theme.

Custom code lives in one labeled place, never scattered. Keep the surface small.


Beto theme conventions (foundation)

  • Standalone theme, no parent. Beto owns its full surface; nothing auto-updates underneath it.
  • Builder-agnostic. Renders cleanly with no page builder, is first-class with Elementor (hands off via theme-builder locations), and gives the block editor a clean, scoped baseline.
  • Namespace is permanent and brand-neutral. All functions, hooks, handles, constants, option keys, and meta keys use the nu_ / nu- / NU_ prefix. The text domain is nu. These never change on rebrand.
  • Display name is the only thing that rebrands. To rebrand a Beto site: change Theme Name/Description in style.css and set the Site Title + logo in Settings. No code changes.
  • Never hardcode brand names or paths. Displayed names come from WordPress (bloginfo('name'), custom logo). All asset paths use get_theme_file_uri() etc.
  • Block styling is scoped to native content (.entry-content), never global, so it never fights Elementor.
  • The backend block editor is a clean, neutral canvas, deliberately decoupled from front-end styles.
  • PHP floor is 8.2. Beto must never be the component that forces a higher PHP version. Verified with the bundled .phpcs.xml (PHPCompatibility, testVersion 8.2-). Production may run newer PHP (8.5), but code must stay 8.2-compatible.

Blueprint defaults (unless a project overrides)

  • Container width: 1440px
  • Container padding: 0 by default
  • Element gaps: 0 by default
  • Breakpoints: Mobile ≤767px, Tablet ≤1024px, Laptop ≤1366px, Desktop >1366px

How we operate on a live site

  • Audit, report, plan, approve, remediate. Inspect and report freely. Announce before any site-modifying action. Get explicit approval before anything consequential or irreversible. Never assume on destructive changes.
  • Keep a restore point before changing anything, and self-recover if a change goes wrong. Confirm a platform backup only before genuinely risky work.
  • Reports go to private/reports/, outside the public web root, named [YYYY-MM-DD]-[report-type]-[scope].md. Never write reports inside public/.

Beto foundation theme — New Urban Media. Edit this file to update the standards everywhere. The manageability standard at the top governs everything below it.

Share this post