Skip to content

feat: Auto-generated settings schema for GUI/TUI parity#803

Merged
jjroelofs merged 3 commits into8.xfrom
jur/8.x/802-settings-schema-auto-generation
Apr 6, 2026
Merged

feat: Auto-generated settings schema for GUI/TUI parity#803
jjroelofs merged 3 commits into8.xfrom
jur/8.x/802-settings-schema-auto-generation

Conversation

@jjroelofs
Copy link
Copy Markdown
Collaborator

Linked issues

Solution

Adds #ai_description to all 163 theme settings form elements and auto-generates data/settings-schema.json via the Grunt build process. This enables dxpr_theme_helper's dxt:* Drush commands to read the settings schema from the installed theme at runtime — no manual cross-repo sync.

How it works

  1. Developer edits features/*-theme-settings.inc — adds form element with #ai_description
  2. The running DEV_WATCH=true docker compose up dev auto-regenerates data/settings-schema.json
  3. Commit both files in the same PR to dxpr_theme
  4. dxpr_theme_helper reads the schema from the theme directory at runtime — zero changes needed

Files

  • 8 inc files: #ai_description added to all form elements (custom Form API property, invisible to GUI)
  • scripts/generate-settings-schema.js: Node.js parser extracting metadata from PHP form arrays
  • data/settings-schema.json: Auto-generated schema (163 settings)
  • Gruntfile.js: generate-schema task + watch on features/**/*-theme-settings.inc

Checklist

  • I have read the CONTRIBUTING.md document.
  • My commit messages follow the contributing standards and style of this project.
  • My code follows the coding standards and style of this project.
  • New feature (non-breaking change which adds functionality)
  • Requires a change to end-user documentation.
  • Requires a change to developer documentation.
  • All new and existing tests passed.

Jurriaan Roelofs added 3 commits April 4, 2026 09:37
Add #ai_description to all 163 theme settings form elements and
auto-generate data/settings-schema.json via the Grunt build.

- #ai_description is a custom Form API property (invisible to GUI)
  containing design-oriented guidance for AI agents
- scripts/generate-settings-schema.js parses all 8 *-theme-settings.inc
  files and extracts type, title, description, ai_description, options,
  ranges, and defaults into JSON
- Gruntfile.js gets a 'generate-schema' task that watches
  features/**/*-theme-settings.inc for changes
- dxpr_theme_helper reads the schema from this theme at runtime,
  eliminating cross-repo sync

Developer workflow: edit the inc file, the watch process regenerates
the schema, commit both in the same PR. No parallel PR needed in
dxpr_theme_helper.

Closes #802
The PHP form API uses generic 'textfield' and 'select' types for color
pickers and font selectors. The parser now detects these by key naming
conventions (_custom suffix for color, _font_face suffix for font,
known color select keys for theme_color) matching the hand-crafted
schema types that the validator relies on.
@jjroelofs
Copy link
Copy Markdown
Collaborator Author

Field testing note: dxt commands + theme schema

The dxt:* commands (from dxpr_theme_helper PR #47) were tested against this theme's settings schema while recreating the bricksbuilder.io homepage. A few schema-related observations:

ai_description quality

The ai_description fields on settings were very helpful for AI-driven configuration. The font settings descriptions with concrete examples (e.g., "0Roboto:400 (versatile), 0Open+Sans:400 (friendly)") made it possible to set fonts correctly without documentation lookups.

Suggestions for schema improvements

  1. h1_font_size — The ai_description should clarify this only affects the page title region, not in-content H1 elements. Currently says "H1 Font Size (Page Title)" as title, but the ai_description doesn't emphasize this scope limitation.

  2. page_title_height — Min 50 means the region can never be fully hidden. Consider allowing 0, or add a boolean page_title_region_hidden setting that removes the region entirely.

  3. full_width_containers + full_width_content_types — These are interdependent but their ai_description fields don't cross-reference each other. An AI agent enabling full-width for landing pages won't know it also needs to enable cnt_content in full_width_containers.

}

// Skip loop-generated keys (color_palette_ prefix, etc.)
if (key.startsWith('color_palette_') || key.startsWith('local_')) {
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Skipping loop-generated color_palette_* keys here means the runtime schema no longer describes the most important color controls. dxpr_theme_helper PR 47 prefers the installed theme schema, so on a real site dxt:config:list --detail, dxt:config:export, and dxt:config:reset cannot surface or round-trip palette settings even though dxt:config:set color_palette_* / dxt:palette:set can still mutate them. That leaves AI agents with a writable API that is larger than the introspectable API. I think the generated schema needs to emit either color_palette or synthetic color_palette_* entries rather than skipping them.

Copy link
Copy Markdown
Collaborator Author

@jjroelofs jjroelofs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left 1 inline comment. Main concern: the generated runtime schema currently drops palette controls, which creates a real discoverability/export/reset gap for AI-agent CLI workflows in dxpr_theme_helper#47 because the helper prefers the installed theme schema at runtime.

@jjroelofs
Copy link
Copy Markdown
Collaborator Author

Code Review: #803

Overall Assessment

Strong PR. The auto-generation approach from PHP form definitions is the right architecture — it eliminates cross-repo sync and makes the theme the single owner of its settings contract. The #ai_description values are consistently high-quality with practical design guidance. A few schema inconsistencies need attention before merge.


Critical Issues

1. Boolean defaults have inconsistent types (int vs boolean)

File: data/settings-schema.json (generated), root cause in scripts/generate-settings-schema.js

Some booleans get false/true (from PHP FALSE/TRUE) while others get 0/1 (from PHP 0/1):

  • header_top_fixed, headings_uppercase, headings_boldfalse
  • title_sticker, block_divider, page_title_breadcrumbs, boxed_layout, sticky_footer0/1

An AI agent reading the schema may interpret 0 as a numeric/range value rather than a boolean toggle. The consumer (dxpr_theme_helper PR dxpr/dxpr_theme_helper#47) handles both forms, so this isn't a runtime bug, but it's a schema contract inconsistency.

Fix: Normalize in parsePhpValue() or add a post-processing step: if type === "boolean", coerce default to true/false.

2. Range defaults stored as strings instead of numbers

File: data/settings-schema.json

Five range-type settings have string defaults due to PHP source using quoted literals:

  • dropdown_width: "200" → should be 200
  • menu_link_spacing: "10" → should be 10
  • menu_border_position_offset: "0" → should be 0
  • menu_border_position_offset_sticky: "0" → should be 0
  • page_title_height: "120" → should be 120

When dxt:config:reset (dxpr/dxpr_theme_helper#47) writes these, they'll be stored as strings rather than numbers.

Fix: Post-processing step: if type === "range" and typeof default === "string", coerce to parseFloat(default).

3. menu_border_animate default (0) is not a valid option key

File: data/settings-schema.json, menu_border_animate

Default is 0 (integer), but the valid option key for "No Animation" is "" (empty string). dxt:config:reset would write 0 which is not a valid option. This is a pre-existing PHP bug (#default_value => 0 instead of #default_value => '') but the schema now codifies it.

Fix: Either fix the PHP source or add an override in the generator.


Important Suggestions

4. logo_height has unit: "px" but is actually a percentage

File: data/settings-schema.json, logo_height

The ai_description correctly says "Logo height as percentage of header height" and max is 100, but the inferred unit is "px". The heuristic max > 3 → "px" doesn't account for this case. dxt:config:list --detail (dxpr/dxpr_theme_helper#47) will show "px" which is misleading.

Fix: Add an explicit override for logo_height, or improve the heuristic to detect percentage patterns.

5. divider_thickness has min: 8 but default: 4

File: data/settings-schema.json, divider_thickness + features/sooper-typography/typography-theme-settings.inc

The default is below the minimum. The ai_description references "1-2px=hairline" which is also below min. Pre-existing PHP bug.

6. Missing defaults for variable-derived settings

File: data/settings-schema.jsonheader_style, logo_height

These use PHP variables as defaults ($header_style, $logo_height), which the regex parser can't capture. AI agents and dxt:config:reset won't know the defaults.

Fix: Add a DEFAULT_OVERRIDES map in the generator for known variable-derived defaults, e.g., { header_style: 'normal' }.

7. block_preset default "none" is not a valid option key

File: data/settings-schema.json, block_preset

Default is "none" but the empty option key is "". Same class of bug as menu_border_animate.


Minor Issues

8. _meta.generated timestamp creates noisy diffs

Every regeneration changes the timestamp even when settings are unchanged. Consider omitting it or only updating when the payload changes.

9. _meta.fields.type lists path and serialized_palette but no generated settings use them

These exist only in the dxpr_theme_helper's extended schema. Could confuse consumers of this file.

10. extractOptions regex [\s\S]*?\] stops at first ]

Would break on option values containing ] (e.g., CSS selectors). Not an issue with current data but fragile.


Cross-Repo Consistency (with dxpr/dxpr_theme_helper#47)

The schema structure is well-matched to the consumer. loadSchema() in dxpr_theme_helper handles the runtime read correctly with a fallback to a bundled copy. The field definitions (section, type, title, description, ai_description, options, min/max/step/unit, default) align with what validateValue(), configList --detail, and configReset expect.

One gap: The bundled fallback schema in dxpr_theme_helper has manual entries (like color_palette with type serialized_palette) that don't exist in this auto-generated schema. This is by design (the theme generates only form-based settings), but the discrepancy means the bundled copy in dxpr_theme_helper needs to be maintained separately for these extra entries.


Positive Observations

  • Clean Gruntfile integrationexecFile with array args (no shell injection), clean watch glob
  • Excellent ai_description quality — consistent key=description patterns, practical px ranges, cross-references to related settings
  • Smart type normalization — correct inference of color, theme_color, font, boolean, media from generic PHP form types
  • Good SKIP_KEYS filtering — excludes containers, AI prompt fields, loop-generated keys
  • Security is clean — build-time only, no user input flows into generation

@jjroelofs
Copy link
Copy Markdown
Collaborator Author

Review notes on the generated schema as the new runtime contract for dxt:*:

  1. features/sooper-block-design/block-design-theme-settings.inc:24-30 and generated data/settings-schema.json:29-48
    block_preset is emitted with default: "none" even though the options use "" for “None”. The same pattern exists for menu_border_animate (features/sooper-header/header-theme-settings.inc:721-730, generated data/settings-schema.json:810-822) where the generated default is 0 but the options use "". Once dxpr_theme_helper reads this schema at runtime, dxt:config:reset will write values that are not actually valid enum options. I think the source form defaults need normalizing before this becomes the shared contract.

  2. scripts/generate-settings-schema.js:167-178 plus features/sooper-header/header-theme-settings.inc:106-114
    Unit inference falls back to px for any range with max > 3, so logo_height is generated as unit: "px" even though the form description says it is a percentage of header height. That makes the schema actively misleading for AI agents.

  3. data/settings-schema.json:297-304
    The generated colors section now only contains color_scheme; it no longer emits the synthetic color_palette setting that dxpr_theme_helper’s bundled schema currently relies on. With dxpr/dxpr_theme_helper#47 now preferring the theme-owned schema at runtime, that means dxt:config:export --section=colors can no longer round-trip the active palette unless one side synthesizes color_palette explicitly.

Copy link
Copy Markdown
Collaborator Author

@jjroelofs jjroelofs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skill Development Review — Settings Schema Auto-generation

Reviewed in the context of the cross-project AI skill infrastructure (dxpr/dxpr_cms#58, dxpr/dxpr_builder#4468, dxpr/rl#32) and skill development best practices (progressive disclosure, content organization, reference material handling).

✅ What's done well

  • #ai_description as Form API property: This is an elegant approach — the AI guidance lives alongside the form element definitions, invisible to the GUI, and is auto-extracted into the schema. This means developers maintain one source of truth.
  • Auto-generation via Grunt: The generate-schema task + watch on features/**/*-theme-settings.inc means the schema stays in sync during development without manual steps.
  • Schema richness: 163 settings with type, title, description, ai_description, options, ranges, and defaults. This gives AI agents everything they need to understand and set theme settings correctly.
  • Type detection by naming convention: The parser detects color, theme_color, and font types by key naming conventions (_custom suffix, _font_face suffix, known color select keys). This is practical and matches how the form API actually works in this theme.
  • Developer workflow: Edit inc file → watch regenerates schema → commit both. Clean and simple.

⚠️ Progressive disclosure considerations

The generated data/settings-schema.json is 1,554 lines. This is a reference file in the progressive disclosure model — it should be loaded by AI agents only when they need to understand or modify specific theme settings, not loaded into context by default.

The dxpr_theme_helper module (which reads this schema at runtime via dxt:* commands) handles this correctly — it reads the JSON file from the installed theme directory on demand. This is the right pattern: the schema lives as data in the theme, and the Drush commands surface it when needed.

If the Claude skill for theme settings (in dxpr_theme_helper) references this file, ensure it's documented as a reference to be loaded on demand, not embedded in SKILL.md.

📋 Schema generation observations

  1. _meta section: Good to include generated timestamp and field descriptions. The sections array is useful for agents to understand the settings categories.

  2. Parser robustness: The parser handles nested form arrays, conditional form elements, and various form API types. A few edge cases to verify:

    • Settings that are only visible conditionally (e.g., #states dependent fields) — are these included in the schema?
    • Settings with #access = FALSE — these should probably be excluded
    • The parser extracts #default_value — does it handle cases where the default is computed (e.g., from a variable)?
  3. Grunt vs. npm scripts: The project uses Grunt (existing infrastructure), so adding the task there is the right choice. No need to introduce a second build system.

  4. Schema validation: Consider adding a simple assertion in the Grunt task that verifies the output has the expected number of settings (or at least > 100), to catch parser regressions early.

📋 Cross-project integration

This PR is the data foundation for the dxt:* Drush commands in dxpr_theme_helper (dxpr/dxpr_theme_helper#40). The architecture is:

dxpr_theme (this PR)           → generates data/settings-schema.json
dxpr_theme_helper (separate PR) → reads schema at runtime via dxt:settings:* commands
.claude/skills/dxt/SKILL.md    → references schema via dxt:settings:list/get/set commands

This separation of concerns is well-designed — the theme owns its own schema, and the helper module provides the CLI interface.

Summary

Clean data-layer PR that provides the foundation for AI-accessible theme settings. The #ai_description pattern and auto-generation workflow are well thought out. Main consideration is ensuring the schema is treated as a reference file in the progressive disclosure model (loaded on demand, not always in context).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant