You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Sibling to #14, not a duplicate: that issue covers props a calling template passes that the child does not declare. This is the mirror — props a component's own twig body reads that its <name>.yaml does not declare.
The shape
graph-budget.twig:137-139 (mairateam) honours item.hidden — an item that feeds the chart's curve but draws no dot, label or year — and the component's own styleguide fixture uses it. graph-budget.yaml declares only year, label, value, highlight.
The consequence is specific to the MCP path: an agent reading get-block-catalog learns the definition, the definition is silent, and a write carrying the undeclared prop is refused by validation. The designed behaviour is unreachable — not degraded, unreachable.
How common, measured
Surveyed ~200 components across five real projects (mairateam 49, eprukaz2025 41, keypers 40, proficio(-de) 34, proficiohub 10, plus fellows' apartment family).
Three genuine instances:
project
component
prop
evidence
mairateam
graph-budget
item.hidden
graph-budget.twig:137-139, no sidecar — portadesign/mairateam#56
mairateam
certificate-list
content.spacing
certificate-list.twig:16-18, no .php sidecar, absent from yaml
fellows
apartment-list-type
item.status
apartment-list-type.twig:132, no sidecar
So this is rare, not systemic. Rare enough that a human catches it only by accident — #56 surfaced while writing MCP guidance annotations, which forces a side-by-side read of twig and yaml.
Why a naive linter would be worse than none
The survey's false positives are the design constraint. Five distinct legitimate reasons a prop is read but undeclared:
Framework injection — wrapper_id / wrapper_classes, set by BlockRenderer.php:308-309 on every component. Present in essentially every twig, absent from every yaml, correctly.
.php sidecar assembly — article-featured.php:37-51, career-list.php:1-27, reference-list.php:1-25 build their items from a WP_Query.
Global options — contact-details.php:1-11 pulls socials via Helpers::formatFields('option').
Render flags from the caller — eprukaz's card-list / divider read content.inner, set by {% include … with {inner: true} %}. divider.yaml deliberately declares fields: {}.
ACF return shapes — cards-list.item.attributes is part of the link type's own {url, title, attributes} structure, not a separate subfield.
A check that flags all of these is noise, and a noisy check gets disabled — the same reasoning #14 already applies to its wrapper_id example.
What would make it viable
The suppression is the feature, not the check:
A Twig-aware extractor collecting content.X and item.X reads per component. This is the hard part and the reason to be honest about cost — definition-kit is pure PHP with no Twig parser today (src/Lint/DriftLinter.php only diffs a definition against its own generated projections).
Not urgent. Three instances in two hundred components does not justify building a Twig parser on its own; it justifies filing this next to #14 so that if a Twig-aware extractor is ever built for that issue, this direction rides along — the extractor is the expensive part and both checks consume it.
Until then this stays a human-review item. Worth noting the one thing that reliably surfaces it: writing MCP guidance for a component forces reading its twig and its definition side by side, which is exactly how #56 was found.
Sibling to #14, not a duplicate: that issue covers props a calling template passes that the child does not declare. This is the mirror — props a component's own twig body reads that its
<name>.yamldoes not declare.The shape
graph-budget.twig:137-139(mairateam) honoursitem.hidden— an item that feeds the chart's curve but draws no dot, label or year — and the component's own styleguide fixture uses it.graph-budget.yamldeclares onlyyear,label,value,highlight.The consequence is specific to the MCP path: an agent reading
get-block-cataloglearns the definition, the definition is silent, and a write carrying the undeclared prop is refused by validation. The designed behaviour is unreachable — not degraded, unreachable.How common, measured
Surveyed ~200 components across five real projects (mairateam 49, eprukaz2025 41, keypers 40, proficio(-de) 34, proficiohub 10, plus fellows' apartment family).
Three genuine instances:
graph-budgetitem.hiddengraph-budget.twig:137-139, no sidecar — portadesign/mairateam#56certificate-listcontent.spacingcertificate-list.twig:16-18, no.phpsidecar, absent from yamlapartment-list-typeitem.statusapartment-list-type.twig:132, no sidecarSo this is rare, not systemic. Rare enough that a human catches it only by accident — #56 surfaced while writing MCP guidance annotations, which forces a side-by-side read of twig and yaml.
Why a naive linter would be worse than none
The survey's false positives are the design constraint. Five distinct legitimate reasons a prop is read but undeclared:
wrapper_id/wrapper_classes, set byBlockRenderer.php:308-309on every component. Present in essentially every twig, absent from every yaml, correctly..phpsidecar assembly —article-featured.php:37-51,career-list.php:1-27,reference-list.php:1-25build their items from a WP_Query.contact-details.php:1-11pulls socials viaHelpers::formatFields('option').card-list/dividerreadcontent.inner, set by{% include … with {inner: true} %}.divider.yamldeliberately declaresfields: {}.cards-list.item.attributesis part of thelinktype's own{url, title, attributes}structure, not a separate subfield.A check that flags all of these is noise, and a noisy check gets disabled — the same reasoning #14 already applies to its
wrapper_idexample.What would make it viable
The suppression is the feature, not the check:
content.Xanditem.Xreads per component. This is the hard part and the reason to be honest about cost — definition-kit is pure PHP with no Twig parser today (src/Lint/DriftLinter.phponly diffs a definition against its own generated projections).role:taxonomy from Implement role: + source: — provenance and ACF projection as two axes, not one #13 —query,computed,globalfields are declared-as-not-authored and must never be flagged.wrapper_id,wrapper_classes) plus anything the component's own.phpsidecar assigns onto$content.{% embed %}— the same half source: parent — declare the props a component takes from its call sites #14 defers, for the same reason.Recommendation
Not urgent. Three instances in two hundred components does not justify building a Twig parser on its own; it justifies filing this next to #14 so that if a Twig-aware extractor is ever built for that issue, this direction rides along — the extractor is the expensive part and both checks consume it.
Until then this stays a human-review item. Worth noting the one thing that reliably surfaces it: writing MCP guidance for a component forces reading its twig and its definition side by side, which is exactly how #56 was found.