What happens
project-bin/page-fidelity.js score() (~l.404) builds the page's class set from the page's own MDL only: Class: '…' and DynamicClasses tokens. It drops a small fixed chromeSet (topbar, userchip, avatar, content, or the dialog frame on popups).
Three sources of classes that really render are invisible to it, so the wireframe's classes read as missing:
- Snippet calls are not expanded. A page that embeds a snippet contributes none of the snippet's classes.
grep -i snippet project-bin/page-fidelity.js → nothing.
ButtonStyle: is not mapped to its classes. ButtonStyle: Default renders btn btn-default, and Primary renders btn btn-primary, etc. A wireframe a.btn reads as a missing class on every page that uses styled buttons.
- Layout-owned chrome beyond the fixed four. The rail/sidebar/topbar classes a project's own layout renders are not the page's to declare, but only the hard-coded set is excluded. Sample width classes a wireframe uses to mock data (e.g.
bar-w-*) are counted too.
Evidence (card-disbursement requirements-driven build, 2026-09-25/26)
- A dashboard page scored 69% (classes 58/82, actions 1/2).
- 3 of the 24 "missing" classes were present inside the health snippet the page embeds.
- Most of the rest were the layout's rail/topbar chrome and the wireframe's sample
bar-w-* widths, which the page sets at runtime.
- Checked against the wireframe class list vs the page and snippet MDL classes (
comm).
- The style-gallery page scored 80%, with
a.btn missing on every default button.
- In both cases the LOOK, not the score, was the check.
Suggested fix
- Resolve
snippetcall <Module>.<Snippet> in the page body, fetch the snippet's MDL the same way the page's is fetched, and union its classes. Do this recursively, with a cycle guard.
- Map
ButtonStyle: <X> to btn + btn-<lowercase X>. Map other widget properties with a fixed rendered class the same way, only where verified in a rendered DOM.
- Let the chrome set come from the page's layout: the classes in the layout's own MDL, or a
chromeClasses list in the project config. Keep reporting them as "chrome (layout-supplied, not scored)", as today.
- Keep printing the miss list. The percentage stays advisory.
These need golden captures, for example describe snippet output and a rendered DOM for ButtonStyle mapping. That is why this is filed rather than fixed.
Files
project-bin/page-fidelity.js: score(), pageMdl()
skills/ui-preflight-pages.md / LOOK guidance, if the score's scope changes
Related: the text-match looseness of the same scorer is filed separately.
What happens
project-bin/page-fidelity.jsscore()(~l.404) builds the page's class set from the page's own MDL only:Class: '…'andDynamicClassestokens. It drops a small fixedchromeSet(topbar,userchip,avatar,content, or the dialog frame on popups).Three sources of classes that really render are invisible to it, so the wireframe's classes read as missing:
grep -i snippet project-bin/page-fidelity.js→ nothing.ButtonStyle:is not mapped to its classes.ButtonStyle: Defaultrendersbtn btn-default, andPrimaryrendersbtn btn-primary, etc. A wireframea.btnreads as a missing class on every page that uses styled buttons.bar-w-*) are counted too.Evidence (card-disbursement requirements-driven build, 2026-09-25/26)
bar-w-*widths, which the page sets at runtime.comm).a.btnmissing on every default button.Suggested fix
snippetcall <Module>.<Snippet>in the page body, fetch the snippet's MDL the same way the page's is fetched, and union its classes. Do this recursively, with a cycle guard.ButtonStyle: <X>tobtn+btn-<lowercase X>. Map other widget properties with a fixed rendered class the same way, only where verified in a rendered DOM.chromeClasseslist in the project config. Keep reporting them as "chrome (layout-supplied, not scored)", as today.These need golden captures, for example
describe snippetoutput and a rendered DOM for ButtonStyle mapping. That is why this is filed rather than fixed.Files
project-bin/page-fidelity.js:score(),pageMdl()skills/ui-preflight-pages.md/ LOOK guidance, if the score's scope changesRelated: the text-match looseness of the same scorer is filed separately.