efficient-web-builder 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/components/BlogPostSource.js +1 -1
  3. package/dist/components/Breadcrumb.js +1 -1
  4. package/dist/components/Btn.js +1 -1
  5. package/dist/components/CardShadowField.js +1 -1
  6. package/dist/components/CategoryBar.js +1 -1
  7. package/dist/components/ChartSection.js +1 -1
  8. package/dist/components/ChatSettingsPanel.js +1 -1
  9. package/dist/components/ClearColoursDialog.js +1 -1
  10. package/dist/components/ContactLocations.js +5 -5
  11. package/dist/components/CornersField.js +1 -1
  12. package/dist/components/DocBody.js +1 -1
  13. package/dist/components/DuplicateToPageMenu.js +1 -1
  14. package/dist/components/FeaturedCover.js +1 -1
  15. package/dist/components/FieldGlyph.js +1 -1
  16. package/dist/components/Footprint.js +1 -1
  17. package/dist/components/HeadingAudit.js +1 -1
  18. package/dist/components/HeroSection.js +1 -1
  19. package/dist/components/HoverEffectField.js +1 -1
  20. package/dist/components/ImageBlock.js +1 -1
  21. package/dist/components/ImageFocusRow.js +1 -1
  22. package/dist/components/LocationsGroupedField.js +1 -1
  23. package/dist/components/LongRead.js +1 -1
  24. package/dist/components/MediaUploadField.js +1 -1
  25. package/dist/components/MockupSection.js +1 -1
  26. package/dist/components/PagesPanel.js +1 -1
  27. package/dist/components/PartResetDot.js +1 -1
  28. package/dist/components/PartSettingsPanel.js +1 -1
  29. package/dist/components/PostCard.js +1 -1
  30. package/dist/components/ProcessSteps.js +1 -1
  31. package/dist/components/RMark.js +1 -1
  32. package/dist/components/Reel.js +1 -1
  33. package/dist/components/SectionEditor.js +1 -1
  34. package/dist/components/SectionHeader.js +1 -1
  35. package/dist/components/SectionThumbnail.js +1 -1
  36. package/dist/components/SlotChildRows.js +1 -1
  37. package/dist/components/SourceStatus.js +1 -1
  38. package/dist/components/Split.js +1 -1
  39. package/dist/components/StandaloneSectionEditor.js +1 -1
  40. package/dist/components/StylesPanel.js +1 -1
  41. package/dist/components/Testimonials.js +1 -1
  42. package/dist/components/TextStylesField.js +1 -1
  43. package/dist/components/ThemeCard.js +1 -1
  44. package/dist/components/ThemeColourField.js +1 -1
  45. package/dist/components/ThemeField.js +1 -1
  46. package/dist/components/TickerSettingsPanel.js +1 -1
  47. package/dist/components/Timeline.js +1 -1
  48. package/dist/components/TimelineTrack.js +1 -1
  49. package/dist/components/TitleWithButtons.js +1 -1
  50. package/dist/components/WebsiteScreenParts.js +1 -1
  51. package/dist/components/WebsiteSettingsPanel.js +1 -1
  52. package/dist/components/goldPhrases.js +1 -1
  53. package/dist/components/storefront-editor.css +25 -212
  54. package/dist/lib/content/contact.js +3 -3
  55. package/dist/lib/content/subscribe.js +1 -1
  56. package/dist/lib/puck/MarkupToolbar.js +1 -1
  57. package/dist/lib/puck/blocks/general/CategoryBar/fields.js +1 -1
  58. package/dist/lib/puck/blocks/general/Chart/fields.js +1 -1
  59. package/dist/lib/puck/blocks/general/FAQ/render.js +1 -1
  60. package/dist/lib/puck/blocks/general/FeaturedCover/render.js +1 -1
  61. package/dist/lib/puck/blocks/general/Footprint/fields.js +1 -1
  62. package/dist/lib/puck/blocks/general/IntroBleed/render.js +1 -1
  63. package/dist/lib/puck/blocks/general/LogoBlock/fields.js +1 -1
  64. package/dist/lib/puck/blocks/general/LongRead/render.js +1 -1
  65. package/dist/lib/puck/blocks/general/Masthead/render.js +1 -1
  66. package/dist/lib/puck/blocks/general/Mockup/fields.js +1 -1
  67. package/dist/lib/puck/blocks/general/OrderSummary/render.js +1 -1
  68. package/dist/lib/puck/blocks/general/ParagraphsA/render.js +1 -1
  69. package/dist/lib/puck/blocks/general/PostCard/render.js +1 -1
  70. package/dist/lib/puck/blocks/general/ProcessSteps/render.js +1 -1
  71. package/dist/lib/puck/blocks/general/Series/render.js +1 -1
  72. package/dist/lib/puck/blocks/general/Split/render.js +1 -1
  73. package/dist/lib/puck/blocks/general/Testimonials/fields.js +1 -1
  74. package/dist/lib/puck/blocks/general/Testimonials/render.js +1 -1
  75. package/dist/lib/puck/blocks/general/TimelineTrack/render.js +1 -1
  76. package/dist/lib/puck/blocks/placeholders.js +1 -1
  77. package/dist/lib/puck/controls.js +1 -1
  78. package/dist/lib/puck/field-groups.js +1 -1
  79. package/dist/lib/puck/footer-block-row-name.js +1 -1
  80. package/dist/lib/puck/geocode-resolve.js +1 -1
  81. package/dist/lib/puck/locations-tree.js +2 -2
  82. package/dist/lib/puck/neutralization-contracts.js +1 -1
  83. package/dist/lib/puck/part-rows.js +1 -1
  84. package/dist/lib/puck/section-catalog.js +1 -1
  85. package/dist/lib/puck/section-descriptions.js +1 -1
  86. package/dist/lib/sections/divider-style.js +1 -1
  87. package/dist/lib/sections/product-card-style.js +1 -1
  88. package/dist/lib/sections/ticker-style.js +1 -1
  89. package/dist/lib/source-rules/accent-token-name.js +2 -2
  90. package/dist/lib/source-rules/button-corners.js +1 -1
  91. package/dist/lib/source-rules/editor-chrome-note.js +2 -2
  92. package/dist/lib/source-rules/editor-context-menu.js +2 -2
  93. package/dist/lib/source-rules/editor-puck-drag-off.js +2 -2
  94. package/dist/lib/source-rules/efficient-easel-version.js +1 -1
  95. package/dist/lib/source-rules/every-button-follows-the-button-colour.js +4 -4
  96. package/dist/lib/source-rules/flat-theme-palette.js +2 -2
  97. package/dist/lib/source-rules/full-width-means-full.js +1 -1
  98. package/dist/lib/source-rules/heading-ceiling-sweep.js +3 -3
  99. package/dist/lib/source-rules/label-hints.js +4 -4
  100. package/dist/lib/source-rules/label-vocabulary.js +4 -4
  101. package/dist/lib/source-rules/mirrored-rows-agree.js +4 -4
  102. package/dist/lib/source-rules/no-comment-in-jsx.js +2 -2
  103. package/dist/lib/source-rules/no-glyph-rows.js +2 -2
  104. package/dist/lib/source-rules/one-colour-control.js +2 -2
  105. package/dist/lib/source-rules/one-dropdown.js +2 -2
  106. package/dist/lib/source-rules/one-editable-list.js +3 -3
  107. package/dist/lib/source-rules/one-editor-list.js +1 -1
  108. package/dist/lib/source-rules/one-right-click.js +2 -2
  109. package/dist/lib/source-rules/one-switch.js +1 -1
  110. package/dist/lib/source-rules/one-up-level-link.js +7 -7
  111. package/dist/lib/source-rules/panel-colour-tokens.js +7 -7
  112. package/dist/lib/source-rules/panel-no-divider-lines.js +5 -5
  113. package/dist/lib/source-rules/panel-one-arrow.js +1 -1
  114. package/dist/lib/source-rules/panel-one-hover.js +2 -2
  115. package/dist/lib/source-rules/panel-rows-use-the-shared-row.js +5 -5
  116. package/dist/lib/source-rules/part-slider-fills-row.js +8 -8
  117. package/dist/lib/source-rules/part-style-naming.js +1 -1
  118. package/dist/lib/source-rules/puck-document-surfaces.js +6 -6
  119. package/dist/lib/source-rules/reduced-motion.js +2 -2
  120. package/dist/lib/source-rules/route-content-width.js +2 -2
  121. package/dist/lib/source-rules/rule.js +1 -1
  122. package/dist/lib/source-rules/section-theme-preview.js +2 -2
  123. package/dist/lib/source-rules/site-square-by-default.js +1 -1
  124. package/dist/lib/source-rules/site-width.js +2 -2
  125. package/dist/lib/source-rules/size-token-paints.js +2 -2
  126. package/dist/lib/source-rules/social-icons-import.js +1 -1
  127. package/dist/lib/source-rules/source-status-is-shared.js +1 -1
  128. package/dist/lib/source-rules/style-face-matches-size.js +3 -3
  129. package/dist/lib/source-rules/support/part-slider-call-sites.js +4 -4
  130. package/dist/lib/source-rules/support/registry-source.js +1 -1
  131. package/dist/lib/source-rules/theme-mode-controls-gone.js +3 -3
  132. package/dist/lib/source-rules/upload-intake-guard.js +2 -2
  133. package/dist/lib/source-rules/word-styles.js +2 -2
  134. package/package.json +5 -5
@@ -1,6 +1,6 @@
1
- import{ruleChecks as $}from"./rule.js";const y=s=>s.replace(/\/\*[\s\S]*?\*\//g,"").replace(/(^|[^:"'`])\/\/[^\n]*/g,"$1").replace(/\{\/\*[\s\S]*?\*\/\}/g,""),v=[["Move up / Move down buttons",/aria-label=["'{`]*Move (up|down)/],['"+ Add \u2026" buttons',/>\s*\+ Add [A-Za-z]|["'`]\+ Add [A-Za-z]/],["sf-textlist markup (the ticker's old list)",/["'` ]sf-textlist/],["TextListEditor",/\bTextListEditor\b/]];function M(s,h){const{check:U,report:w}=$("one editable list"),i=(t,d,o)=>{let p=!1,A=o;try{p=typeof d=="function"?d():d}catch(e){A=String(e.message??e)}return U(t,p,A)},x=`
2
- <button type="button" style={iconBtn} aria-label="Move up" title="Move up" onClick={() => swap(1, 0)}>\u2191</button>
1
+ import{ruleChecks as $}from"./rule.js";const y=s=>s.replace(/\/\*[\s\S]*?\*\//g,"").replace(/(^|[^:"'`])\/\/[^\n]*/g,"$1").replace(/\{\/\*[\s\S]*?\*\/\}/g,""),v=[["Move up / Move down buttons",/aria-label=["'{`]*Move (up|down)/],['"+ Add …" buttons',/>\s*\+ Add [A-Za-z]|["'`]\+ Add [A-Za-z]/],["sf-textlist markup (the ticker's old list)",/["'` ]sf-textlist/],["TextListEditor",/\bTextListEditor\b/]];function M(s,h){const{check:U,report:w}=$("one editable list"),i=(t,d,o)=>{let p=!1,A=o;try{p=typeof d=="function"?d():d}catch(e){A=String(e.message??e)}return U(t,p,A)},x=`
2
+ <button type="button" style={iconBtn} aria-label="Move up" title="Move up" onClick={() => swap(1, 0)}>↑</button>
3
3
  <button type="button" style={addBtn} onClick={addGroup}>+ Add group</button>
4
4
  <div className="sf-textlist"><button className="sf-textlist-add">+ Add item</button></div>
5
5
  export function TextListEditor() {}
6
- `;for(const[t,d]of v)i(`control: the rule for ${t} catches it in old code`,d.test(y(x)));const a=s.filter(t=>t.path.endsWith(".tsx"));i(`read the editor's .tsx files (${a.length}, at least ${h.minFiles})`,a.length>=h.minFiles&&a.length>0,`only ${a.length}`);for(const[t,d]of v){const o=a.filter(p=>d.test(y(p.text))).map(p=>p.path);i(`no ${t} in the .tsx files`,o.length===0,o.join(", "))}const u=s.filter(t=>t.path.endsWith(".css"));i(`read the editor's stylesheets (${u.length}, at least ${h.minSheets})`,u.length>=h.minSheets);const r=u.filter(t=>/\.sf-textlist\b/.test(t.text.replace(/\/\*[\s\S]*?\*\//g,"")));i("no .sf-textlist rule left behind",r.length===0,r.map(t=>t.path).join(", "));const m=t=>{const d=s.find(o=>o.path===t);if(!d)throw new Error(`${t} was not among the files read`);return y(d.text)},c=h.presence??{};if(c.controls!==void 0){const t=c.controls;i(`${t} draws lines of copy with easel's ListField`,()=>/import \{[^}]*\bListField\b[^}]*\} from "efficient-easel\/fields"/.test(m(t)))}if(c.locationsPanel!==void 0){const t=c.locationsPanel;i(`the Locations panel (${t}) draws its groups and cities with ListField`,()=>(m(t).match(/<ListField\b/g)??[]).length===2)}if(c.tickerPanel!==void 0){const t=c.tickerPanel;i(`the ticker panel (${t}) draws Ticker items with the same TextListField the textList builder draws`,()=>/<TextListField\b/.test(m(t)))}return h.listModules&&E(i,h.listModules),w()}function E(s,h){const U=n=>()=>{throw new Error(`${n} does not exist`)},w=(n,l)=>n[l]??U(l),i=w(h.locationsTree,"writeGroupCities"),x=w(h.locationsTree,"writeGroupOrder"),a=w(h.controls,"aimAfterListChange"),u=(n,l)=>({name:n,group:l,groupName:""}),r=n=>n.map(l=>`${l.group}:${l.name}`).join(" "),m=(n,l,f)=>{const g=n.slice();return[g[l],g[f]]=[g[f],g[l]],g},c=(n,l,f,g)=>{const b=l.slice();return[b[f],b[f+g]]=[b[f+g],b[f]],b.flatMap(L=>n.filter(S=>(S.group??"")===L))},t=[u("Sydney","AU"),u("Austin","US"),u("Perth","AU"),u("Miami","US"),u("Hobart","AU")],d=[0,2,4],o=d.map(n=>t[n]);s("a city one step down saves what the old \u2193 saved",()=>r(i(t,"AU",[o[1],o[0],o[2]]))===r(m(t,0,2))),s("a city one step up saves what the old \u2191 saved",()=>r(i(t,"AU",[o[0],o[2],o[1]]))===r(m(t,4,2))),s("a city dragged from first to last leaves every other group's cards in their places",()=>{const n=i(t,"AU",[o[1],o[2],o[0]]);return n[1]===t[1]&&n[3]===t[3]&&n.length===t.length}),s("\u2026and puts the group's own cards in the new order",()=>{const n=i(t,"AU",[o[1],o[2],o[0]]);return r(d.map(l=>n[l]))==="AU:Perth AU:Hobart AU:Sydney"});const p={...u("","AU")};s('"+ Add city" at the end saves what the old button saved (the new card last)',()=>r(i(t,"AU",[...o,p]))===r([...t,p])),s("a group one step down saves what the old \u2193 saved",()=>r(x(t,["US","AU"],()=>[]))===r(c(t,["AU","US"],0,1)));const A=u("","Group 3");s('a new group gets its first city ("+ Add group" put it last)',()=>r(x(t,["AU","US","Group 3"],n=>n==="Group 3"?[A]:[]))===r([...c(t,["US","AU"],0,1),A]));const e=[{text:"a"},{text:"b"},{text:"c"}];s("typing in the aimed line keeps the aim",()=>a(e,[e[0],{text:"bb"},e[2]],1)===1),s("taking out a line BELOW the aimed one keeps the aim",()=>a(e,[e[0],e[1]],1)===1),s("taking out the aimed line lets go",()=>a(e,[e[0],e[2]],1)===null),s("taking out a line ABOVE the aimed one lets go (its box now holds other words)",()=>a(e,[e[1],e[2]],1)===null),s("a line added at the end keeps the aim",()=>a(e,[...e,{text:""}],1)===1),s("a line added above the aimed one lets go",()=>a(e,[e[0],{text:""},e[1],e[2]],1)===null),s("a move lets go (the caret going with the line aims again)",()=>a(e,[e[1],e[0],e[2]],1)===null),s("nothing aimed stays nothing aimed",()=>a(e,[e[1],e[0]],null)===null)}export{M as oneEditableList};
6
+ `;for(const[t,d]of v)i(`control: the rule for ${t} catches it in old code`,d.test(y(x)));const a=s.filter(t=>t.path.endsWith(".tsx"));i(`read the editor's .tsx files (${a.length}, at least ${h.minFiles})`,a.length>=h.minFiles&&a.length>0,`only ${a.length}`);for(const[t,d]of v){const o=a.filter(p=>d.test(y(p.text))).map(p=>p.path);i(`no ${t} in the .tsx files`,o.length===0,o.join(", "))}const u=s.filter(t=>t.path.endsWith(".css"));i(`read the editor's stylesheets (${u.length}, at least ${h.minSheets})`,u.length>=h.minSheets);const r=u.filter(t=>/\.sf-textlist\b/.test(t.text.replace(/\/\*[\s\S]*?\*\//g,"")));i("no .sf-textlist rule left behind",r.length===0,r.map(t=>t.path).join(", "));const m=t=>{const d=s.find(o=>o.path===t);if(!d)throw new Error(`${t} was not among the files read`);return y(d.text)},c=h.presence??{};if(c.controls!==void 0){const t=c.controls;i(`${t} draws lines of copy with easel's ListField`,()=>/import \{[^}]*\bListField\b[^}]*\} from "efficient-easel\/fields"/.test(m(t)))}if(c.locationsPanel!==void 0){const t=c.locationsPanel;i(`the Locations panel (${t}) draws its groups and cities with ListField`,()=>(m(t).match(/<ListField\b/g)??[]).length===2)}if(c.tickerPanel!==void 0){const t=c.tickerPanel;i(`the ticker panel (${t}) draws Ticker items with the same TextListField the textList builder draws`,()=>/<TextListField\b/.test(m(t)))}return h.listModules&&E(i,h.listModules),w()}function E(s,h){const U=n=>()=>{throw new Error(`${n} does not exist`)},w=(n,l)=>n[l]??U(l),i=w(h.locationsTree,"writeGroupCities"),x=w(h.locationsTree,"writeGroupOrder"),a=w(h.controls,"aimAfterListChange"),u=(n,l)=>({name:n,group:l,groupName:""}),r=n=>n.map(l=>`${l.group}:${l.name}`).join(" "),m=(n,l,f)=>{const g=n.slice();return[g[l],g[f]]=[g[f],g[l]],g},c=(n,l,f,g)=>{const b=l.slice();return[b[f],b[f+g]]=[b[f+g],b[f]],b.flatMap(L=>n.filter(S=>(S.group??"")===L))},t=[u("Sydney","AU"),u("Austin","US"),u("Perth","AU"),u("Miami","US"),u("Hobart","AU")],d=[0,2,4],o=d.map(n=>t[n]);s("a city one step down saves what the old ↓ saved",()=>r(i(t,"AU",[o[1],o[0],o[2]]))===r(m(t,0,2))),s("a city one step up saves what the old ↑ saved",()=>r(i(t,"AU",[o[0],o[2],o[1]]))===r(m(t,4,2))),s("a city dragged from first to last leaves every other group's cards in their places",()=>{const n=i(t,"AU",[o[1],o[2],o[0]]);return n[1]===t[1]&&n[3]===t[3]&&n.length===t.length}),s("…and puts the group's own cards in the new order",()=>{const n=i(t,"AU",[o[1],o[2],o[0]]);return r(d.map(l=>n[l]))==="AU:Perth AU:Hobart AU:Sydney"});const p={...u("","AU")};s('"+ Add city" at the end saves what the old button saved (the new card last)',()=>r(i(t,"AU",[...o,p]))===r([...t,p])),s("a group one step down saves what the old ↓ saved",()=>r(x(t,["US","AU"],()=>[]))===r(c(t,["AU","US"],0,1)));const A=u("","Group 3");s('a new group gets its first city ("+ Add group" put it last)',()=>r(x(t,["AU","US","Group 3"],n=>n==="Group 3"?[A]:[]))===r([...c(t,["US","AU"],0,1),A]));const e=[{text:"a"},{text:"b"},{text:"c"}];s("typing in the aimed line keeps the aim",()=>a(e,[e[0],{text:"bb"},e[2]],1)===1),s("taking out a line BELOW the aimed one keeps the aim",()=>a(e,[e[0],e[1]],1)===1),s("taking out the aimed line lets go",()=>a(e,[e[0],e[2]],1)===null),s("taking out a line ABOVE the aimed one lets go (its box now holds other words)",()=>a(e,[e[1],e[2]],1)===null),s("a line added at the end keeps the aim",()=>a(e,[...e,{text:""}],1)===1),s("a line added above the aimed one lets go",()=>a(e,[e[0],{text:""},e[1],e[2]],1)===null),s("a move lets go (the caret going with the line aims again)",()=>a(e,[e[1],e[0],e[2]],1)===null),s("nothing aimed stays nothing aimed",()=>a(e,[e[1],e[0]],null)===null)}export{M as oneEditableList};
@@ -1,3 +1,3 @@
1
1
  import{ruleChecks as d}from"./rule.js";function s(i){return i.includes('role="listbox"')||i.includes('type="checkbox"')&&/role="group"/.test(i)&&/set\w*Open\(/.test(i)}function u(i,o){const{check:t,report:c}=d("one editor list"),n=o.kept??{};t("a listbox is a list",s('<ul role="listbox">')),t("tick boxes in a group the file opens itself are a list",s(`const [o, setOpen] = useState(false);
2
2
  <div role="group"><input type="checkbox" /></div>
3
- setOpen(true);`)),t("a lone tick box is not a list",!s('<input type="checkbox" />')),t("FieldSelect is not a list of the file's own",!s("<FieldSelect options={o} multiple inline />"));const l=i.filter(e=>e.path.endsWith(".tsx"));t(`read the source (${l.length} .tsx files, at least ${o.minFiles})`,l.length>=o.minFiles,"fewer means the walk broke, and the census is vacuous");const r=l.filter(e=>s(e.text)).map(e=>e.path);for(const[e,p]of Object.entries(n))t(`the kept list is still real: ${e} (${p})`,r.includes(e),"it no longer draws a list \u2014 remove the stale entry");const a=r.filter(e=>!(e in n));return t("no second editor list: every file that draws a list of its own is a kept one",a.length===0,`a second editor list: ${a.join(", ")} \u2014 draw it with efficient-easel's FieldSelect`),c()}export{s as drawsList,u as oneEditorList};
3
+ setOpen(true);`)),t("a lone tick box is not a list",!s('<input type="checkbox" />')),t("FieldSelect is not a list of the file's own",!s("<FieldSelect options={o} multiple inline />"));const l=i.filter(e=>e.path.endsWith(".tsx"));t(`read the source (${l.length} .tsx files, at least ${o.minFiles})`,l.length>=o.minFiles,"fewer means the walk broke, and the census is vacuous");const r=l.filter(e=>s(e.text)).map(e=>e.path);for(const[e,p]of Object.entries(n))t(`the kept list is still real: ${e} (${p})`,r.includes(e),"it no longer draws a list — remove the stale entry");const a=r.filter(e=>!(e in n));return t("no second editor list: every file that draws a list of its own is a kept one",a.length===0,`a second editor list: ${a.join(", ")} — draw it with efficient-easel's FieldSelect`),c()}export{s as drawsList,u as oneEditorList};
@@ -1,2 +1,2 @@
1
- import{ruleChecks as y}from"./rule.js";import{isPanelFile as b,readJsxLabels as m,readLabels as f,readPanelLabels as u,readRowLabels as w}from"./support/label-extraction.js";import{registryFiles as L,registryText as $}from"./support/registry-source.js";const F=/label:\s*"(?:[^"\\]|\\.)*"\s*,\s*onClick:/g;function p(e){const s=new Set;return[...f(e),...w(e),...m(e)].filter(t=>{const l=`${t.line}:${t.label}`;return s.has(l)?!1:(s.add(l),!0)})}function g(e){return{lost:p(e).length-u(e).length,actions:(e.match(F)??[]).length}}function k(e,s,t){const l=o=>s.find(a=>a.path===o)?.text,i=[...t.stylesheets,t.locationsRows],r=i.filter(o=>l(o)===void 0);e(`read the files drawn round the screen (${i.length-r.length} of ${i.length})`,r.length===0&&t.stylesheets.length>0,`not among the files handed in: ${r.join(", ")||"no stylesheet named"} \u2014 every clause below is vacuous without them`);const n=l(t.locationsRows)??"",c=t.stylesheets.filter(o=>(l(o)??"").includes("sf-paste-hint"));e("\xA71d no stylesheet of the editor's draws a second toast",c.length===0,`sf-paste-hint in ${c.join(", ")}`),e("\xA73 the Locations rows are where this reads them",/selectLocationsFocus\(si, \{ level: "group"/.test(n)&&/selectLocationsFocus\(si, \{ level: "city"/.test(n)),e("\xA73 a Locations group row is not addressed as a node",!/selectLocationsFocus\(si, \{ level: "group"[\s\S]{0,200}data-node-id/.test(n)&&!/data-node-id/.test(n)),e("\xA73 a Locations city row is not addressed as a node",!/selectLocationsFocus\(si, \{ level: "city"[\s\S]{0,200}data-node-id/.test(n))}function P(e,s){const{check:t,report:l}=y("one right click");s.editor&&k(t,e,s.editor);{const a=["const items = [",' { label: "Copy", onClick: () => copy() },',"];","const rows = [",' { key: "heading", label: "Heading", group: "text" },',"];"].join(`
2
- `),d=u(a).map(h=>h.label);t("\xA74 the action rule drops an action's label and keeps a field's",d.length===1&&d[0]==="Heading"&&g(a).lost===1&&g(a).actions===1,JSON.stringify({kept:d,...g(a)}))}const i=e.filter(b);t(`\xA74 read the panel files (${i.length}, at least ${s.minPanelFiles})`,i.length>=s.minPanelFiles,"fewer means a panel stopped drawing a row or the predicate broke");const r=[];for(const a of i){const{lost:d,actions:h}=g(a.text);d>h&&r.push(`${a.path} (${d}, ${h} of them actions)`)}t("\xA74 no panel file loses a label to the action rule beyond its actions",r.length===0,r.join(", ")+" \u2014 a real panel dropping out of the vocabulary guard is the failure this rule must not cause");const n=L(e),c=$(e),o=f(c).length;return t(`\xA74 read the block registry (${n.length} files, ${o} labels)`,n.length>0,"no registry file among the files handed in"),t("\xA74 the inspector's own labels are read by a path the rule cannot reach",n.length>0&&(p(c).length===u(c).length||o>=s.registryLabelFloor),`the registry goes through readLabels, not readPanelLabels (${o} read, floor ${s.registryLabelFloor})`),l()}export{P as oneRightClick};
1
+ import{ruleChecks as y}from"./rule.js";import{isPanelFile as b,readJsxLabels as m,readLabels as f,readPanelLabels as u,readRowLabels as w}from"./support/label-extraction.js";import{registryFiles as L,registryText as $}from"./support/registry-source.js";const F=/label:\s*"(?:[^"\\]|\\.)*"\s*,\s*onClick:/g;function p(e){const s=new Set;return[...f(e),...w(e),...m(e)].filter(t=>{const l=`${t.line}:${t.label}`;return s.has(l)?!1:(s.add(l),!0)})}function g(e){return{lost:p(e).length-u(e).length,actions:(e.match(F)??[]).length}}function k(e,s,t){const l=o=>s.find(a=>a.path===o)?.text,i=[...t.stylesheets,t.locationsRows],r=i.filter(o=>l(o)===void 0);e(`read the files drawn round the screen (${i.length-r.length} of ${i.length})`,r.length===0&&t.stylesheets.length>0,`not among the files handed in: ${r.join(", ")||"no stylesheet named"} — every clause below is vacuous without them`);const n=l(t.locationsRows)??"",c=t.stylesheets.filter(o=>(l(o)??"").includes("sf-paste-hint"));e("§1d no stylesheet of the editor's draws a second toast",c.length===0,`sf-paste-hint in ${c.join(", ")}`),e("§3 the Locations rows are where this reads them",/selectLocationsFocus\(si, \{ level: "group"/.test(n)&&/selectLocationsFocus\(si, \{ level: "city"/.test(n)),e("§3 a Locations group row is not addressed as a node",!/selectLocationsFocus\(si, \{ level: "group"[\s\S]{0,200}data-node-id/.test(n)&&!/data-node-id/.test(n)),e("§3 a Locations city row is not addressed as a node",!/selectLocationsFocus\(si, \{ level: "city"[\s\S]{0,200}data-node-id/.test(n))}function P(e,s){const{check:t,report:l}=y("one right click");s.editor&&k(t,e,s.editor);{const a=["const items = [",' { label: "Copy", onClick: () => copy() },',"];","const rows = [",' { key: "heading", label: "Heading", group: "text" },',"];"].join(`
2
+ `),d=u(a).map(h=>h.label);t("§4 the action rule drops an action's label and keeps a field's",d.length===1&&d[0]==="Heading"&&g(a).lost===1&&g(a).actions===1,JSON.stringify({kept:d,...g(a)}))}const i=e.filter(b);t(`§4 read the panel files (${i.length}, at least ${s.minPanelFiles})`,i.length>=s.minPanelFiles,"fewer means a panel stopped drawing a row or the predicate broke");const r=[];for(const a of i){const{lost:d,actions:h}=g(a.text);d>h&&r.push(`${a.path} (${d}, ${h} of them actions)`)}t("§4 no panel file loses a label to the action rule beyond its actions",r.length===0,r.join(", ")+" — a real panel dropping out of the vocabulary guard is the failure this rule must not cause");const n=L(e),c=$(e),o=f(c).length;return t(`§4 read the block registry (${n.length} files, ${o} labels)`,n.length>0,"no registry file among the files handed in"),t("§4 the inspector's own labels are read by a path the rule cannot reach",n.length>0&&(p(c).length===u(c).length||o>=s.registryLabelFloor),`the registry goes through readLabels, not readPanelLabels (${o} read, floor ${s.registryLabelFloor})`),l()}export{P as oneRightClick};
@@ -1,4 +1,4 @@
1
- import{ruleChecks as x}from"./rule.js";function S(i,o){const n=i.createSourceFile(o.path,o.text,i.ScriptTarget.Latest,!0,i.ScriptKind.TSX),s=[],f=[],a=t=>n.getLineAndCharacterOfPosition(t.getStart(n)).line+1,r=t=>{if(i.isJsxOpeningElement(t)||i.isJsxSelfClosingElement(t)){let h=!1,g="";for(const e of t.attributes.properties){if(!i.isJsxAttribute(e))continue;const c=e.name.getText(n),m=e.initializer,w=m?m.getText(n):"";c==="role"&&/["'`]switch["'`]/.test(w)&&(h=!0),c==="className"&&(g=w)}s.push({name:t.tagName.getText(n),line:a(t),roleSwitch:h,classText:g})}if(i.isPropertyAssignment(t)){const h=t.name.getText(n).replace(/["']/g,""),g=t.initializer.getText(n);h==="role"&&/^["'`]switch["'`]$/.test(g)&&s.push({name:"(createElement)",line:a(t),roleSwitch:!0,classText:""}),h==="className"&&f.push({line:a(t),text:g})}i.forEachChild(t,r)};return r(n),{tags:s,classStrings:f}}function p(i,o,n){const{tags:s,classStrings:f}=S(i,o),a=[];let r=0;for(const t of s)t.name==="SettingsSwitch"&&r++,t.name==="ToggleSwitch"&&a.push({file:o.path,line:t.line,what:"R1 <ToggleSwitch> \u2014 use easel's SettingsSwitch"}),t.roleSwitch&&!(o.path in n)&&a.push({file:o.path,line:t.line,what:`R2 hand-rolled role="switch" on <${t.name}> \u2014 use easel's SettingsSwitch`}),/\bsf-toggle\b/.test(t.classText)&&a.push({file:o.path,line:t.line,what:"R3 sf-toggle class in className"});for(const t of f)/\bsf-toggle\b/.test(t.text)&&a.push({file:o.path,line:t.line,what:"R3 sf-toggle class in className"});return{bad:a,settingsSwitch:r}}function d(i){const o=[];return i.text.replace(/\/\*[\s\S]*?\*\//g,s=>s.replace(/[^\n]/g," ")).split(`
1
+ import{ruleChecks as x}from"./rule.js";function S(i,o){const n=i.createSourceFile(o.path,o.text,i.ScriptTarget.Latest,!0,i.ScriptKind.TSX),s=[],f=[],a=t=>n.getLineAndCharacterOfPosition(t.getStart(n)).line+1,r=t=>{if(i.isJsxOpeningElement(t)||i.isJsxSelfClosingElement(t)){let h=!1,g="";for(const e of t.attributes.properties){if(!i.isJsxAttribute(e))continue;const c=e.name.getText(n),m=e.initializer,w=m?m.getText(n):"";c==="role"&&/["'`]switch["'`]/.test(w)&&(h=!0),c==="className"&&(g=w)}s.push({name:t.tagName.getText(n),line:a(t),roleSwitch:h,classText:g})}if(i.isPropertyAssignment(t)){const h=t.name.getText(n).replace(/["']/g,""),g=t.initializer.getText(n);h==="role"&&/^["'`]switch["'`]$/.test(g)&&s.push({name:"(createElement)",line:a(t),roleSwitch:!0,classText:""}),h==="className"&&f.push({line:a(t),text:g})}i.forEachChild(t,r)};return r(n),{tags:s,classStrings:f}}function p(i,o,n){const{tags:s,classStrings:f}=S(i,o),a=[];let r=0;for(const t of s)t.name==="SettingsSwitch"&&r++,t.name==="ToggleSwitch"&&a.push({file:o.path,line:t.line,what:"R1 <ToggleSwitch> — use easel's SettingsSwitch"}),t.roleSwitch&&!(o.path in n)&&a.push({file:o.path,line:t.line,what:`R2 hand-rolled role="switch" on <${t.name}> — use easel's SettingsSwitch`}),/\bsf-toggle\b/.test(t.classText)&&a.push({file:o.path,line:t.line,what:"R3 sf-toggle class in className"});for(const t of f)/\bsf-toggle\b/.test(t.text)&&a.push({file:o.path,line:t.line,what:"R3 sf-toggle class in className"});return{bad:a,settingsSwitch:r}}function d(i){const o=[];return i.text.replace(/\/\*[\s\S]*?\*\//g,s=>s.replace(/[^\n]/g," ")).split(`
2
2
  `).forEach((s,f)=>{/\.sf-toggle\b/.test(s)&&o.push({file:i.path,line:f+1,what:"R3 .sf-toggle selector"})}),o}function b(i,o,n){const{check:s,report:f}=x("one switch"),a=n.notEditor??{};{const e=["const a = <ToggleSwitch value onChange={f} />;",'const b = <button role="switch" aria-checked={x} />;','const c = <span className={`sf-toggle ${on ? "is-on" : ""}`} />;','const d = h("button", { role: "switch", className: "sf-toggle" });',"// a comment about <ToggleSwitch> is not a use",'const ok = <SettingsSwitch checked onChange={f} ariaLabel="X" />;'].join(`
3
3
  `),{bad:c,settingsSwitch:m}=p(i,{path:"fixture.tsx",text:e},{}),w=c.map(l=>l.line);s("R1 flags <ToggleSwitch>",c.some(l=>l.line===1&&l.what.startsWith("R1"))),s('R2 flags <button role="switch">',c.some(l=>l.line===2&&l.what.startsWith("R2"))),s("R3 flags an sf-toggle className",c.some(l=>l.line===3&&l.what.startsWith("R3"))),s("R2 + R3 flag the createElement form",w.filter(l=>l===4).length===2),s("a comment is not a use",!w.includes(5)),s("SettingsSwitch passes and is counted",!w.includes(6)&&m===1);const u=d({path:"fixture.css",text:`/* .sf-toggle in a comment */
4
4
  .sf-toggle.is-on { }
@@ -1,18 +1,18 @@
1
1
  import{ruleChecks as w}from"./rule.js";const k=/className=["'][^"']*?(?<=["'\s-])back(?=["'\s-])/,v=/^\s*(?:<span[^>]*>\s*)?(?:\{\s*)?['"`]?\s*(?:[←‹↰⟵]|Back\b|All of\b)/,C=/<[A-Z]\w*(?:Back|Chevron|ArrowLeft)\w*\b/;function b(l){const a=l.replace(/=>/g,"=:"),e=[];for(const i of a.matchAll(/<button\b[^>]*(?<!\/)>([\s\S]*?)<\/button>/g)){const c=i[1],h=i[0].slice(0,i[0].length-c.length-9);(v.test(c)||k.test(h)||k.test(c)||C.test(c))&&e.push(a.slice(0,i.index).split(`
2
2
  `).length)}return e}const y=l=>/from\s+["']efficient-easel\/(?!render["']|markup["']|data["'])[\w-]+["']/.test(l);function $(l,a){const{check:e,report:i}=w("one up-level link"),c=a.waiting??{},h=[["Locations link",`<button type="button" style={linkBtn} onClick={() => setLocationsFocus({ level: "groups" })}>
3
- \u2190 Locations
4
- </button>`],["Locations link, a name",'<button type="button" style={linkBtn} onClick={onBack}>\u2190 {String(card.name ?? "") || "City"}</button>'],["part panel back",`<button type="button" className="sf-part-back" onClick={onBack}>
3
+ ← Locations
4
+ </button>`],["Locations link, a name",'<button type="button" style={linkBtn} onClick={onBack}>← {String(card.name ?? "") || "City"}</button>'],["part panel back",`<button type="button" className="sf-part-back" onClick={onBack}>
5
5
  <BackChevron />
6
6
  <span>Back to the page</span>
7
7
  </button>`],["easel's old list back",`<button type="button" className="es-list-back" onClick={() => onOpen(null)}>
8
8
  <span className="es-list-back-chev" aria-hidden="true" />
9
9
  {label}
10
10
  </button>`],["a chevron as text",`<button type="button" onClick={() => setOpen(null)}>
11
- <span aria-hidden>\u2039</span>
11
+ <span aria-hidden>‹</span>
12
12
  <span>All panels</span>
13
- </button>`]];for(const[t,n]of h)e(`\xA71 the scanner sees a retired shape: ${t}`,b(n).length===1);const g=[["a select's chevron",`<button className="es-fin es-fsel-button" onClick={() => toggle()}>
13
+ </button>`]];for(const[t,n]of h)e(`§1 the scanner sees a retired shape: ${t}`,b(n).length===1);const g=[["a select's chevron",`<button className="es-fin es-fsel-button" onClick={() => toggle()}>
14
14
  <span className="es-fsel-chev" aria-hidden="true" />
15
- </button>`],["a reorder arrow",'<button type="button" onClick={() => move(-1)}>\u2191</button>'],["a background class",'<button className="sf-background-chip">Fill</button>'],["a self-closing button before a real one",`<button className="sf-swatch" onClick={() => pick(t)} />
15
+ </button>`],["a reorder arrow",'<button type="button" onClick={() => move(-1)}>↑</button>'],["a background class",'<button className="sf-background-chip">Fill</button>'],["a self-closing button before a real one",`<button className="sf-swatch" onClick={() => pick(t)} />
16
16
  <span className="sf-go-back">x</span>
17
- <button>Done</button>`]];for(const[t,n]of g)e(`\xA71 the scanner leaves alone: ${t}`,b(n).length===0);const p=l.filter(t=>/\.(tsx|jsx)$/.test(t.path)&&y(t.text));e(`\xA72 the scan reached the editor code (${p.length} files, at least ${a.minEditorFiles})`,p.length>=a.minEditorFiles,"fewer means the walk or the editor-code test broke, and the check below is vacuous");const u=[],f={};for(const t of p){const n=b(t.text);if(t.path in c)f[t.path]=n.length;else for(const r of n)u.push(`${t.path}:${r}`)}e("\xA72 no hand-built back control in editor code \u2014 use InspectorUpLevel from efficient-easel/chrome",u.length===0,u.join(`
18
- `));for(const[t,{count:n,why:r}]of Object.entries(c)){const o=f[t];e(`\xA73 ${t} still has exactly ${n} waiting (${o??"file not scanned"})`,o===n,`${r}. If they moved, delete this waiting entry.`)}for(const t of a.upLevelUses??[]){const n=l.find(s=>s.path===t.file);if(!e(`\xA74 read ${t.file}`,!!n)||!n)continue;const r=n.text.replace(/=>/g,"=:"),o=[...r.matchAll(/<InspectorUpLevel\b([^>]*?)\/>/g)].map(s=>s[1]);e(`\xA74 ${t.file} draws InspectorUpLevel ${t.words.length} times (${o.length})`,o.length===t.words.length),e("\xA74 \u2026each inline: it sits in the panel body, under the inspector head",o.length>0&&o.every(s=>/\binline\b/.test(s)));const d=o.map(s=>s.match(/name=(\{.*?\}|"[^"]*")\s+onSelect=/)?.[1]??s);e(`\xA74 \u2026with the words it had: ${t.what}`,t.words.every((s,m)=>s.test(d[m]??"")),d.join(" | ")),e("\xA74 \u2026and no linkBtn back is left",!/style=\{linkBtn\}\s+onClick=\{(?:onBack|\(\) =: setLocationsFocus)/.test(r))}return i()}export{b as backControls,y as isEditorCode,$ as oneUpLevelLink};
17
+ <button>Done</button>`]];for(const[t,n]of g)e(`§1 the scanner leaves alone: ${t}`,b(n).length===0);const p=l.filter(t=>/\.(tsx|jsx)$/.test(t.path)&&y(t.text));e(`§2 the scan reached the editor code (${p.length} files, at least ${a.minEditorFiles})`,p.length>=a.minEditorFiles,"fewer means the walk or the editor-code test broke, and the check below is vacuous");const u=[],f={};for(const t of p){const n=b(t.text);if(t.path in c)f[t.path]=n.length;else for(const r of n)u.push(`${t.path}:${r}`)}e("§2 no hand-built back control in editor code — use InspectorUpLevel from efficient-easel/chrome",u.length===0,u.join(`
18
+ `));for(const[t,{count:n,why:r}]of Object.entries(c)){const o=f[t];e(`§3 ${t} still has exactly ${n} waiting (${o??"file not scanned"})`,o===n,`${r}. If they moved, delete this waiting entry.`)}for(const t of a.upLevelUses??[]){const n=l.find(s=>s.path===t.file);if(!e(`§4 read ${t.file}`,!!n)||!n)continue;const r=n.text.replace(/=>/g,"=:"),o=[...r.matchAll(/<InspectorUpLevel\b([^>]*?)\/>/g)].map(s=>s[1]);e(`§4 ${t.file} draws InspectorUpLevel ${t.words.length} times (${o.length})`,o.length===t.words.length),e("§4 …each inline: it sits in the panel body, under the inspector head",o.length>0&&o.every(s=>/\binline\b/.test(s)));const d=o.map(s=>s.match(/name=(\{.*?\}|"[^"]*")\s+onSelect=/)?.[1]??s);e(`§4 …with the words it had: ${t.what}`,t.words.every((s,m)=>s.test(d[m]??"")),d.join(" | ")),e("§4 …and no linkBtn back is left",!/style=\{linkBtn\}\s+onClick=\{(?:onBack|\(\) =: setLocationsFocus)/.test(r))}return i()}export{b as backControls,y as isEditorCode,$ as oneUpLevelLink};
@@ -1,20 +1,20 @@
1
- import{ruleChecks as F}from"./rule.js";const I=new Set("slate gray zinc neutral stone red orange amber yellow lime green emerald teal cyan sky blue indigo violet purple fuchsia pink rose".split(" ")),S=new Set("text bg border ring fill stroke divide outline shadow from via to placeholder accent caret decoration".split(" ")),E=new Set("text w h p px py pt pb pl pr m mx my mt mb ml mr gap top bottom left right inset basis size leading tracking rounded border min-w max-w min-h max-h gap-x gap-y space-x space-y translate-x translate-y".split(" "));function R(t){let a=t;for(let s=0;s<20;s++){const n=a.replace(/var\(\s*--[A-Za-z0-9-]+\s*,((?:[^()]|\([^()]*\))*)\)/g,(d,u)=>`var(--x,${"\xB7".repeat(u.length)})`);if(n===a)break;a=n}return a}function N(t){const a=t.lastIndexOf("]"),s=t.indexOf("/",a+1);if(s===-1)return t;const n=t.slice(s+1);return/^(?:\d{1,3}|\[[^\]]*\])$/.test(n)?t.slice(0,s):t}function P(t){let a=t;for(let s=0;s<12;s++){const n=/^(?:[a-z0-9-]+|[a-z-]+-\[[^\]]*\]|\[[^\]]*\]):(.*)$/.exec(a);if(!n)break;a=n[1]}return N(a.replace(/^!/,"").replace(/^-/,""))}function O(t){const a=P(t),s=/^([a-z-]+)-\[([^\]]+)\]$/.exec(a);if(s){const[,u,o]=s;if(o.includes("\xB7")||/^var\(/.test(o)||/^url\(|^none$|^inherit$|^auto$/.test(o))return null;const p=/^#/.test(o)||/^(?:rgba?|hsla?|oklch|color-mix)\(/.test(o)||/^(?:white|black|transparent|currentColor)$/.test(o),f=/^-?[\d.]+(?:px|rem|em|%|vw|vh|ch|ex|pt|fr)?$|calc\(|^clamp\(/.test(o);return p&&(S.has(u)||E.has(u))?"colour-value":f&&E.has(u)?"size-value":S.has(u)&&!f?"colour-value":E.has(u)?"size-value":null}const n=/^([a-z-]+)-([a-z]+)-(\d{2,3})$/.exec(a);if(n&&S.has(n[1])&&I.has(n[2]))return"palette";const d=/^([a-z-]+)-(white|black)$/.exec(a);return d&&S.has(d[1])?"bw":null}const C=/#(?:[0-9a-fA-F]{8}|[0-9a-fA-F]{6}|[0-9a-fA-F]{4}|[0-9a-fA-F]{3})\b/g,B=/\b(?:rgba?|hsla?|oklch|color-mix)\(/g;function A(t,a,s){const n=t.createSourceFile(a,s,t.ScriptTarget.Latest,!0,t.ScriptKind.TSX),d=[],u=o=>{let p=null;if((t.isStringLiteral(o)||t.isNoSubstitutionTemplateLiteral(o)||t.isTemplateHead(o)||t.isTemplateMiddle(o)||t.isTemplateTail(o))&&(p=o.text),p&&p.length){const f=n.getLineAndCharacterOfPosition(o.getStart(n)).line+1;let r=R(p);for(const c of r.split(/[\s;{}]+/)){if(!c)continue;const w=O(c);w&&(d.push({file:a,line:f,kind:w,text:c}),c.includes("[")&&(r=r.split(c).join("\xB7".repeat(c.length))))}for(const c of[C,B]){c.lastIndex=0;let w;for(;w=c.exec(r);)d.push({file:a,line:f,kind:"colour-value",text:w[0]})}}t.forEachChild(o,u)};return t.forEachChild(n,u),d}function U(t,a,s){let n;if(a.startsWith("@/"))n=a.slice(2);else if(a.startsWith("./")||a.startsWith("../")){const d=s.split("/").slice(0,-1);for(const u of a.split("/"))u===".."?d.pop():u!=="."&&d.push(u);n=d.join("/")}else return null;for(const d of[`${n}.ts`,`${n}.tsx`,`${n}/index.ts`,`${n}/index.tsx`,n])if(t.has(d)&&/\.tsx?$/.test(d)&&!d.endsWith(".d.ts"))return d;return null}function j(t,a){const s=new Set(a.map(o=>o.path)),n=new Map,d=new Set;for(const o of a){const p=t.createSourceFile(o.path,o.text,t.ScriptTarget.Latest,!0,t.ScriptKind.TSX),f=new Map,r=[],c=(i,m)=>{const x=U(s,i,o.path);if(x&&(r.push(x),!!m&&(m.name&&f.set(m.name.text,x),m.namedBindings&&t.isNamedImports(m.namedBindings))))for(const g of m.namedBindings.elements)f.set(g.name.text,x)},w=i=>{if(t.isImportDeclaration(i)&&t.isStringLiteral(i.moduleSpecifier)&&c(i.moduleSpecifier.text,i.importClause),t.isExportDeclaration(i)&&i.moduleSpecifier&&t.isStringLiteral(i.moduleSpecifier)&&c(i.moduleSpecifier.text),t.isCallExpression(i)&&i.expression.kind===t.SyntaxKind.ImportKeyword){const m=i.arguments[0];m&&t.isStringLiteral(m)&&c(m.text)}if(t.isObjectLiteralExpression(i)){const m=i.properties.some(g=>t.isPropertyAssignment(g)&&t.isIdentifier(g.name)&&g.name.text==="type"&&t.isStringLiteral(g.initializer)&&g.initializer.text==="custom"),x=i.properties.find(g=>t.isPropertyAssignment(g)&&t.isIdentifier(g.name)&&g.name.text==="render");if(m&&x){const g=e=>{(t.isJsxOpeningElement(e)||t.isJsxSelfClosingElement(e))&&t.isIdentifier(e.tagName)&&f.has(e.tagName.text)&&d.add(f.get(e.tagName.text)),t.forEachChild(e,g)};g(x.initializer)}}t.forEachChild(i,w)};t.forEachChild(p,w),n.set(o.path,r)}return{customFieldTargets:d,reachableFrom:o=>{const p=new Set,f=[...o];for(;f.length;){const r=f.pop();if(!p.has(r)){p.add(r);for(const c of n.get(r)??[])f.push(c)}}return p}}}const L=["bg-[","url(a","/b.png)]"].join("");function K(t,a,s){const n=a.filter(i=>/\.tsx?$/.test(i.path)&&!i.path.endsWith(".d.ts")),d=j(t,n),u=n.filter(i=>s.editorRoot(i.path)).map(i=>i.path),o=n.filter(i=>s.publishedRoot(i.path)).map(i=>i.path),p=d.reachableFrom(u),f=d.reachableFrom(o),r=[...p].filter(i=>s.panelFile(i)&&!f.has(i)),c=[...d.customFieldTargets].filter(s.panelFile),w=[...new Set([...r,...c])].sort();return{source:n,editorRoots:u,publishedRoots:o,customFields:c,population:w}}function M(t,a,s){const{check:n,report:d}=F("panel colour tokens");{const e=["// a stale slot \u2014 #319 and 13.6:1","/** Theme 1 accent #2e2e2e + black ink */",'const a = "text-[13px] py-[7px]";','const b = "text-[var(--sf-warning,#b45309)]";','const c = "border-[var(--sf-line, rgba(128,128,128,0.3))]";','const d = "text-zinc-400 hover:bg-amber-100 md:text-white";','const e = "z-[200] grid-cols-[1fr_auto]";','const f = { color: "#d85a30", boxShadow: "0 1px 2px rgba(20,20,22,0.10)" };','const g = "text-[#eee] max-w-[170px]";','const h = "bg-black/30 text-zinc-400/50 border-white/10";','const j = "w-2 bg-white/45 hover:bg-white/70";',`const i = "${L} w-1/2";`].join(`
2
- `),l=A(t,"sample.tsx",e).map(b=>`${b.kind}:${b.text}`).sort(),h=["bw:bg-black/30","bw:bg-white/45","bw:border-white/10","bw:hover:bg-white/70","bw:md:text-white","colour-value:#d85a30","colour-value:rgba(","colour-value:text-[#eee]","palette:hover:bg-amber-100","palette:text-zinc-400","palette:text-zinc-400/50","size-value:max-w-[170px]","size-value:py-[7px]","size-value:text-[13px]"];n("the scanner is CALIBRATED \u2014 it reads code, not comments, and not tokens",JSON.stringify(l)===JSON.stringify(h),`the scanner has drifted. It must (1) never read a comment \u2014 the card number #319 and the contrast table are not colours; (2) read var(--tok, #fallback) as the TOKEN it is, at any nesting; (3) decide colour-vs-size from the VALUE, so text-[13px] is a size and text-[#eee] is a colour; (4) ignore z-[\u2026] and grid-cols-[\u2026], which are neither; (5) see THROUGH an opacity modifier \u2014 bg-black/30 is still a hard-coded black \u2014 without mistaking the slash inside ${L} or the one in w-1/2 for one. Got: ${JSON.stringify(l)}`)}const{source:u,editorRoots:o,publishedRoots:p,customFields:f,population:r}=K(t,a,s),{floors:c}=s;n(`the editor entries were found (${o.length}, at least ${c.editorRoots})`,o.length>=c.editorRoots&&o.length>0,"the walk read no editor entry, so every rule below would be an assertion over an empty list and would pass for the wrong reason"),n(`the published entries were found (${p.length}, at least ${c.publishedRoots})`,p.length>=c.publishedRoots&&p.length>0,"without them every component would look editor-only and the population would balloon"),n(`the panel population is ${r.length} files (at least ${c.population})`,r.length>=c.population&&r.length>0,"a population that small is not a clean repository, it is a scan that read nowhere \u2014 something in module resolution broke"),n(`Puck custom fields draw ${f.length} panel files (at least ${c.customFields})`,f.length>=c.customFields,"rule B is what covers every settings field a registry reaches; if it stops matching, the field components leave the population in silence");for(const e of s.anchors??[])n(`${e} is panel chrome, and in the population`,r.includes(e),"it is missing \u2014 the walk has stopped reaching the editor");for(const e of s.drawnByAField??[])n(`${e} is drawn by a Puck custom field, and in the population`,r.includes(e),'it belongs to the panel, but its NAME does not say "panel" \u2014 rule B has stopped working');for(const e of s.opacitySites??[])n(`${e} carries a colour behind an opacity modifier, and stays in the population`,r.includes(e),"it is one of the sites this rule was once blind to; if it has dropped out, the rule is green while ignoring a defect it was written to find");for(const e of s.shopperFacing??[])n(`${e} paints a shopper's page, and is not in the population`,!r.includes(e),"it is not this rule's business, and taking it would make this a storefront gate wearing a panel gate's name");const w=new Map(u.map(e=>[e.path,e.text])),i=new Map;for(const e of r){const l=A(t,e,w.get(e)??"");l.length&&i.set(e,l)}const m=[...i.values()].reduce((e,l)=>e+l.length,0),x=new Map(s.allow.map(e=>[e.file,e])),g=r.reduce((e,l)=>e+(w.get(l)??"").length,0);n(`the scan read ${g} bytes of panel source (at least ${c.bytes})`,g>=c.bytes&&g>0,'that is not the editor. Every "no literals found" below would be meaningless.'),n("the scanner found literals while the allow list still lists files",m>0||s.allow.length===0,"the scanner found nothing anywhere while the allow list still lists files. A scan that reads nothing looks exactly like a clean codebase.");{const e=[];for(const[l,h]of[...i].sort()){if(x.has(l))continue;const b=h.slice(0,6).map($=>` ${l}:${$.line} ${$.text}`);e.push(` ${l} \u2014 ${h.length} literal${h.length===1?"":"s"}
1
+ import{ruleChecks as F}from"./rule.js";const I=new Set("slate gray zinc neutral stone red orange amber yellow lime green emerald teal cyan sky blue indigo violet purple fuchsia pink rose".split(" ")),S=new Set("text bg border ring fill stroke divide outline shadow from via to placeholder accent caret decoration".split(" ")),E=new Set("text w h p px py pt pb pl pr m mx my mt mb ml mr gap top bottom left right inset basis size leading tracking rounded border min-w max-w min-h max-h gap-x gap-y space-x space-y translate-x translate-y".split(" "));function R(t){let a=t;for(let s=0;s<20;s++){const n=a.replace(/var\(\s*--[A-Za-z0-9-]+\s*,((?:[^()]|\([^()]*\))*)\)/g,(d,u)=>`var(--x,${"·".repeat(u.length)})`);if(n===a)break;a=n}return a}function N(t){const a=t.lastIndexOf("]"),s=t.indexOf("/",a+1);if(s===-1)return t;const n=t.slice(s+1);return/^(?:\d{1,3}|\[[^\]]*\])$/.test(n)?t.slice(0,s):t}function P(t){let a=t;for(let s=0;s<12;s++){const n=/^(?:[a-z0-9-]+|[a-z-]+-\[[^\]]*\]|\[[^\]]*\]):(.*)$/.exec(a);if(!n)break;a=n[1]}return N(a.replace(/^!/,"").replace(/^-/,""))}function O(t){const a=P(t),s=/^([a-z-]+)-\[([^\]]+)\]$/.exec(a);if(s){const[,u,o]=s;if(o.includes("·")||/^var\(/.test(o)||/^url\(|^none$|^inherit$|^auto$/.test(o))return null;const p=/^#/.test(o)||/^(?:rgba?|hsla?|oklch|color-mix)\(/.test(o)||/^(?:white|black|transparent|currentColor)$/.test(o),f=/^-?[\d.]+(?:px|rem|em|%|vw|vh|ch|ex|pt|fr)?$|calc\(|^clamp\(/.test(o);return p&&(S.has(u)||E.has(u))?"colour-value":f&&E.has(u)?"size-value":S.has(u)&&!f?"colour-value":E.has(u)?"size-value":null}const n=/^([a-z-]+)-([a-z]+)-(\d{2,3})$/.exec(a);if(n&&S.has(n[1])&&I.has(n[2]))return"palette";const d=/^([a-z-]+)-(white|black)$/.exec(a);return d&&S.has(d[1])?"bw":null}const C=/#(?:[0-9a-fA-F]{8}|[0-9a-fA-F]{6}|[0-9a-fA-F]{4}|[0-9a-fA-F]{3})\b/g,B=/\b(?:rgba?|hsla?|oklch|color-mix)\(/g;function A(t,a,s){const n=t.createSourceFile(a,s,t.ScriptTarget.Latest,!0,t.ScriptKind.TSX),d=[],u=o=>{let p=null;if((t.isStringLiteral(o)||t.isNoSubstitutionTemplateLiteral(o)||t.isTemplateHead(o)||t.isTemplateMiddle(o)||t.isTemplateTail(o))&&(p=o.text),p&&p.length){const f=n.getLineAndCharacterOfPosition(o.getStart(n)).line+1;let r=R(p);for(const c of r.split(/[\s;{}]+/)){if(!c)continue;const w=O(c);w&&(d.push({file:a,line:f,kind:w,text:c}),c.includes("[")&&(r=r.split(c).join("·".repeat(c.length))))}for(const c of[C,B]){c.lastIndex=0;let w;for(;w=c.exec(r);)d.push({file:a,line:f,kind:"colour-value",text:w[0]})}}t.forEachChild(o,u)};return t.forEachChild(n,u),d}function U(t,a,s){let n;if(a.startsWith("@/"))n=a.slice(2);else if(a.startsWith("./")||a.startsWith("../")){const d=s.split("/").slice(0,-1);for(const u of a.split("/"))u===".."?d.pop():u!=="."&&d.push(u);n=d.join("/")}else return null;for(const d of[`${n}.ts`,`${n}.tsx`,`${n}/index.ts`,`${n}/index.tsx`,n])if(t.has(d)&&/\.tsx?$/.test(d)&&!d.endsWith(".d.ts"))return d;return null}function j(t,a){const s=new Set(a.map(o=>o.path)),n=new Map,d=new Set;for(const o of a){const p=t.createSourceFile(o.path,o.text,t.ScriptTarget.Latest,!0,t.ScriptKind.TSX),f=new Map,r=[],c=(i,m)=>{const x=U(s,i,o.path);if(x&&(r.push(x),!!m&&(m.name&&f.set(m.name.text,x),m.namedBindings&&t.isNamedImports(m.namedBindings))))for(const g of m.namedBindings.elements)f.set(g.name.text,x)},w=i=>{if(t.isImportDeclaration(i)&&t.isStringLiteral(i.moduleSpecifier)&&c(i.moduleSpecifier.text,i.importClause),t.isExportDeclaration(i)&&i.moduleSpecifier&&t.isStringLiteral(i.moduleSpecifier)&&c(i.moduleSpecifier.text),t.isCallExpression(i)&&i.expression.kind===t.SyntaxKind.ImportKeyword){const m=i.arguments[0];m&&t.isStringLiteral(m)&&c(m.text)}if(t.isObjectLiteralExpression(i)){const m=i.properties.some(g=>t.isPropertyAssignment(g)&&t.isIdentifier(g.name)&&g.name.text==="type"&&t.isStringLiteral(g.initializer)&&g.initializer.text==="custom"),x=i.properties.find(g=>t.isPropertyAssignment(g)&&t.isIdentifier(g.name)&&g.name.text==="render");if(m&&x){const g=e=>{(t.isJsxOpeningElement(e)||t.isJsxSelfClosingElement(e))&&t.isIdentifier(e.tagName)&&f.has(e.tagName.text)&&d.add(f.get(e.tagName.text)),t.forEachChild(e,g)};g(x.initializer)}}t.forEachChild(i,w)};t.forEachChild(p,w),n.set(o.path,r)}return{customFieldTargets:d,reachableFrom:o=>{const p=new Set,f=[...o];for(;f.length;){const r=f.pop();if(!p.has(r)){p.add(r);for(const c of n.get(r)??[])f.push(c)}}return p}}}const L=["bg-[","url(a","/b.png)]"].join("");function K(t,a,s){const n=a.filter(i=>/\.tsx?$/.test(i.path)&&!i.path.endsWith(".d.ts")),d=j(t,n),u=n.filter(i=>s.editorRoot(i.path)).map(i=>i.path),o=n.filter(i=>s.publishedRoot(i.path)).map(i=>i.path),p=d.reachableFrom(u),f=d.reachableFrom(o),r=[...p].filter(i=>s.panelFile(i)&&!f.has(i)),c=[...d.customFieldTargets].filter(s.panelFile),w=[...new Set([...r,...c])].sort();return{source:n,editorRoots:u,publishedRoots:o,customFields:c,population:w}}function M(t,a,s){const{check:n,report:d}=F("panel colour tokens");{const e=["// a stale slot — #319 and 13.6:1","/** Theme 1 accent #2e2e2e + black ink */",'const a = "text-[13px] py-[7px]";','const b = "text-[var(--sf-warning,#b45309)]";','const c = "border-[var(--sf-line, rgba(128,128,128,0.3))]";','const d = "text-zinc-400 hover:bg-amber-100 md:text-white";','const e = "z-[200] grid-cols-[1fr_auto]";','const f = { color: "#d85a30", boxShadow: "0 1px 2px rgba(20,20,22,0.10)" };','const g = "text-[#eee] max-w-[170px]";','const h = "bg-black/30 text-zinc-400/50 border-white/10";','const j = "w-2 bg-white/45 hover:bg-white/70";',`const i = "${L} w-1/2";`].join(`
2
+ `),l=A(t,"sample.tsx",e).map(b=>`${b.kind}:${b.text}`).sort(),h=["bw:bg-black/30","bw:bg-white/45","bw:border-white/10","bw:hover:bg-white/70","bw:md:text-white","colour-value:#d85a30","colour-value:rgba(","colour-value:text-[#eee]","palette:hover:bg-amber-100","palette:text-zinc-400","palette:text-zinc-400/50","size-value:max-w-[170px]","size-value:py-[7px]","size-value:text-[13px]"];n("the scanner is CALIBRATED — it reads code, not comments, and not tokens",JSON.stringify(l)===JSON.stringify(h),`the scanner has drifted. It must (1) never read a comment — the card number #319 and the contrast table are not colours; (2) read var(--tok, #fallback) as the TOKEN it is, at any nesting; (3) decide colour-vs-size from the VALUE, so text-[13px] is a size and text-[#eee] is a colour; (4) ignore z-[…] and grid-cols-[…], which are neither; (5) see THROUGH an opacity modifier — bg-black/30 is still a hard-coded black — without mistaking the slash inside ${L} or the one in w-1/2 for one. Got: ${JSON.stringify(l)}`)}const{source:u,editorRoots:o,publishedRoots:p,customFields:f,population:r}=K(t,a,s),{floors:c}=s;n(`the editor entries were found (${o.length}, at least ${c.editorRoots})`,o.length>=c.editorRoots&&o.length>0,"the walk read no editor entry, so every rule below would be an assertion over an empty list and would pass for the wrong reason"),n(`the published entries were found (${p.length}, at least ${c.publishedRoots})`,p.length>=c.publishedRoots&&p.length>0,"without them every component would look editor-only and the population would balloon"),n(`the panel population is ${r.length} files (at least ${c.population})`,r.length>=c.population&&r.length>0,"a population that small is not a clean repository, it is a scan that read nowhere — something in module resolution broke"),n(`Puck custom fields draw ${f.length} panel files (at least ${c.customFields})`,f.length>=c.customFields,"rule B is what covers every settings field a registry reaches; if it stops matching, the field components leave the population in silence");for(const e of s.anchors??[])n(`${e} is panel chrome, and in the population`,r.includes(e),"it is missing — the walk has stopped reaching the editor");for(const e of s.drawnByAField??[])n(`${e} is drawn by a Puck custom field, and in the population`,r.includes(e),'it belongs to the panel, but its NAME does not say "panel" — rule B has stopped working');for(const e of s.opacitySites??[])n(`${e} carries a colour behind an opacity modifier, and stays in the population`,r.includes(e),"it is one of the sites this rule was once blind to; if it has dropped out, the rule is green while ignoring a defect it was written to find");for(const e of s.shopperFacing??[])n(`${e} paints a shopper's page, and is not in the population`,!r.includes(e),"it is not this rule's business, and taking it would make this a storefront gate wearing a panel gate's name");const w=new Map(u.map(e=>[e.path,e.text])),i=new Map;for(const e of r){const l=A(t,e,w.get(e)??"");l.length&&i.set(e,l)}const m=[...i.values()].reduce((e,l)=>e+l.length,0),x=new Map(s.allow.map(e=>[e.file,e])),g=r.reduce((e,l)=>e+(w.get(l)??"").length,0);n(`the scan read ${g} bytes of panel source (at least ${c.bytes})`,g>=c.bytes&&g>0,'that is not the editor. Every "no literals found" below would be meaningless.'),n("the scanner found literals while the allow list still lists files",m>0||s.allow.length===0,"the scanner found nothing anywhere while the allow list still lists files. A scan that reads nothing looks exactly like a clean codebase.");{const e=[];for(const[l,h]of[...i].sort()){if(x.has(l))continue;const b=h.slice(0,6).map($=>` ${l}:${$.line} ${$.text}`);e.push(` ${l} — ${h.length} literal${h.length===1?"":"s"}
3
3
  `+b.join(`
4
4
  `)+(h.length>b.length?`
5
- \u2026and ${h.length-b.length} more`:""))}n("NO panel file outside the list carries a colour or size literal",e.length===0,`Hard-coded colour/size literals in editor PANEL components.
5
+ …and ${h.length-b.length} more`:""))}n("NO panel file outside the list carries a colour or size literal",e.length===0,`Hard-coded colour/size literals in editor PANEL components.
6
6
 
7
- The panel's colours and sizes come from tokens \u2014 the --sf-* custom properties the editor's sheets define (efficient-easel/screen.css, and the builder's components/storefront-editor.css) and the .es-* classes efficient-easel draws. A literal here is a private copy of one step of that scale: it does not move when the scale moves, and nothing says it is wrong until somebody looks at the panel beside the campaign designer.
7
+ The panel's colours and sizes come from tokens — the --sf-* custom properties the editor's sheets define (efficient-easel/screen.css, and the builder's components/storefront-editor.css) and the .es-* classes efficient-easel draws. A literal here is a private copy of one step of that scale: it does not move when the scale moves, and nothing says it is wrong until somebody looks at the panel beside the campaign designer.
8
8
 
9
9
  Spell it as a token. If it genuinely cannot be one, add a line to the allow list with KEEP (a design reason) or UNTIL (what has to land), and its exact count.
10
10
 
11
11
  `+e.join(`
12
12
 
13
- `))}{const e=[];for(const l of s.allow){const h=i.get(l.file);if(!r.includes(l.file)){e.push(` ${l.file} is on the list but is no longer a panel component (the editor does not reach it, and no Puck custom field draws it). Delete the line.`);continue}if(!h){e.push(` ${l.file} is CLEAN now \u2014 0 literals, the line still allows ${l.count}. Delete the line: that is what "done" looks like.`);continue}if(h.length>l.count){const b=h.slice(l.count).map($=>`${$.text} @${$.line}`);e.push(` ${l.file} has ${h.length} literals, the line allows ${l.count}. A NEW literal went into a file we already tolerate \u2014 this is exactly how the count creeps back. Spell it as a token; do not raise the number. (e.g. ${b.slice(0,3).join(", ")})`)}h.length<l.count&&e.push(` ${l.file} is down to ${h.length} literals from ${l.count}. Good \u2014 now finish it: either clear the rest and delete the line, or lower the count in this same PR. A line that allows more than the file spends goes on excusing a debt that has been paid.`)}n(`every allow line still describes its file \u2014 counts pinned BOTH ways (${s.allow.length} lines)`,e.length===0,`the allow list is out of date:
13
+ `))}{const e=[];for(const l of s.allow){const h=i.get(l.file);if(!r.includes(l.file)){e.push(` ${l.file} is on the list but is no longer a panel component (the editor does not reach it, and no Puck custom field draws it). Delete the line.`);continue}if(!h){e.push(` ${l.file} is CLEAN now — 0 literals, the line still allows ${l.count}. Delete the line: that is what "done" looks like.`);continue}if(h.length>l.count){const b=h.slice(l.count).map($=>`${$.text} @${$.line}`);e.push(` ${l.file} has ${h.length} literals, the line allows ${l.count}. A NEW literal went into a file we already tolerate — this is exactly how the count creeps back. Spell it as a token; do not raise the number. (e.g. ${b.slice(0,3).join(", ")})`)}h.length<l.count&&e.push(` ${l.file} is down to ${h.length} literals from ${l.count}. Good — now finish it: either clear the rest and delete the line, or lower the count in this same PR. A line that allows more than the file spends goes on excusing a debt that has been paid.`)}n(`every allow line still describes its file — counts pinned BOTH ways (${s.allow.length} lines)`,e.length===0,`the allow list is out of date:
14
14
 
15
15
  ${e.join(`
16
16
 
17
- `)}`)}{const e=[],l=new Set;for(const h of s.allow)l.has(h.file)&&e.push(` ${h.file} is listed twice`),l.add(h.file),h.count<1&&e.push(` ${h.file} pins count ${h.count} \u2014 a line allowing nothing is a deleted line`),h.kind==="KEEP"&&h.why.trim().length<20&&e.push(` ${h.file} is a KEEP with no design behind it. A KEEP says why the literal is RIGHT; if you cannot say that, it is an UNTIL.`),h.kind==="UNTIL"&&h.until.trim().length<20&&e.push(` ${h.file} is an UNTIL with no expiry. A reason explains why a line is here today; an expiry is what GETS IT OUT. Say what has to land.`);n("every allow line carries an EXPIRY, not just a reason",e.length===0,`allow lines are malformed:
17
+ `)}`)}{const e=[],l=new Set;for(const h of s.allow)l.has(h.file)&&e.push(` ${h.file} is listed twice`),l.add(h.file),h.count<1&&e.push(` ${h.file} pins count ${h.count} — a line allowing nothing is a deleted line`),h.kind==="KEEP"&&h.why.trim().length<20&&e.push(` ${h.file} is a KEEP with no design behind it. A KEEP says why the literal is RIGHT; if you cannot say that, it is an UNTIL.`),h.kind==="UNTIL"&&h.until.trim().length<20&&e.push(` ${h.file} is an UNTIL with no expiry. A reason explains why a line is here today; an expiry is what GETS IT OUT. Say what has to land.`);n("every allow line carries an EXPIRY, not just a reason",e.length===0,`allow lines are malformed:
18
18
 
19
19
  ${e.join(`
20
- `)}`)}{const e=i.size,l=r.length-e,h=r.length?Math.round(l/r.length*100):0,b=s.allow.filter(y=>y.kind==="KEEP").length,$=s.allow.filter(y=>y.kind==="UNTIL").length,z=[...i.values()].flat().filter(y=>y.kind!=="size-value").length;n(`PROGRESS \u2014 panel token coverage: ${l}/${r.length} files clean (${h}%) \xB7 ${m} literals left in ${e} file${e===1?"":"s"} (${z} colour + ${m-z} size) \xB7 allow: ${b} KEEP + ${$} UNTIL`,r.length>0);for(const[y,T]of[...i].sort((k,v)=>v[1].length-k[1].length)){const k=T.filter(v=>v.kind!=="size-value").length;n(`PROGRESS ${String(k).padStart(3)} colour ${String(T.length-k).padStart(3)} size ${y}`,!0)}}return d()}export{R as blankVarFallbacks,O as classifyClassToken,M as panelColourTokens,K as panelPopulation,A as scanSource};
20
+ `)}`)}{const e=i.size,l=r.length-e,h=r.length?Math.round(l/r.length*100):0,b=s.allow.filter(y=>y.kind==="KEEP").length,$=s.allow.filter(y=>y.kind==="UNTIL").length,z=[...i.values()].flat().filter(y=>y.kind!=="size-value").length;n(`PROGRESS — panel token coverage: ${l}/${r.length} files clean (${h}%) · ${m} literals left in ${e} file${e===1?"":"s"} (${z} colour + ${m-z} size) · allow: ${b} KEEP + ${$} UNTIL`,r.length>0);for(const[y,T]of[...i].sort((k,v)=>v[1].length-k[1].length)){const k=T.filter(v=>v.kind!=="size-value").length;n(`PROGRESS ${String(k).padStart(3)} colour ${String(T.length-k).padStart(3)} size ${y}`,!0)}}return d()}export{R as blankVarFallbacks,O as classifyClassToken,M as panelColourTokens,K as panelPopulation,A as scanSource};
@@ -1,10 +1,10 @@
1
1
  import{ruleChecks as E}from"./rule.js";function v(r){const s=r.split(`
2
2
  `),n=[];return s.forEach((g,a)=>{if(!/border-(top|bottom|left|right)[a-z-]*\s*:[^;]*\bsolid\b/.test(g))return;let $=0,c="?";for(let i=a-1;i>=0;i-=1)if($+=(s[i].match(/\}/g)??[]).length-(s[i].match(/\{/g)??[]).length,$<0){c=s[i].split("{")[0].trim();let h=i-1;for(;h>=0&&s[h].trim().endsWith(",");)c=`${s[h].trim()} ${c}`,h-=1;break}n.push({line:a+1,selector:c,decl:g.trim()})}),n}const y=r=>r.split(/[\s,+>~]/)[0].trim(),j=(r,s)=>s in r||y(s)in r,O=/\bzinc-|--sf-line\b|--sf-divider\b|--sf-input-border\b|--sf-chip-bg\b|--es-line\b|--es-divider\b|--sf-text-secondary\b/,k=[["utility",/(^|["'`\s])border-(t|b|l|r)($|["'`\s])/],["style object",/border(Top|Bottom|Left|Right)\s*:/],["css in a string",/border-(top|bottom|left|right)\s*:/]];function S(r){return/radius/i.test(r)||!O.test(r)?null:k.find(([,s])=>s.test(r))?.[0]??null}function N(r,s){const{check:n,report:g}=E("panel divider lines"),a=s.allowed??{},$=s.pendingElsewhere??{},c=s.allowedTsx??[];{const e=u=>["border",u].join("-"),t=[".a,",".b + .c {",` ${e("top")}: 1px solid red;`,"}",`.d { ${e("radius")}: 4px; }`].join(`
3
- `),l=v(t);n("the census finds a directional solid border, with its whole multi-line selector",l.length===1&&l[0].selector===".a, .b + .c"&&y(l[0].selector)===".a",JSON.stringify(l));const d=["zinc","200"].join("-"),f=[["a utility",`<div className="${e("t")} border-${d}" />`,"utility"],["a style object",`style={{ ${["border","Top"].join("")}: "1px solid var(--sf-line)" }}`,"style object"],["CSS in a string",`const s = "${e("left")}: 1px solid var(--es-divider)";`,"css in a string"],["a border in the storefront's own ink",`<div className="${e("t")} border-line" />`,null],["a radius",`<div className="${e("t")} border-${d} rounded" style={{ borderRadius: 4 }} />`,null]];for(const[u,o,p]of f)n(`the sweep reads ${u} as ${p??"no hairline"}`,S(o)===p,o)}const i=[];for(const e of s.panelSheets??[]){const t=r.find(o=>o.path===e)?.text??"",l=v(t);n(`${e} was read and parsed`,t.length>1e4&&l.length>0,`${t.length} bytes, ${l.length} directional border(s)`),i.push(...l.map(o=>({...o,sheet:e})));const f=(t.replace(/\/\*[\s\S]*?\*\//g,"").match(/\[class\*="_PuckFields[^"]*"\][^{]*\{/g)??[]).length;n(`${e} reaches for no Puck field wrapper`,f===0,`${f} rule(s) still select \`[class*="_PuckFields\u2026"]\` \u2014 the rows are \`.es-insp-row\` now`);const u=t.replace(/,\s*\n\s*/g,", ");for(const o of s.fullBleed??[]){if(!(o in a))continue;const p=u.indexOf(`
4
- ${o} {`);if(!n(`the allowed full-bleed rule is still in ${e}`,p>=0,o))continue;const x=u.slice(p,u.indexOf("}",p));for(const w of["left","right"])n(`the full-bleed rule cancels the panel's ${w} padding, so it reaches the edge`,new RegExp(`margin-${w}:\\s*calc\\(var\\(--sf-insp-pad-x\\)\\s*\\*\\s*-1\\)`).test(x),"inset, this is the line the owner rejected \u2014 full bleed is the exemption"),n(`\u2026and puts that ${w} padding back inside, so the words stay on the rows' edge`,new RegExp(`padding-${w}:\\s*var\\(--sf-insp-pad-x\\)`).test(x))}}for(const e of s.fullBleed??[])n(`the full-bleed selector is an ALLOWED entry: ${e}`,e in a);if(s.panelSheets?.length){const e=i.filter(t=>!j(a,t.selector)&&!j($,t.selector));n(`no unlisted directional border survives in a panel stylesheet (${i.length} censused)`,e.length===0,`${e.length} neither removed, allowed, nor owed to another card \u2014 delete it and let the rhythm tokens separate, or add it to the allow-list with a reason if it is a control edge:
5
- `+e.map(t=>`${t.sheet}:${t.line} ${t.selector} \u2014 ${t.decl}`).join(`
6
- `))}const h=e=>i.some(t=>t.selector===e||y(t.selector)===e);for(const[e,t]of Object.entries(a))n(`ALLOWED entry ${e} still names a real rule (${t.slice(0,48)}\u2026)`,h(e),"the rule is gone: delete this entry with it");for(const[e,t]of Object.entries($))n(`PENDING entry ${e} is still owed \u2014 ${t}`,h(e),"that card has landed and the rule is gone: delete this entry");const b=r.filter(e=>/\.tsx?$/.test(e.path));n(`the repository's own TS/TSX files were swept (${b.length}, at least ${s.minFiles})`,b.length>=s.minFiles);const m=[];for(const e of b)e.text.split(`
3
+ `),l=v(t);n("the census finds a directional solid border, with its whole multi-line selector",l.length===1&&l[0].selector===".a, .b + .c"&&y(l[0].selector)===".a",JSON.stringify(l));const d=["zinc","200"].join("-"),f=[["a utility",`<div className="${e("t")} border-${d}" />`,"utility"],["a style object",`style={{ ${["border","Top"].join("")}: "1px solid var(--sf-line)" }}`,"style object"],["CSS in a string",`const s = "${e("left")}: 1px solid var(--es-divider)";`,"css in a string"],["a border in the storefront's own ink",`<div className="${e("t")} border-line" />`,null],["a radius",`<div className="${e("t")} border-${d} rounded" style={{ borderRadius: 4 }} />`,null]];for(const[u,o,p]of f)n(`the sweep reads ${u} as ${p??"no hairline"}`,S(o)===p,o)}const i=[];for(const e of s.panelSheets??[]){const t=r.find(o=>o.path===e)?.text??"",l=v(t);n(`${e} was read and parsed`,t.length>1e4&&l.length>0,`${t.length} bytes, ${l.length} directional border(s)`),i.push(...l.map(o=>({...o,sheet:e})));const f=(t.replace(/\/\*[\s\S]*?\*\//g,"").match(/\[class\*="_PuckFields[^"]*"\][^{]*\{/g)??[]).length;n(`${e} reaches for no Puck field wrapper`,f===0,`${f} rule(s) still select \`[class*="_PuckFields…"]\` — the rows are \`.es-insp-row\` now`);const u=t.replace(/,\s*\n\s*/g,", ");for(const o of s.fullBleed??[]){if(!(o in a))continue;const p=u.indexOf(`
4
+ ${o} {`);if(!n(`the allowed full-bleed rule is still in ${e}`,p>=0,o))continue;const x=u.slice(p,u.indexOf("}",p));for(const w of["left","right"])n(`the full-bleed rule cancels the panel's ${w} padding, so it reaches the edge`,new RegExp(`margin-${w}:\\s*calc\\(var\\(--sf-insp-pad-x\\)\\s*\\*\\s*-1\\)`).test(x),"inset, this is the line the owner rejected — full bleed is the exemption"),n(`…and puts that ${w} padding back inside, so the words stay on the rows' edge`,new RegExp(`padding-${w}:\\s*var\\(--sf-insp-pad-x\\)`).test(x))}}for(const e of s.fullBleed??[])n(`the full-bleed selector is an ALLOWED entry: ${e}`,e in a);if(s.panelSheets?.length){const e=i.filter(t=>!j(a,t.selector)&&!j($,t.selector));n(`no unlisted directional border survives in a panel stylesheet (${i.length} censused)`,e.length===0,`${e.length} neither removed, allowed, nor owed to another card — delete it and let the rhythm tokens separate, or add it to the allow-list with a reason if it is a control edge:
5
+ `+e.map(t=>`${t.sheet}:${t.line} ${t.selector} — ${t.decl}`).join(`
6
+ `))}const h=e=>i.some(t=>t.selector===e||y(t.selector)===e);for(const[e,t]of Object.entries(a))n(`ALLOWED entry ${e} still names a real rule (${t.slice(0,48)}…)`,h(e),"the rule is gone: delete this entry with it");for(const[e,t]of Object.entries($))n(`PENDING entry ${e} is still owed — ${t}`,h(e),"that card has landed and the rule is gone: delete this entry");const b=r.filter(e=>/\.tsx?$/.test(e.path));n(`the repository's own TS/TSX files were swept (${b.length}, at least ${s.minFiles})`,b.length>=s.minFiles);const m=[];for(const e of b)e.text.split(`
7
7
  `).forEach((t,l)=>{const d=S(t);d&&(c.some(f=>f.file===e.path&&t.includes(f.util))||m.push(`${e.path}:${l+1} (${d}) ${t.trim().slice(0,80)}`))});n("no editor panel file draws an unlisted directional border",m.length===0,`${m.length} found:
8
8
  ${m.join(`
9
- `)}`);for(const e of c){const t=b.find(l=>l.path===e.file)?.text??"";n(`ALLOWED_TSX entry ${e.file} ${e.util} still names a real line (${e.why.slice(0,40)}\u2026)`,t.split(`
9
+ `)}`);for(const e of c){const t=b.find(l=>l.path===e.file)?.text??"";n(`ALLOWED_TSX entry ${e.file} ${e.util} still names a real line (${e.why.slice(0,40)}…)`,t.split(`
10
10
  `).some(l=>k.some(([,d])=>d.test(l))&&l.includes(e.util)),"the line is gone: delete this entry")}return g()}export{N as panelNoDividerLines};
@@ -1 +1 @@
1
- import{isRuleSource as U,ruleChecks as G}from"./rule.js";import{readRules as K}from"./support/css-rules.js";import{stripComments as V}from"./support/css-source.js";import{applicable as q,census as J,deriveShapes as L,derefVars as Q,importsOf as B,readVars as X,resolve as O,splitDecls as W}from"./support/panel-select-cascade.js";const S="--sf-select-arrow",R=["background-image","background-size","background-repeat","background-position"],I=l=>/(^|[\s,>+~(])select(?![\w-])/.test(l);function Y(l,a){return typeof a!="string"?{sheet:a,ours:!1}:{sheet:l.find(o=>o.path===a),ours:!0}}const Z=l=>l.filter(a=>/\.(tsx?|mts)$/.test(a.path)&&!U(a.path)),P=(l,a)=>l===`@/${a}`||l.endsWith(`/${a}`)||l===`./${a.split("/").pop()}`;function oe(l,a,o,n){const x=new Set;for(const j of o){const k=a.find(r=>r.path===j);if(k)for(const r of B(l,k))r.endsWith(".css")&&!P(r,n)&&!r.startsWith("@/")&&!r.startsWith(".")&&x.add(r)}return[...x].sort()}function ae(l,a,o){const{check:n,report:x}=G("panel one arrow"),{sheet:j,ours:k}=Y(a,o.panelSheet),r=j?.path??(typeof o.panelSheet=="string"?o.panelSheet:o.panelSheet.path),{population:g,helperDeclared:N}=J(l,Z(a),o.selectHelper),T=g.filter(e=>e.engineDrawn),p=g.filter(e=>!e.engineDrawn);a.some(e=>e.path===o.selectHelper.file)&&n(`${o.selectHelper.file} declares the house \`select\` helper`,N,"the census has no helper to resolve (B) against"),n(`the panel draws house-drawn selects (${p.length}, at least ${o.houseDrawnFloor})`,p.length>=o.houseDrawnFloor&&(p.length>0||o.houseDrawnFloor===0),`${p.length} found: ${p.map(e=>`${e.file}:${e.line}`).join(", ")||"(none)"} \u2014 with none, this card's rows do not exist and \xA73 passes empty`);const v=V(j?.text??""),$=X(v);n(`${r} was read`,v.length>1e4,`${v.length} bytes`);const D=K(v),i=L(g,v),F=i.reduce((e,t)=>e+t.rows,0);n(`every house-drawn select is in exactly one shape, and no engine-drawn field is in any (${i.map(e=>e.name).join(", ")})`,F===p.length,`shapes hold ${F} row(s), the census found ${p.length} house-drawn`);const u=new Map,E=new Map,y=[],M=[];for(const e of i){const t=q(D,e,r,0,"rest"),s=q(D,e,r,0,"hover");y.push(...t.unreadable,...s.unreadable),M.push(...s.undecided);const m={appearance:O(t.applied,"appearance",$).value};for(const h of R)m[h]=O(t.applied,h,$).value;m["@from"]=O(t.applied,"background-image",$).from,u.set(e.name,m);const c={};for(const h of R)c[h]=O(s.applied,h,$).value;E.set(e.name,c)}const f=(e,t)=>e.get(t.name);n("every rule that can reach a panel select was understood",y.length===0,`could not parse: ${[...new Set(y)].slice(0,4).join(" \xB7 ")}${y.length>4?` (+${y.length-4} more)`:""} \u2014 a rule this model skips is a rule it lies about`);for(const e of i){const t=f(u,e);n(`${e.name}: the browser draws no arrow of its own (appearance: none)`,t.appearance==="none",`appearance resolves to \`${t.appearance}\` on ${e.rows} row(s) \u2014 the platform arrow is still painted, so an added image would sit beside it`)}for(const e of i){const t=f(u,e);n(`${e.name}: an arrow IS drawn`,/url\(/.test(t["background-image"]),`background-image resolves to \`${t["background-image"]}\` on ${e.rows} row(s) \u2014 a row with no arrow reads as a stylesheet that failed to load, and taking the arrow away undoes the owner's 2026-09-01 ruling`)}const A=f(u,i[0]);for(const e of["appearance",...R]){const t=i.filter(s=>f(u,s)[e]!==A[e]);n(`every shape resolves the same \`${e}\``,t.length===0,`${i[0].name} (${i[0].rows} row(s)) says \`${A[e].slice(0,44)}\`, but `+t.map(s=>`${s.name} (${s.rows} row(s)) says \`${f(u,s)[e].slice(0,44)}\``).join("; "))}const C=e=>e["background-repeat"]!=="no-repeat"||e["background-size"]==="auto";for(const e of i){const t=f(u,e);n(`${e.name}: the arrow is never tiled (66397)`,!C(t),`repeat=${t["background-repeat"]} size=${t["background-size"]} \u2014 a full-size tile repeats ~2.5x across a 248px row and draws a bar of triangles`)}for(const e of i){const t=f(E,e);n(`${e.name}: the arrow is never tiled under the pointer either (66397)`,/url\(/.test(t["background-image"])&&!C(t),`hovered image=${t["background-image"].slice(0,40)} repeat=${t["background-repeat"]} size=${t["background-size"]} \u2014 this is the dark bar with three triangles, in the state it was filed in`)}const H=M.filter(e=>W(e.decls).some(t=>t.prop==="background"||R.includes(t.prop)));if(n("no rule this model cannot judge decides a hovered arrow property",H.length===0,`${[...new Set(H.map(e=>`@media ${e.media} { ${e.selector} }`))].join(" \xB7 ")} \u2014 an unjudged media query holding one of these properties makes (e) a guess`),k){const e=D.flatMap(t=>t.selector.split(",").map(s=>s.trim()).filter(s=>I(s)).filter(()=>W(t.decls).some(s=>s.prop==="background")).map(s=>t.media?`${t.media} { ${s} }`:s));n("no rule that names the `select` element uses the `background` shorthand",e.length===0,`${e.join(" \xB7 ")} \u2014 a shorthand resets background-size/-repeat/-position/-image; set \`background-color\` instead (card 66397)`)}if(o.editorEntries!==void 0){const e=/^@measured\/puck(\/|$)/,t=new Set;for(const c of o.editorEntries){const h=a.find(d=>d.path===c),w=h?B(l,h):[];n(`${c} loads ${r}`,w.some(d=>P(d,r)),h?"\xA73 resolves the panel stylesheet's cascade, and this editor does not load it":"the entry is not among the files");const b=w.filter(d=>e.test(d));n(`${c} imports nothing from Puck's package`,b.length===0,`imports ${b.join(", ")} \u2014 Puck was removed in blueprint 6.3, and \xA73 no longer models its stylesheet`);for(const d of w)d.endsWith(".css")&&!P(d,r)&&t.add(d)}const s=[],m=[];for(const c of t){const w=(c.startsWith("@/")?a.find(b=>b.path===c.slice(2))?.text:void 0)??o.loadedSheets?.[c]??null;if(w===null){m.push(c);continue}for(const b of K(V(w)))for(const d of b.selector.split(","))I(d)&&s.push(`${c}: ${d.trim()}`)}n(`every other stylesheet the editors load was read (${t.size})`,m.length===0,`could not resolve ${m.join(", ")} \u2014 a sheet this rule cannot open is a cascade it cannot rule out`),n("no other stylesheet the editors load has a rule naming the `select` element",s.length===0,`${s.slice(0,4).join(" \xB7 ")}${s.length>4?` (+${s.length-4} more)`:""} \u2014 \xA73 resolves ${r} alone, so this rule is invisible to every answer above`)}const z=Q($.get(S)??"",$);if(k){const e=D.flatMap(t=>W(t.decls)).filter(t=>t.prop===S);n(`${r} declares \`${S}\` exactly once, as an image`,e.length===1&&/^url\(/.test(z),`${e.length} declaration(s), value \`${z.slice(0,60)||"(none)"}\` \u2014 the one arrow needs one definition`)}for(const e of i){const t=f(u,e)["background-image"];n(`${e.name}: the arrow it draws is \`var(${S})\`, glyph for glyph`,z!==""&&t===z,`resolves \`${t.slice(0,60)}\` \u2014 a redrawn arrow is the SS seat's call, made once in \`${S}\`, not in a rule of its own`)}const _=p.flatMap(e=>e.classes.filter(t=>/^(appearance-|bg-(none|\[|gradient|linear|radial|conic))/.test(t)).map(t=>`${e.file}:${e.line} \`${t}\``));return n("no panel select decides its arrow with a utility class",_.length===0,`${_.join(", ")} \u2014 \xA73 reads only ${r}`),n(`the population came from the program, not from a number typed here (${g.length}, at least ${o.populationFloor})`,g.length>=o.populationFloor&&g.length>0,`${g.length} dropdowns found (${p.length} house-drawn, ${T.length} engine-drawn) \u2014 a census that collapses to a handful means the walk stopped reading`),x()}export{Z as censusFiles,oe as editorPackageSheets,I as namesSelectElement,ae as panelOneArrow,Y as panelSheetOf};
1
+ import{isRuleSource as U,ruleChecks as G}from"./rule.js";import{readRules as K}from"./support/css-rules.js";import{stripComments as V}from"./support/css-source.js";import{applicable as q,census as J,deriveShapes as L,derefVars as Q,importsOf as B,readVars as X,resolve as O,splitDecls as W}from"./support/panel-select-cascade.js";const S="--sf-select-arrow",R=["background-image","background-size","background-repeat","background-position"],I=l=>/(^|[\s,>+~(])select(?![\w-])/.test(l);function Y(l,a){return typeof a!="string"?{sheet:a,ours:!1}:{sheet:l.find(o=>o.path===a),ours:!0}}const Z=l=>l.filter(a=>/\.(tsx?|mts)$/.test(a.path)&&!U(a.path)),P=(l,a)=>l===`@/${a}`||l.endsWith(`/${a}`)||l===`./${a.split("/").pop()}`;function oe(l,a,o,n){const x=new Set;for(const j of o){const k=a.find(r=>r.path===j);if(k)for(const r of B(l,k))r.endsWith(".css")&&!P(r,n)&&!r.startsWith("@/")&&!r.startsWith(".")&&x.add(r)}return[...x].sort()}function ae(l,a,o){const{check:n,report:x}=G("panel one arrow"),{sheet:j,ours:k}=Y(a,o.panelSheet),r=j?.path??(typeof o.panelSheet=="string"?o.panelSheet:o.panelSheet.path),{population:g,helperDeclared:N}=J(l,Z(a),o.selectHelper),T=g.filter(e=>e.engineDrawn),p=g.filter(e=>!e.engineDrawn);a.some(e=>e.path===o.selectHelper.file)&&n(`${o.selectHelper.file} declares the house \`select\` helper`,N,"the census has no helper to resolve (B) against"),n(`the panel draws house-drawn selects (${p.length}, at least ${o.houseDrawnFloor})`,p.length>=o.houseDrawnFloor&&(p.length>0||o.houseDrawnFloor===0),`${p.length} found: ${p.map(e=>`${e.file}:${e.line}`).join(", ")||"(none)"} — with none, this card's rows do not exist and §3 passes empty`);const v=V(j?.text??""),$=X(v);n(`${r} was read`,v.length>1e4,`${v.length} bytes`);const D=K(v),i=L(g,v),F=i.reduce((e,t)=>e+t.rows,0);n(`every house-drawn select is in exactly one shape, and no engine-drawn field is in any (${i.map(e=>e.name).join(", ")})`,F===p.length,`shapes hold ${F} row(s), the census found ${p.length} house-drawn`);const u=new Map,E=new Map,y=[],M=[];for(const e of i){const t=q(D,e,r,0,"rest"),s=q(D,e,r,0,"hover");y.push(...t.unreadable,...s.unreadable),M.push(...s.undecided);const m={appearance:O(t.applied,"appearance",$).value};for(const h of R)m[h]=O(t.applied,h,$).value;m["@from"]=O(t.applied,"background-image",$).from,u.set(e.name,m);const c={};for(const h of R)c[h]=O(s.applied,h,$).value;E.set(e.name,c)}const f=(e,t)=>e.get(t.name);n("every rule that can reach a panel select was understood",y.length===0,`could not parse: ${[...new Set(y)].slice(0,4).join(" · ")}${y.length>4?` (+${y.length-4} more)`:""} — a rule this model skips is a rule it lies about`);for(const e of i){const t=f(u,e);n(`${e.name}: the browser draws no arrow of its own (appearance: none)`,t.appearance==="none",`appearance resolves to \`${t.appearance}\` on ${e.rows} row(s) — the platform arrow is still painted, so an added image would sit beside it`)}for(const e of i){const t=f(u,e);n(`${e.name}: an arrow IS drawn`,/url\(/.test(t["background-image"]),`background-image resolves to \`${t["background-image"]}\` on ${e.rows} row(s) — a row with no arrow reads as a stylesheet that failed to load, and taking the arrow away undoes the owner's 2026-09-01 ruling`)}const A=f(u,i[0]);for(const e of["appearance",...R]){const t=i.filter(s=>f(u,s)[e]!==A[e]);n(`every shape resolves the same \`${e}\``,t.length===0,`${i[0].name} (${i[0].rows} row(s)) says \`${A[e].slice(0,44)}\`, but `+t.map(s=>`${s.name} (${s.rows} row(s)) says \`${f(u,s)[e].slice(0,44)}\``).join("; "))}const C=e=>e["background-repeat"]!=="no-repeat"||e["background-size"]==="auto";for(const e of i){const t=f(u,e);n(`${e.name}: the arrow is never tiled (66397)`,!C(t),`repeat=${t["background-repeat"]} size=${t["background-size"]} — a full-size tile repeats ~2.5x across a 248px row and draws a bar of triangles`)}for(const e of i){const t=f(E,e);n(`${e.name}: the arrow is never tiled under the pointer either (66397)`,/url\(/.test(t["background-image"])&&!C(t),`hovered image=${t["background-image"].slice(0,40)} repeat=${t["background-repeat"]} size=${t["background-size"]} — this is the dark bar with three triangles, in the state it was filed in`)}const H=M.filter(e=>W(e.decls).some(t=>t.prop==="background"||R.includes(t.prop)));if(n("no rule this model cannot judge decides a hovered arrow property",H.length===0,`${[...new Set(H.map(e=>`@media ${e.media} { ${e.selector} }`))].join(" · ")} — an unjudged media query holding one of these properties makes (e) a guess`),k){const e=D.flatMap(t=>t.selector.split(",").map(s=>s.trim()).filter(s=>I(s)).filter(()=>W(t.decls).some(s=>s.prop==="background")).map(s=>t.media?`${t.media} { ${s} }`:s));n("no rule that names the `select` element uses the `background` shorthand",e.length===0,`${e.join(" · ")} — a shorthand resets background-size/-repeat/-position/-image; set \`background-color\` instead (card 66397)`)}if(o.editorEntries!==void 0){const e=/^@measured\/puck(\/|$)/,t=new Set;for(const c of o.editorEntries){const h=a.find(d=>d.path===c),w=h?B(l,h):[];n(`${c} loads ${r}`,w.some(d=>P(d,r)),h?"§3 resolves the panel stylesheet's cascade, and this editor does not load it":"the entry is not among the files");const b=w.filter(d=>e.test(d));n(`${c} imports nothing from Puck's package`,b.length===0,`imports ${b.join(", ")} — Puck was removed in blueprint 6.3, and §3 no longer models its stylesheet`);for(const d of w)d.endsWith(".css")&&!P(d,r)&&t.add(d)}const s=[],m=[];for(const c of t){const w=(c.startsWith("@/")?a.find(b=>b.path===c.slice(2))?.text:void 0)??o.loadedSheets?.[c]??null;if(w===null){m.push(c);continue}for(const b of K(V(w)))for(const d of b.selector.split(","))I(d)&&s.push(`${c}: ${d.trim()}`)}n(`every other stylesheet the editors load was read (${t.size})`,m.length===0,`could not resolve ${m.join(", ")} — a sheet this rule cannot open is a cascade it cannot rule out`),n("no other stylesheet the editors load has a rule naming the `select` element",s.length===0,`${s.slice(0,4).join(" · ")}${s.length>4?` (+${s.length-4} more)`:""} — §3 resolves ${r} alone, so this rule is invisible to every answer above`)}const z=Q($.get(S)??"",$);if(k){const e=D.flatMap(t=>W(t.decls)).filter(t=>t.prop===S);n(`${r} declares \`${S}\` exactly once, as an image`,e.length===1&&/^url\(/.test(z),`${e.length} declaration(s), value \`${z.slice(0,60)||"(none)"}\` — the one arrow needs one definition`)}for(const e of i){const t=f(u,e)["background-image"];n(`${e.name}: the arrow it draws is \`var(${S})\`, glyph for glyph`,z!==""&&t===z,`resolves \`${t.slice(0,60)}\` — a redrawn arrow is the SS seat's call, made once in \`${S}\`, not in a rule of its own`)}const _=p.flatMap(e=>e.classes.filter(t=>/^(appearance-|bg-(none|\[|gradient|linear|radial|conic))/.test(t)).map(t=>`${e.file}:${e.line} \`${t}\``));return n("no panel select decides its arrow with a utility class",_.length===0,`${_.join(", ")} — §3 reads only ${r}`),n(`the population came from the program, not from a number typed here (${g.length}, at least ${o.populationFloor})`,g.length>=o.populationFloor&&g.length>0,`${g.length} dropdowns found (${p.length} house-drawn, ${T.length} engine-drawn) — a census that collapses to a handful means the walk stopped reading`),x()}export{Z as censusFiles,oe as editorPackageSheets,I as namesSelectElement,ae as panelOneArrow,Y as panelSheetOf};
@@ -1,2 +1,2 @@
1
- import{censusFiles as Q,panelSheetOf as X}from"./panel-one-arrow.js";import{ruleChecks as Y}from"./rule.js";import{readRules as I}from"./support/css-rules.js";import{stripComments as K}from"./support/css-source.js";import{applicable as P,census as ee,chainInMarkup as te,compound as q,deriveShapes as oe,readVars as ne,resolve as A,splitCombinators as B,splitDecls as F}from"./support/panel-select-cascade.js";const y=["background-color","background-image","border-color"],se=["background-image","border-color"],j="--sf-hover",C="--sf-select-arrow",m=p=>{const u=/fill='([^']*)'/.exec(p)?.[1];return u?`url(<svg \u2026 fill='${u}'>)`:p.length>46?`${p.slice(0,46)}\u2026`:p};function ie(p,u,r){const{check:s,report:Z}=Y("panel one hover"),{sheet:N,ours:E}=X(u,r.panelSheet),k=N?.path??(typeof r.panelSheet=="string"?r.panelSheet:r.panelSheet.path),{population:f,helperDeclared:G}=ee(p,Q(u),r.selectHelper),J=f.filter(e=>e.engineDrawn),$=f.filter(e=>!e.engineDrawn);u.some(e=>e.path===r.selectHelper.file)&&s(`${r.selectHelper.file} declares the house \`select\` helper`,G,"the census has no helper to resolve against"),s(`the panel draws house-drawn selects (${$.length}, at least ${r.houseDrawnFloor})`,$.length>=r.houseDrawnFloor&&($.length>0||r.houseDrawnFloor===0),`${$.length} found \u2014 with none, this card's rows do not exist`),s(`the population came from the program, not from a number typed here (${f.length}, at least ${r.populationFloor})`,f.length>=r.populationFloor&&f.length>0,`${f.length} dropdowns found (${$.length} house-drawn, ${J.length} engine-drawn) \u2014 a census that collapses to a handful means the walk stopped reading`);const a=r.screenSheet,S=K(a?.text??""),w=K(N?.text??""),R=a?`${S}
2
- ${w}`:w,v=ne(R);s(`${k} was read`,w.length>1e4,`${w.length} bytes`),a&&s(`${a.path} was read`,S.length>1e4,`${S.length} bytes`);const D=I(S),L=I(w),c=oe(f,R),b=(e,t)=>{const n=P(D,e,a?.path??"",0,t),o=P(L,e,k,D.length,t);return{applied:[...n.applied,...o.applied],unreadable:[...n.unreadable,...o.unreadable],undecided:[...n.undecided,...o.undecided]}},T=(e,t)=>Object.fromEntries(t.map(n=>[n,A(e.applied,n,v).value])),x=new Map,g=new Map,H=[],V=[];for(const e of c){const t=b(e,"rest"),n=b(e,"hover");H.push(...t.unreadable,...n.unreadable),V.push(...n.undecided),x.set(e.name,T(t,y)),g.set(e.name,{...T(n,y),"@from":A(n.applied,"background-color",v).from})}const d=(e,t)=>e.get(t.name);s("every rule that can reach a panel select was understood",H.length===0,`could not parse: ${[...new Set(H)].slice(0,4).join(" \xB7 ")} \u2014 a rule this model skips is a rule it lies about`);const U=V.filter(e=>F(e.decls).some(t=>y.includes(t.prop)||t.prop==="background"||t.prop==="border"));s("no rule this model cannot judge decides a hovered value",U.length===0,`${U.map(e=>`@media ${e.media} { ${e.selector} }`).join(" \xB7 ")} \u2014 an unjudged media query holding one of these properties makes every answer above a guess`);const M=c[0];for(const e of y){const t=d(g,M)[e],n=c.filter(o=>d(g,o)[e]!==t);s(`every shape resolves the same hovered \`${e}\``,n.length===0,`${M.name} (${M.rows} row(s)) says \`${m(t)}\`, but `+n.map(o=>`${o.name} (${o.rows} row(s)) says \`${m(d(g,o)[e])}\``).join("; "))}for(const e of c){const t=d(x,e),n=d(g,e);s(`${e.name}: the ground actually changes under the pointer`,t["background-color"]!==n["background-color"],`rest and hover both resolve \`${m(n["background-color"])}\` on ${e.rows} row(s) \u2014 a row that does not answer the pointer is the other half of this card's defect`)}const O=v.get(j);(E||a)&&s(`the ${a?"editor's sheets declare":"panel declares"} \`${j}\``,O!==void 0,`the token the ruling names is not in ${a?`${a.path} or ${k}`:"the panel stylesheet"}`);for(const e of c){const t=d(g,e);s(`${e.name}: the hovered ground is the panel's own \`var(${j})\``,O!==void 0&&t["background-color"]===O,`resolves to \`${m(t["background-color"])}\`, and \`${j}\` is \`${O??"(not declared)"}\` \u2014 option D is the panel's own hover ground, picked by Lewis on 2026-09-02, and a near miss is not it`)}for(const e of c){const t=d(x,e),n=d(g,e);for(const o of se)s(`${e.name}: \`${o}\` is unchanged under the pointer`,t[o]===n[o],`rest \`${m(t[o])}\` -> hover \`${m(n[o])}\` on ${e.rows} row(s) \u2014 Lewis chose D (ground only) over B (border jump) and C (darker glyph) from pictures; moving this is re-deciding it`)}if(E){const e=l=>l.replace(/\s+/g," ").replace(/^url\(([\s\S]*)\)$/,"$1").replace(/^["']|["']$/g,"").trim(),t=v.get(C),n=e(t??"");s(`the panel declares \`${C}\` as an image`,t!==void 0&&/^url\(/.test(t.trim())&&n!=="",`\`${C}\` is \`${m(t??"(not declared)")}\` \u2014 the one arrow needs one definition`);const o=n===""?-1:R.split(n).length-1;s(`the arrow glyph is written ONCE in ${k}${a?` and ${a.path}`:""}`,o===1,`found ${o} \u2014 pasting the arrow into a second rule is exactly what card 66399 removed. Read \`var(${C})\` instead`);const i=[...D,...L].filter(l=>/select/.test(l.selector)&&F(l.decls).some(_=>/url\(\s*["']?data:/.test(_.value)));s("no select rule holds the arrow inline; every one reads the token",i.length===0,`${i.map(l=>l.selector.trim()).join(" \xB7 ")} still writes a data URI`)}const W=b(c[0],"hover").applied.filter(e=>/:hover/.test(e.selector)),h=W[0];if(E){s("the panel declares a select hover rule at all",W.length>0,"no `\u2026select:hover` rule in the panel stylesheet reaches a house row \u2014 they do nothing under the pointer, which is this card's defect");const e=h?F(h.decls):[],t=h?B(h.selector).slice(0,-1).flatMap(o=>q(o)?.classes??[]):[];s("the hover rule uses no `!important`",h!==void 0&&!/!\s*important/i.test(h.decls),"an `!important` here beats every other state the panel paints, including ones this card was never given");const n=e.map(o=>o.prop).filter(o=>!y.includes(o));s("the hover rule declares only the three properties the ruling names",h!==void 0&&n.length===0,`also declares ${n.join(", ")} \u2014 the ruling is a ground colour, plus the arrow and border restated at rest. Anything else is a state this card was not given`),s("the hover rule is confined to the inspector body",t.includes("sf-insp-body"),`its ancestor chain is \`${t.join(" ")}\` \u2014 without \`.sf-insp-body\` it reaches every select in the editor, not the panel's`)}for(const e of c){const t=A(b(e,"rest").applied,"background-color",v).from;s(`${e.name}: the resting ground is NOT decided by the hover rule`,h===void 0||!t.includes(h.selector),`rest resolves from \`${t}\` \u2014 the pointer state has leaked into the row at rest`)}if(r.chrome){const e=new Set;for(const o of c)for(const i of["rest","hover"])for(const l of b(o,i).applied)for(const _ of B(l.selector).slice(0,-1))for(const z of q(_)?.classes??[])z.startsWith("@")||e.add(z);s(`the rules that resolved lean on wrapper classes (${[...e].map(o=>`.${o}`).join(" ")||"none"})`,e.size>0);const t=te(p,u,[...e]),n=new Set(D.flatMap(o=>[...o.selector.matchAll(/\.(-?[_a-zA-Z][\w-]*)/g)].map(i=>i[1])));for(const o of e){const i=(t.get(o)??[]).length>0,l=!i&&n.has(o);s(`\`.${o}\` is still written above a panel select in the editor's markup${l?` \u2014 the screen's (${a?.path} styles it)`:""}`,i||l,`no component writes \`${o}\` as a className any more${a?`, and ${a.path} styles no \`.${o}\` either`:""} \u2014 the rules leaning on it still parse, still look right, and match none of the ${$.length} house-drawn rows`)}}return Z()}export{ie as panelOneHover};
1
+ import{censusFiles as Q,panelSheetOf as X}from"./panel-one-arrow.js";import{ruleChecks as Y}from"./rule.js";import{readRules as I}from"./support/css-rules.js";import{stripComments as K}from"./support/css-source.js";import{applicable as P,census as ee,chainInMarkup as te,compound as q,deriveShapes as oe,readVars as ne,resolve as A,splitCombinators as B,splitDecls as F}from"./support/panel-select-cascade.js";const y=["background-color","background-image","border-color"],se=["background-image","border-color"],j="--sf-hover",C="--sf-select-arrow",m=p=>{const u=/fill='([^']*)'/.exec(p)?.[1];return u?`url(<svg … fill='${u}'>)`:p.length>46?`${p.slice(0,46)}…`:p};function ie(p,u,r){const{check:s,report:Z}=Y("panel one hover"),{sheet:N,ours:E}=X(u,r.panelSheet),k=N?.path??(typeof r.panelSheet=="string"?r.panelSheet:r.panelSheet.path),{population:f,helperDeclared:G}=ee(p,Q(u),r.selectHelper),J=f.filter(e=>e.engineDrawn),$=f.filter(e=>!e.engineDrawn);u.some(e=>e.path===r.selectHelper.file)&&s(`${r.selectHelper.file} declares the house \`select\` helper`,G,"the census has no helper to resolve against"),s(`the panel draws house-drawn selects (${$.length}, at least ${r.houseDrawnFloor})`,$.length>=r.houseDrawnFloor&&($.length>0||r.houseDrawnFloor===0),`${$.length} found — with none, this card's rows do not exist`),s(`the population came from the program, not from a number typed here (${f.length}, at least ${r.populationFloor})`,f.length>=r.populationFloor&&f.length>0,`${f.length} dropdowns found (${$.length} house-drawn, ${J.length} engine-drawn) — a census that collapses to a handful means the walk stopped reading`);const a=r.screenSheet,S=K(a?.text??""),w=K(N?.text??""),R=a?`${S}
2
+ ${w}`:w,v=ne(R);s(`${k} was read`,w.length>1e4,`${w.length} bytes`),a&&s(`${a.path} was read`,S.length>1e4,`${S.length} bytes`);const D=I(S),L=I(w),c=oe(f,R),b=(e,t)=>{const n=P(D,e,a?.path??"",0,t),o=P(L,e,k,D.length,t);return{applied:[...n.applied,...o.applied],unreadable:[...n.unreadable,...o.unreadable],undecided:[...n.undecided,...o.undecided]}},T=(e,t)=>Object.fromEntries(t.map(n=>[n,A(e.applied,n,v).value])),x=new Map,g=new Map,H=[],V=[];for(const e of c){const t=b(e,"rest"),n=b(e,"hover");H.push(...t.unreadable,...n.unreadable),V.push(...n.undecided),x.set(e.name,T(t,y)),g.set(e.name,{...T(n,y),"@from":A(n.applied,"background-color",v).from})}const d=(e,t)=>e.get(t.name);s("every rule that can reach a panel select was understood",H.length===0,`could not parse: ${[...new Set(H)].slice(0,4).join(" · ")} — a rule this model skips is a rule it lies about`);const U=V.filter(e=>F(e.decls).some(t=>y.includes(t.prop)||t.prop==="background"||t.prop==="border"));s("no rule this model cannot judge decides a hovered value",U.length===0,`${U.map(e=>`@media ${e.media} { ${e.selector} }`).join(" · ")} — an unjudged media query holding one of these properties makes every answer above a guess`);const M=c[0];for(const e of y){const t=d(g,M)[e],n=c.filter(o=>d(g,o)[e]!==t);s(`every shape resolves the same hovered \`${e}\``,n.length===0,`${M.name} (${M.rows} row(s)) says \`${m(t)}\`, but `+n.map(o=>`${o.name} (${o.rows} row(s)) says \`${m(d(g,o)[e])}\``).join("; "))}for(const e of c){const t=d(x,e),n=d(g,e);s(`${e.name}: the ground actually changes under the pointer`,t["background-color"]!==n["background-color"],`rest and hover both resolve \`${m(n["background-color"])}\` on ${e.rows} row(s) — a row that does not answer the pointer is the other half of this card's defect`)}const O=v.get(j);(E||a)&&s(`the ${a?"editor's sheets declare":"panel declares"} \`${j}\``,O!==void 0,`the token the ruling names is not in ${a?`${a.path} or ${k}`:"the panel stylesheet"}`);for(const e of c){const t=d(g,e);s(`${e.name}: the hovered ground is the panel's own \`var(${j})\``,O!==void 0&&t["background-color"]===O,`resolves to \`${m(t["background-color"])}\`, and \`${j}\` is \`${O??"(not declared)"}\` — option D is the panel's own hover ground, picked by Lewis on 2026-09-02, and a near miss is not it`)}for(const e of c){const t=d(x,e),n=d(g,e);for(const o of se)s(`${e.name}: \`${o}\` is unchanged under the pointer`,t[o]===n[o],`rest \`${m(t[o])}\` -> hover \`${m(n[o])}\` on ${e.rows} row(s) — Lewis chose D (ground only) over B (border jump) and C (darker glyph) from pictures; moving this is re-deciding it`)}if(E){const e=l=>l.replace(/\s+/g," ").replace(/^url\(([\s\S]*)\)$/,"$1").replace(/^["']|["']$/g,"").trim(),t=v.get(C),n=e(t??"");s(`the panel declares \`${C}\` as an image`,t!==void 0&&/^url\(/.test(t.trim())&&n!=="",`\`${C}\` is \`${m(t??"(not declared)")}\` — the one arrow needs one definition`);const o=n===""?-1:R.split(n).length-1;s(`the arrow glyph is written ONCE in ${k}${a?` and ${a.path}`:""}`,o===1,`found ${o} — pasting the arrow into a second rule is exactly what card 66399 removed. Read \`var(${C})\` instead`);const i=[...D,...L].filter(l=>/select/.test(l.selector)&&F(l.decls).some(_=>/url\(\s*["']?data:/.test(_.value)));s("no select rule holds the arrow inline; every one reads the token",i.length===0,`${i.map(l=>l.selector.trim()).join(" · ")} still writes a data URI`)}const W=b(c[0],"hover").applied.filter(e=>/:hover/.test(e.selector)),h=W[0];if(E){s("the panel declares a select hover rule at all",W.length>0,"no `…select:hover` rule in the panel stylesheet reaches a house row — they do nothing under the pointer, which is this card's defect");const e=h?F(h.decls):[],t=h?B(h.selector).slice(0,-1).flatMap(o=>q(o)?.classes??[]):[];s("the hover rule uses no `!important`",h!==void 0&&!/!\s*important/i.test(h.decls),"an `!important` here beats every other state the panel paints, including ones this card was never given");const n=e.map(o=>o.prop).filter(o=>!y.includes(o));s("the hover rule declares only the three properties the ruling names",h!==void 0&&n.length===0,`also declares ${n.join(", ")} — the ruling is a ground colour, plus the arrow and border restated at rest. Anything else is a state this card was not given`),s("the hover rule is confined to the inspector body",t.includes("sf-insp-body"),`its ancestor chain is \`${t.join(" ")}\` — without \`.sf-insp-body\` it reaches every select in the editor, not the panel's`)}for(const e of c){const t=A(b(e,"rest").applied,"background-color",v).from;s(`${e.name}: the resting ground is NOT decided by the hover rule`,h===void 0||!t.includes(h.selector),`rest resolves from \`${t}\` — the pointer state has leaked into the row at rest`)}if(r.chrome){const e=new Set;for(const o of c)for(const i of["rest","hover"])for(const l of b(o,i).applied)for(const _ of B(l.selector).slice(0,-1))for(const z of q(_)?.classes??[])z.startsWith("@")||e.add(z);s(`the rules that resolved lean on wrapper classes (${[...e].map(o=>`.${o}`).join(" ")||"none"})`,e.size>0);const t=te(p,u,[...e]),n=new Set(D.flatMap(o=>[...o.selector.matchAll(/\.(-?[_a-zA-Z][\w-]*)/g)].map(i=>i[1])));for(const o of e){const i=(t.get(o)??[]).length>0,l=!i&&n.has(o);s(`\`.${o}\` is still written above a panel select in the editor's markup${l?` — the screen's (${a?.path} styles it)`:""}`,i||l,`no component writes \`${o}\` as a className any more${a?`, and ${a.path} styles no \`.${o}\` either`:""} — the rules leaning on it still parse, still look right, and match none of the ${$.length} house-drawn rows`)}}return Z()}export{ie as panelOneHover};
@@ -6,16 +6,16 @@ import{ruleChecks as v}from"./rule.js";import{isRegistryPath as F,registryFiles
6
6
  `);if(!P.test(d))return;const r=d.match(/sf-field-label[^>]*>([^<{][^<]*)</),p=d.match(/sf-field-label[^>]*>\{([^}]*)\}/),x=d.match(/<Label>([^<{][^<]*)</),b=d.match(/<Label>\{([^}]*)\}/),h=$=>$.trim()==="label"?"{label}":"{...}",m=r?r[1].trim():p?h(p[1]):x?x[1].trim():b?h(b[1]):"{...}";(o[e.path]??=[]).push(m)})}return o}function M(s){const o=[];let e=0;for(const g of s){const a=g.text.split(`
7
7
  `);a.forEach((f,d)=>{if(!/<SettingsField\b/.test(f))return;const r=[];let p=!1;for(let b=d;b<Math.min(d+40,a.length)&&!p;b+=1){let h=a[b];if(h=h.replace(/\/\*[\s\S]*?\*\//g," "),/\/\*/.test(h)&&(h=h.slice(0,h.indexOf("/*"))),/^\s*\*/.test(a[b]))continue;const m=h.indexOf("//");m!==-1&&(h=h.slice(0,m)),r.push(h),h.includes(">")&&(p=!0)}const x=r.join(`
8
8
  `);/\blabel=/.test(x)&&(e+=1,!/\bicon=/.test(x)&&o.push(`${g.path}:${d+1} ${f.trim().slice(0,80)}`))})}return{seen:e,bare:o}}function K(s,o){const{check:e,report:g}=v("panel rows use the shared row"),a=o.exceptions??[],{floors:f}=o;{const t={path:"fixture.tsx",text:['import { slider, textList } from "@/lib/puck/controls";','const a = { textList: () => controls.textList("Items", <ListIcon />, x) };','const b = slider("Thickness", 0, 10);','const c = slider("Gap", <GapIcon />, 0, 10);','// slider("In a comment", 0, 10) is not a row'].join(`
9
- `)},n=G([t],"textList",[]),c=G([t],"slider",[]);e("the reader reads a namespaced call's own arguments, not the arrow's",n.total===1&&n.bare.length===0,JSON.stringify(n)),e("the reader flags a bare row whose neighbour has a glyph, and skips a comment",c.total===2&&c.bare.length===1&&c.bare[0].startsWith("fixture.tsx:3"),JSON.stringify(c))}const r=new Map(s.map(t=>[t.path,t])).get(L);e(o.writesBuilders?`read ${L}, where the builders are declared`:`${L} is not among this repository's files (it calls the builders, it does not declare them)`,!!r===o.writesBuilders);const p=D(s);e(`read this repository's share of the block registry (${p.length} files)`,p.length>0);const x=s.filter(t=>/^components\/[^/]+\.tsx$/.test(t.path)),b=s.filter(t=>/^scripts\/[^/]+\.tsx$/.test(t.path)),h=[...p,...r?[r]:[],...x],m=t=>t.path===L;{const t=h.filter(l=>m(l)||/import \{[^}]*\btoggle\b[^}]*\} from "@\/lib\/puck\/controls"/.test(l.text)),n=t.filter(l=>m(l)||F(l.path));e("only the known files use the toggle builder",t.length===n.length&&(!o.writesBuilders||n.some(m))&&n.some(l=>F(l.path)),t.map(l=>l.path).join(" \xB7 "));const c=[];let u=0;for(const l of n)l.text.split(`
9
+ `)},n=G([t],"textList",[]),c=G([t],"slider",[]);e("the reader reads a namespaced call's own arguments, not the arrow's",n.total===1&&n.bare.length===0,JSON.stringify(n)),e("the reader flags a bare row whose neighbour has a glyph, and skips a comment",c.total===2&&c.bare.length===1&&c.bare[0].startsWith("fixture.tsx:3"),JSON.stringify(c))}const r=new Map(s.map(t=>[t.path,t])).get(L);e(o.writesBuilders?`read ${L}, where the builders are declared`:`${L} is not among this repository's files (it calls the builders, it does not declare them)`,!!r===o.writesBuilders);const p=D(s);e(`read this repository's share of the block registry (${p.length} files)`,p.length>0);const x=s.filter(t=>/^components\/[^/]+\.tsx$/.test(t.path)),b=s.filter(t=>/^scripts\/[^/]+\.tsx$/.test(t.path)),h=[...p,...r?[r]:[],...x],m=t=>t.path===L;{const t=h.filter(l=>m(l)||/import \{[^}]*\btoggle\b[^}]*\} from "@\/lib\/puck\/controls"/.test(l.text)),n=t.filter(l=>m(l)||F(l.path));e("only the known files use the toggle builder",t.length===n.length&&(!o.writesBuilders||n.some(m))&&n.some(l=>F(l.path)),t.map(l=>l.path).join(" · "));const c=[];let u=0;for(const l of n)l.text.split(`
10
10
  `).forEach((w,i)=>{const y=w.trim();B(y)||/classList\.toggle/.test(w)||/^export function toggle\(/.test(y)||/(^|[^.\w])toggle\(/.test(w)&&(u+=1,!(N.test(w)||/<FieldGlyph\b/.test(w))&&c.push(`${l.path}:${i+1} ${y.slice(0,80)}`))});e("every toggle() row carries a glyph",c.length===0,`
11
11
  ${c.join(`
12
- `)}`),e(`\u2026and there are rows to check (${u} toggle() call sites, at least ${f.toggle})`,u>=f.toggle,"the scan is reading nothing")}const $=[{builder:"textList",inControls:!1,accept:[/TICKER_ITEMS_ROW\.icon/],name:"every textList() row carries a glyph"},{builder:"segmented",inControls:!0,accept:[/\bnoGlyph\(/],name:"every segmented() row carries a glyph (or a ruled noGlyph)"},{builder:"select",inControls:!0,accept:[/\btextKindGlyph\(/,/\bnoGlyph\(/],name:"every select() row carries a glyph (or a ruled noGlyph)"},{builder:"slider",inControls:!0,accept:[/\bnoGlyph\(/],name:"every slider() row carries a glyph"}];for(const t of $){const n=[...p,...t.inControls&&r?[r]:[],...b].filter(l=>t.inControls&&m(l)||F(l.path)||T(l.text,t.builder)),{bare:c,total:u}=G(n,t.builder,t.accept);e(t.name,c.length===0,`
12
+ `)}`),e(`…and there are rows to check (${u} toggle() call sites, at least ${f.toggle})`,u>=f.toggle,"the scan is reading nothing")}const $=[{builder:"textList",inControls:!1,accept:[/TICKER_ITEMS_ROW\.icon/],name:"every textList() row carries a glyph"},{builder:"segmented",inControls:!0,accept:[/\bnoGlyph\(/],name:"every segmented() row carries a glyph (or a ruled noGlyph)"},{builder:"select",inControls:!0,accept:[/\btextKindGlyph\(/,/\bnoGlyph\(/],name:"every select() row carries a glyph (or a ruled noGlyph)"},{builder:"slider",inControls:!0,accept:[/\bnoGlyph\(/],name:"every slider() row carries a glyph"}];for(const t of $){const n=[...p,...t.inControls&&r?[r]:[],...b].filter(l=>t.inControls&&m(l)||F(l.path)||T(l.text,t.builder)),{bare:c,total:u}=G(n,t.builder,t.accept);e(t.name,c.length===0,`
13
13
  ${c.join(`
14
- `)}`),e(`\u2026and the ${t.builder} scan actually read rows (${u} call sites in ${n.length} files, at least ${f[t.builder]})`,u>=f[t.builder])}{const t=U(h);for(const i of a)e(`the exception still names a real row \u2014 ${i.label}`,(t[i.file]??[]).includes(i.label),`${i.file} no longer draws it`);const n={};for(const[i,y]of Object.entries(t)){const k=y.filter(O=>!a.some(j=>j.file===i&&j.label===O));k.length&&(n[i]=k)}const c=i=>i.slice().sort(),u=[];for(const[i,y]of Object.entries(n)){const k=I[i];if(!k){u.push(`${i} \u2014 hand-rolls ${y.length} row(s) and is not in PENDING`);continue}const O=c(y),j=c(k);(O.length!==j.length||O.some((C,R)=>C!==j[R]))&&u.push(`${i} \u2014 found [${O.join(" \xB7 ")}] but PENDING says [${j.join(" \xB7 ")}]`)}e("no NEW hand-rolled settings row",u.length===0,`
14
+ `)}`),e(`…and the ${t.builder} scan actually read rows (${u} call sites in ${n.length} files, at least ${f[t.builder]})`,u>=f[t.builder])}{const t=U(h);for(const i of a)e(`the exception still names a real row — ${i.label}`,(t[i.file]??[]).includes(i.label),`${i.file} no longer draws it`);const n={};for(const[i,y]of Object.entries(t)){const k=y.filter(O=>!a.some(j=>j.file===i&&j.label===O));k.length&&(n[i]=k)}const c=i=>i.slice().sort(),u=[];for(const[i,y]of Object.entries(n)){const k=I[i];if(!k){u.push(`${i} — hand-rolls ${y.length} row(s) and is not in PENDING`);continue}const O=c(y),j=c(k);(O.length!==j.length||O.some((C,R)=>C!==j[R]))&&u.push(`${i} — found [${O.join(" · ")}] but PENDING says [${j.join(" · ")}]`)}e("no NEW hand-rolled settings row",u.length===0,`
15
15
  ${u.join(`
16
- `)}`);const l=Object.keys(I).filter(i=>!n[i]);e("no PENDING entry has outlived its subject",l.length===0,l.join(" \xB7 "));const w=Object.values(n).reduce((i,y)=>i+y.length,0);e("the hand-rolled rows are GONE \u2014 the count is zero",w===0,`${w}`),e("\u2026and no file is left hand-rolling one",Object.keys(I).length===0,Object.keys(I).join(" \xB7 ")),e(`\u2026and the rulings are exactly ${o.rulings} (a new one is argued in review, not added)`,a.length===o.rulings&&a.every(i=>i.why.trim().length>40),`${a.length}`),e("\u2026and BlogSource is not among them, because its row is gone",!a.some(i=>i.file.endsWith("BlogSource.tsx")))}{const t=Object.values(S).reduce((n,c)=>n+c,0);if(e("every builder is finished \u2014 nothing left to count",t===0,`${t}`),e("\u2026and the emptiness is because FOUR builders are done, not because the list was cleared",E.length===8&&["textList","segmented","select","slider"].every(n=>E.includes(n)),E.join(" \xB7 ")),e("\u2026and a finished builder has left the list",E.every(n=>!(n in S)),E.join(" \xB7 ")),r){const n=Object.keys(S).filter(c=>!new RegExp(`(export )?function ${c}\\(`).test(r.text));e(`\u2026and every builder it names is really declared in ${L}`,n.length===0,n.join(" \xB7 "))}}if(r){const t=r.text,n=t.indexOf("export function backgroundField("),c=n===-1?"":t.slice(n),u=c.slice(0,c.indexOf(`
16
+ `)}`);const l=Object.keys(I).filter(i=>!n[i]);e("no PENDING entry has outlived its subject",l.length===0,l.join(" · "));const w=Object.values(n).reduce((i,y)=>i+y.length,0);e("the hand-rolled rows are GONE — the count is zero",w===0,`${w}`),e("…and no file is left hand-rolling one",Object.keys(I).length===0,Object.keys(I).join(" · ")),e(`…and the rulings are exactly ${o.rulings} (a new one is argued in review, not added)`,a.length===o.rulings&&a.every(i=>i.why.trim().length>40),`${a.length}`),e("…and BlogSource is not among them, because its row is gone",!a.some(i=>i.file.endsWith("BlogSource.tsx")))}{const t=Object.values(S).reduce((n,c)=>n+c,0);if(e("every builder is finished — nothing left to count",t===0,`${t}`),e("…and the emptiness is because FOUR builders are done, not because the list was cleared",E.length===8&&["textList","segmented","select","slider"].every(n=>E.includes(n)),E.join(" · ")),e("…and a finished builder has left the list",E.every(n=>!(n in S)),E.join(" · ")),r){const n=Object.keys(S).filter(c=>!new RegExp(`(export )?function ${c}\\(`).test(r.text));e(`…and every builder it names is really declared in ${L}`,n.length===0,n.join(" · "))}}if(r){const t=r.text,n=t.indexOf("export function backgroundField("),c=n===-1?"":t.slice(n),u=c.slice(0,c.indexOf(`
17
17
  }
18
- `));e("backgroundField is declared",n!==-1),e("backgroundField renders SettingsField",/<SettingsField/.test(u)),e("\u2026and hand-rolls no row of its own",u.length>0&&!/className="sf-field"/.test(u)),e("\u2026and passes `BackgroundIcon`, not merely some glyph",/icon=\{<BackgroundIcon \/>\}/.test(u));const l=t.indexOf("export function cardImageBackgroundField("),w=l===-1?"":t.slice(l);e("\u2026and cardImageBackgroundField still passes `GroundIcon`",/icon=\{<GroundIcon \/>\}/.test(w.slice(0,w.indexOf(`
18
+ `));e("backgroundField is declared",n!==-1),e("backgroundField renders SettingsField",/<SettingsField/.test(u)),e("…and hand-rolls no row of its own",u.length>0&&!/className="sf-field"/.test(u)),e("…and passes `BackgroundIcon`, not merely some glyph",/icon=\{<BackgroundIcon \/>\}/.test(u));const l=t.indexOf("export function cardImageBackgroundField("),w=l===-1?"":t.slice(l);e("…and cardImageBackgroundField still passes `GroundIcon`",/icon=\{<GroundIcon \/>\}/.test(w.slice(0,w.indexOf(`
19
19
  }
20
20
  `))))}{const{seen:t,bare:n}=M(x);e(`the hand-rolled row scan read something (${t} hand-drawn SettingsField rows across ${x.length} files, at least ${f.handDrawn})`,t>=f.handDrawn),e("no hand-rolled SettingsField row goes without a glyph",n.length===0,`
21
21
  ${n.join(`
@@ -1,9 +1,9 @@
1
- import{DEFAULT_PANEL_W as k,MAX_PANEL_W as N,MIN_PANEL_W as y}from"../../components/panel-widths.js";import{isRuleSource as A,ruleChecks as F}from"./rule.js";import{parseRules as w}from"./support/css-box-model.js";import{readSliderCallSites as E,sliderFields as v}from"./support/part-slider-call-sites.js";const m=[["sf-editor","es-editor"],["sf-panel","sf-panel--right"],["sf-insp-body","sf-scroll"],["sf-part-panel"],["es-group"],["es-group-body"],["es-field","sf-part-field"],["es-field-ctl"]],_=["sf-slider","has-notch"],S=129;function $(r){const e=r.trim();if(!e.startsWith("."))return null;const s=e.split(".").filter(Boolean);return s.every(o=>/^[a-zA-Z0-9_-]+$/.test(o))?s:null}function c(r){if(/[+~:]/.test(r))return!1;const e=r.trim().split(/\s*(>)\s*|\s+/).filter(Boolean);if(!e.length)return!1;const s=$(e[e.length-1]);if(!s||!s.every(a=>_.includes(a)))return!1;let o=e.length-2,d=m.length-1,i=!1;for(;o>=0;){const a=e[o];if(a===">"){i=!0,o-=1;continue}const f=$(a);if(!f)return!1;let t=-1;for(let n=d;n>=0;n-=1){if(f.every(l=>m[n].includes(l))){t=n;break}if(i)break}if(t===-1)return!1;d=t-1,i=!1,o-=1}return!0}function W(r){const e=r.trim();if(e==="none")return 0;if(e==="auto"||e==="initial")return e==="auto"?1:0;const s=Number.parseFloat(e);return Number.isFinite(s)?s:0}function P(r){const e={grow:0,width:"auto",from:[]};for(const s of r)for(const o of w(s.css)){if(!o.selector.split(",").some(i=>c(i)))continue;let d=!1;for(const[i,a]of o.declarations)i==="flex"?(e.grow=W(a),d=!0):i==="flex-grow"?(e.grow=Number.parseFloat(a)||0,d=!0):i==="width"&&(e.width=a.trim(),d=!0);d&&e.from.push(`${s.name} ${o.selector}`)}return e}function x(r,e){return r.grow>=1||/^100%$/.test(r.width)?e:S}function j(r){return r.filter(e=>{const s=e.text.replace(/\/\*[\s\S]*?\*\//g,"").replace(/\/\/[^\n]*/g,"");return/SettingsField/.test(s)&&/<Slider[\s/>]/.test(s)}).map(e=>e.path).sort()}function I(r,e){const{check:s,report:o}=F("part slider fills row"),d=new Map(r.map(t=>[t.path,t]));if(s("the walk reaches the slot's own slider, and not the notch, a hover or another panel's row",c(".es-field-ctl > .sf-slider")&&c(".sf-insp-body .sf-slider")&&!c(".sf-slider.has-notch::before")&&!c(".es-field-ctl > .sf-slider:hover")&&!c(".sf-field .sf-slider")&&!c(".es-field-ctl > div > .sf-slider")),e.partPanel!==void 0){const t=d.get(e.partPanel);let n=null,l="";try{n=t?E(t.text):null}catch(u){l=u instanceof Error?u.message:String(u)}s(`read the part panel ${e.partPanel}`,!!t&&n!==null,l||"the file is not among those handed in"),s("\xA71 the part panel draws no slider since plan S2",n!==null&&n.length===0,`${e.partPanel} draws ${n?.length??"?"} slider(s) again \u2014 fields ${v(n??[]).join(", ")}. Since plan S2 its numbers are the text box's cells; if a slider is meant to come back, it comes back into the hosts and this section moves with it.`)}if(e.shellSheet!==void 0){const t=w(e.shellSheet.text).filter(n=>n.selector.trim()===".es-field-ctl");s(`\xA72 ${e.shellSheet.path} declares the control slot \`.es-field-ctl\``,t.length>0,"that is the slot `SettingsField` stacks a control into, and this rule is about how an item sizes inside it"),s("\xA72 the shell's control slot is still a flex row",t.length>0&&t[t.length-1].declarations.get("display")==="flex","`.es-field-ctl` is no longer `display: flex` in the installed shell. How a flex ITEM sizes is then not the question, and this rule would be answering the old one. Re-read the shell before trusting a verdict from here.")}if(e.sheet!==void 0){const t=d.get(e.sheet);s(`read the stylesheet ${e.sheet}`,!!t),s("\xA73 the installed shell stylesheet was handed in beside it",e.shellSheet!==void 0);const n=[...t?[{name:t.path,css:t.text}]:[],...e.shellSheet?[{name:e.shellSheet.path,css:e.shellSheet.text}]:[]],l=P(n),u=[{panel:y,content:192,what:"MIN_PANEL_W"},{panel:k.right,content:280,what:"the default"},{panel:N,content:520,what:"MAX_PANEL_W"}],b=u.map(h=>{const p=x(l,h.content),g=h.content-p;return` panel ${String(h.panel).padStart(3)} (${h.what.padEnd(11)}) field content ${String(h.content).padStart(3)} track ${String(p).padStart(3)} `+(g>0?`SHORT BY ${g} \u2014 ${Math.round(p/h.content*100)}% of the row`:"fills")}).join(`
2
- `);for(const h of u)s(`\xA73 the stacked slider fills its row at ${h.what} (field content ${h.content}px)`,!!t&&x(l,h.content)===h.content,`the shell's stacked sliders do not fill their row.
1
+ import{DEFAULT_PANEL_W as k,MAX_PANEL_W as N,MIN_PANEL_W as y}from"../../components/panel-widths.js";import{isRuleSource as A,ruleChecks as F}from"./rule.js";import{parseRules as w}from"./support/css-box-model.js";import{readSliderCallSites as E,sliderFields as v}from"./support/part-slider-call-sites.js";const m=[["sf-editor","es-editor"],["sf-panel","sf-panel--right"],["sf-insp-body","sf-scroll"],["sf-part-panel"],["es-group"],["es-group-body"],["es-field","sf-part-field"],["es-field-ctl"]],_=["sf-slider","has-notch"],S=129;function $(r){const e=r.trim();if(!e.startsWith("."))return null;const s=e.split(".").filter(Boolean);return s.every(o=>/^[a-zA-Z0-9_-]+$/.test(o))?s:null}function c(r){if(/[+~:]/.test(r))return!1;const e=r.trim().split(/\s*(>)\s*|\s+/).filter(Boolean);if(!e.length)return!1;const s=$(e[e.length-1]);if(!s||!s.every(a=>_.includes(a)))return!1;let o=e.length-2,d=m.length-1,i=!1;for(;o>=0;){const a=e[o];if(a===">"){i=!0,o-=1;continue}const f=$(a);if(!f)return!1;let t=-1;for(let n=d;n>=0;n-=1){if(f.every(l=>m[n].includes(l))){t=n;break}if(i)break}if(t===-1)return!1;d=t-1,i=!1,o-=1}return!0}function W(r){const e=r.trim();if(e==="none")return 0;if(e==="auto"||e==="initial")return e==="auto"?1:0;const s=Number.parseFloat(e);return Number.isFinite(s)?s:0}function P(r){const e={grow:0,width:"auto",from:[]};for(const s of r)for(const o of w(s.css)){if(!o.selector.split(",").some(i=>c(i)))continue;let d=!1;for(const[i,a]of o.declarations)i==="flex"?(e.grow=W(a),d=!0):i==="flex-grow"?(e.grow=Number.parseFloat(a)||0,d=!0):i==="width"&&(e.width=a.trim(),d=!0);d&&e.from.push(`${s.name} ${o.selector}`)}return e}function x(r,e){return r.grow>=1||/^100%$/.test(r.width)?e:S}function j(r){return r.filter(e=>{const s=e.text.replace(/\/\*[\s\S]*?\*\//g,"").replace(/\/\/[^\n]*/g,"");return/SettingsField/.test(s)&&/<Slider[\s/>]/.test(s)}).map(e=>e.path).sort()}function I(r,e){const{check:s,report:o}=F("part slider fills row"),d=new Map(r.map(t=>[t.path,t]));if(s("the walk reaches the slot's own slider, and not the notch, a hover or another panel's row",c(".es-field-ctl > .sf-slider")&&c(".sf-insp-body .sf-slider")&&!c(".sf-slider.has-notch::before")&&!c(".es-field-ctl > .sf-slider:hover")&&!c(".sf-field .sf-slider")&&!c(".es-field-ctl > div > .sf-slider")),e.partPanel!==void 0){const t=d.get(e.partPanel);let n=null,l="";try{n=t?E(t.text):null}catch(u){l=u instanceof Error?u.message:String(u)}s(`read the part panel ${e.partPanel}`,!!t&&n!==null,l||"the file is not among those handed in"),s("§1 the part panel draws no slider since plan S2",n!==null&&n.length===0,`${e.partPanel} draws ${n?.length??"?"} slider(s) again — fields ${v(n??[]).join(", ")}. Since plan S2 its numbers are the text box's cells; if a slider is meant to come back, it comes back into the hosts and this section moves with it.`)}if(e.shellSheet!==void 0){const t=w(e.shellSheet.text).filter(n=>n.selector.trim()===".es-field-ctl");s(`§2 ${e.shellSheet.path} declares the control slot \`.es-field-ctl\``,t.length>0,"that is the slot `SettingsField` stacks a control into, and this rule is about how an item sizes inside it"),s("§2 the shell's control slot is still a flex row",t.length>0&&t[t.length-1].declarations.get("display")==="flex","`.es-field-ctl` is no longer `display: flex` in the installed shell. How a flex ITEM sizes is then not the question, and this rule would be answering the old one. Re-read the shell before trusting a verdict from here.")}if(e.sheet!==void 0){const t=d.get(e.sheet);s(`read the stylesheet ${e.sheet}`,!!t),s("§3 the installed shell stylesheet was handed in beside it",e.shellSheet!==void 0);const n=[...t?[{name:t.path,css:t.text}]:[],...e.shellSheet?[{name:e.shellSheet.path,css:e.shellSheet.text}]:[]],l=P(n),u=[{panel:y,content:192,what:"MIN_PANEL_W"},{panel:k.right,content:280,what:"the default"},{panel:N,content:520,what:"MAX_PANEL_W"}],b=u.map(h=>{const p=x(l,h.content),g=h.content-p;return` panel ${String(h.panel).padStart(3)} (${h.what.padEnd(11)}) field content ${String(h.content).padStart(3)} track ${String(p).padStart(3)} `+(g>0?`SHORT BY ${g} — ${Math.round(p/h.content*100)}% of the row`:"fills")}).join(`
2
+ `);for(const h of u)s(`§3 the stacked slider fills its row at ${h.what} (field content ${h.content}px)`,!!t&&x(l,h.content)===h.content,`the shell's stacked sliders do not fill their row.
3
3
 
4
- ${e.hosts.length} host(s) \u2014 ${e.hosts.join(", ")} \u2014 draw the shared \`Slider\` inside
4
+ ${e.hosts.length} host(s) — ${e.hosts.join(", ")} — draw the shared \`Slider\` inside
5
5
  \`SettingsField\`, whose control slot \`.es-field-ctl\` is \`display: flex\`. Nothing in ${e.sheet} makes \`.sf-slider\` take
6
- that row, so it is \`flex: 0 1 auto\` and collapses to the UA's own ${S}px range input \u2014 the
6
+ that row, so it is \`flex: 0 1 auto\` and collapses to the UA's own ${S}px range input — the
7
7
  same width whatever the panel is dragged to:
8
8
 
9
9
  ${b}
@@ -15,15 +15,15 @@ ${b}
15
15
 
16
16
  The shell asks for this in its own words (src/panel/SettingsField.tsx):
17
17
  "The row is stacked rather than side-by-side so the control gets the panel's full
18
- width \u2014 a 308px panel cannot afford a label column AND a usable slider."`)}const i=r.filter(t=>/\.tsx?$/.test(t.path)&&!A(t.path)&&(t.path.startsWith("components/")||t.path.startsWith("lib/")));s(`read the source (${i.length} .ts/.tsx files under components/ and lib/, at least ${e.minFiles})`,i.length>=e.minFiles);const a=j(i),f=[...e.hosts].sort();s(`\xA74 the files that draw a \`Slider\` inside a \`SettingsField\` are the ${f.length} known host(s)`,a.join(`
18
+ width — a 308px panel cannot afford a label column AND a usable slider."`)}const i=r.filter(t=>/\.tsx?$/.test(t.path)&&!A(t.path)&&(t.path.startsWith("components/")||t.path.startsWith("lib/")));s(`read the source (${i.length} .ts/.tsx files under components/ and lib/, at least ${e.minFiles})`,i.length>=e.minFiles);const a=j(i),f=[...e.hosts].sort();s(`§4 the files that draw a \`Slider\` inside a \`SettingsField\` are the ${f.length} known host(s)`,a.join(`
19
19
  `)===f.join(`
20
20
  `),`the set has changed.
21
21
 
22
22
  known: ${f.join(", ")||"(none)"}
23
23
  found: ${a.join(", ")||"(none)"}
24
24
 
25
- A NEW one is a row \`.es-field-ctl > .sf-slider\` now fills and nothing here watches \u2014 add it to the hosts
26
- (and render it for the direct-child check). One REMOVED is a guard pointing at nothing.`);for(const[t,n]of Object.entries(e.renderedHosts??{})){const l=/class="es-field-ctl"><([a-z]+) class="([^"]*)"/.exec(n);s(`\xA74 ${t}: the rendered row has a \`.es-field-ctl\` slot with an element child`,l!==null,"has it stopped going through `SettingsField`?"),l&&s(`\xA74 ${t}: the slot's first child is the \`.sf-slider\` itself`,l[2].split(/\s+/).includes("sf-slider"),`the control slot's first child is <${l[1]} class="${l[2]}">. Something wraps the slider, so
27
- \`.es-field-ctl > .sf-slider\` no longer reaches it and that row is back to the UA's 129px \u2014 the wrapper is the
25
+ A NEW one is a row \`.es-field-ctl > .sf-slider\` now fills and nothing here watches — add it to the hosts
26
+ (and render it for the direct-child check). One REMOVED is a guard pointing at nothing.`);for(const[t,n]of Object.entries(e.renderedHosts??{})){const l=/class="es-field-ctl"><([a-z]+) class="([^"]*)"/.exec(n);s(`§4 ${t}: the rendered row has a \`.es-field-ctl\` slot with an element child`,l!==null,"has it stopped going through `SettingsField`?"),l&&s(`§4 ${t}: the slot's first child is the \`.sf-slider\` itself`,l[2].split(/\s+/).includes("sf-slider"),`the control slot's first child is <${l[1]} class="${l[2]}">. Something wraps the slider, so
27
+ \`.es-field-ctl > .sf-slider\` no longer reaches it and that row is back to the UA's 129px — the wrapper is the
28
28
  flex item now. Widening the selector to a descendant would NOT fix it; it would paint \`flex: 1\` on an element
29
29
  whose parent is the wrapper and leave the measurement unchanged. Card 66247.`)}return o()}export{I as partSliderFillsRow,c as reachesSlider};
@@ -1,2 +1,2 @@
1
1
  import{fontRoleOfClassName as y,FONT_ROLE_LABELS as x}from"../fonts.js";import{isRuleSource as w,ruleChecks as E}from"./rule.js";import{enclosingLiteral as N}from"./heading-ceiling-sweep.js";import{stripComments as b}from"./support/ts-source.js";import{typeScaleGroupOfClassName as u}from"../theme.js";const R=["app/","components/","lib/"],p={"font-display":"heading","font-heading2":"heading2","font-heading3":"heading3","font-body":"body","font-serif":"accent","font-mono":"label"},k={heading:"--font-role-heading",heading2:"--font-role-heading2",heading3:"--font-role-heading3",body:"--font-role-body",accent:"--font-role-accent",label:"--font-role-label"},$=[];function L(m){const n=[],t=new Set;for(const d of m){const c=b(d.text),l=/\bfont-(?:display|body|serif|mono)\b/g;let r;for(;r=l.exec(c);){const i=N(c,r.index);if(!i)continue;const o=c.slice(0,r.index).split(`
2
- `).length,h=`${d.path}:${o}:${i}`;if(t.has(h))continue;t.add(h);const g=i.split(/\s+/).filter(a=>!a.slice(0,a.lastIndexOf(":")+1).includes("&")).map(a=>a.slice(a.lastIndexOf(":")+1)),f=Object.keys(p).filter(a=>g.includes(a));f.length!==0&&n.push({file:d.path,line:o,className:i,utilities:f})}}return n}function I(m,n){const{check:t,report:d}=E("part style naming"),c=m.filter(e=>/\.tsx?$/.test(e.path)&&!w(e.path)&&R.some(s=>e.path.startsWith(s))),l=L(c),r=new Map(m.map(e=>[e.path,e]));t(`read the source (${c.length} .ts/.tsx files)`,c.length>0,"found no source to sweep \u2014 this rule is not measuring anything"),t(`the scanner finds face-tagged class lists at all (${l.length}, at least ${n.minSites})`,l.length>=n.minSites);const i=l.filter(e=>u(e.className)!==null),o=l.filter(e=>u(e.className)===null);t(`\u2026both taggings are represented, so neither branch below is untested (${i.length} with a scale token, at least ${n.minWithScale})`,i.length>=n.minWithScale&&i.length>0),t(`\u2026and the face-without-a-size-knob population is real, not an artefact (${o.length}, at least ${n.minWithoutScale})`,o.length>=n.minWithoutScale&&o.length>0,`${o.length} class lists carry a face utility and no matching scale token`);const h=l.filter(e=>y(e.className)!==p[e.utilities[0]]);if(t("every class list in the census reads back as its own utility's role",h.length===0,h.slice(0,5).map(e=>`${e.file}:${e.line} has ${e.utilities[0]} but reads ${y(e.className)}`).join("; ")),n.roleSheet!==void 0){const e=r.get(n.roleSheet),s=Object.values(k).filter(O=>!e||!e.text.includes(O));t(`\u2026and every role named here is a custom property ${n.roleSheet} defines`,!!e&&s.length===0,e?s.join(", "):"the sheet was not read")}const g=Object.values(p).filter(e=>!(e in x));t("every face utility names a role the panel offers",g.length===0,g.join(", "));const f=Object.keys(x).filter(e=>!Object.values(p).includes(e));t("\u2026and the roles no utility can name are exactly the ones decided to have none",f.join()===$.join(),`no utility reaches: [${f}] \u2014 expected [${$}]`);const a=o.filter(e=>u(e.className)!==null);t("no class list without a scale token claims a size group",a.length===0,a.slice(0,5).map(e=>`${e.file}:${e.line}`).join("; ")),t("the reported case: a `font-display text-[22px]` heading is Headings, size group none",y("font-display text-[22px] font-medium")==="heading"&&u("font-display text-[22px] font-medium")===null),t("\u2026and genuinely body-tagged copy still reads as the body size group",u("text-[var(--size-body,16px)]")==="body");const S=l.filter(e=>e.utilities.length>1);if(t("every class list names at most one face",S.length===0,S.slice(0,5).map(e=>`${e.file}:${e.line} \u2014 ${e.utilities.join(" + ")}`).join("; ")),n.panel!==void 0){const e=r.get(n.panel),s=e?b(e.text):"";t(`read the part panel ${n.panel}`,!!e),t("the box's name comes from the FACE, not from a size group",s.includes("FONT_ROLE_LABELS[face.role]")&&!s.includes("TYPE_GROUP_LABELS[")),t("\u2026a face the page does not draw as its declared role is called Custom",/return\s*"Custom"/.test(s)&&s.includes('kind: "custom"')),t("\u2026and a part no size knob reaches says so under the text box",/sizeGroup === null && \(\s*<p className="es-field-hint sf-hint-keep">Fixed size/.test(s))}if(n.bridge!==void 0){const e=r.get(n.bridge),s=e?b(e.text):"";t(`read the preview's part bridge ${n.bridge}`,!!e),t("the preview publishes BOTH tags, each walked up to its own nearest declarer",s.includes("fontRoleOfClassName")&&s.includes("typeScaleGroupOfClassName")&&s.includes("el.parentElement")),t("\u2026and the element it reads from is one that declares EITHER tag",/partStyleSource\([\s\S]{0,200}fontRoleOfClassName[\s\S]{0,120}typeScaleGroupOfClassName/.test(s))}return d()}export{I as partStyleNaming};
2
+ `).length,h=`${d.path}:${o}:${i}`;if(t.has(h))continue;t.add(h);const g=i.split(/\s+/).filter(a=>!a.slice(0,a.lastIndexOf(":")+1).includes("&")).map(a=>a.slice(a.lastIndexOf(":")+1)),f=Object.keys(p).filter(a=>g.includes(a));f.length!==0&&n.push({file:d.path,line:o,className:i,utilities:f})}}return n}function I(m,n){const{check:t,report:d}=E("part style naming"),c=m.filter(e=>/\.tsx?$/.test(e.path)&&!w(e.path)&&R.some(s=>e.path.startsWith(s))),l=L(c),r=new Map(m.map(e=>[e.path,e]));t(`read the source (${c.length} .ts/.tsx files)`,c.length>0,"found no source to sweep — this rule is not measuring anything"),t(`the scanner finds face-tagged class lists at all (${l.length}, at least ${n.minSites})`,l.length>=n.minSites);const i=l.filter(e=>u(e.className)!==null),o=l.filter(e=>u(e.className)===null);t(`…both taggings are represented, so neither branch below is untested (${i.length} with a scale token, at least ${n.minWithScale})`,i.length>=n.minWithScale&&i.length>0),t(`…and the face-without-a-size-knob population is real, not an artefact (${o.length}, at least ${n.minWithoutScale})`,o.length>=n.minWithoutScale&&o.length>0,`${o.length} class lists carry a face utility and no matching scale token`);const h=l.filter(e=>y(e.className)!==p[e.utilities[0]]);if(t("every class list in the census reads back as its own utility's role",h.length===0,h.slice(0,5).map(e=>`${e.file}:${e.line} has ${e.utilities[0]} but reads ${y(e.className)}`).join("; ")),n.roleSheet!==void 0){const e=r.get(n.roleSheet),s=Object.values(k).filter(O=>!e||!e.text.includes(O));t(`…and every role named here is a custom property ${n.roleSheet} defines`,!!e&&s.length===0,e?s.join(", "):"the sheet was not read")}const g=Object.values(p).filter(e=>!(e in x));t("every face utility names a role the panel offers",g.length===0,g.join(", "));const f=Object.keys(x).filter(e=>!Object.values(p).includes(e));t("…and the roles no utility can name are exactly the ones decided to have none",f.join()===$.join(),`no utility reaches: [${f}] — expected [${$}]`);const a=o.filter(e=>u(e.className)!==null);t("no class list without a scale token claims a size group",a.length===0,a.slice(0,5).map(e=>`${e.file}:${e.line}`).join("; ")),t("the reported case: a `font-display text-[22px]` heading is Headings, size group none",y("font-display text-[22px] font-medium")==="heading"&&u("font-display text-[22px] font-medium")===null),t("…and genuinely body-tagged copy still reads as the body size group",u("text-[var(--size-body,16px)]")==="body");const S=l.filter(e=>e.utilities.length>1);if(t("every class list names at most one face",S.length===0,S.slice(0,5).map(e=>`${e.file}:${e.line} — ${e.utilities.join(" + ")}`).join("; ")),n.panel!==void 0){const e=r.get(n.panel),s=e?b(e.text):"";t(`read the part panel ${n.panel}`,!!e),t("the box's name comes from the FACE, not from a size group",s.includes("FONT_ROLE_LABELS[face.role]")&&!s.includes("TYPE_GROUP_LABELS[")),t("…a face the page does not draw as its declared role is called Custom",/return\s*"Custom"/.test(s)&&s.includes('kind: "custom"')),t("…and a part no size knob reaches says so under the text box",/sizeGroup === null && \(\s*<p className="es-field-hint sf-hint-keep">Fixed size/.test(s))}if(n.bridge!==void 0){const e=r.get(n.bridge),s=e?b(e.text):"";t(`read the preview's part bridge ${n.bridge}`,!!e),t("the preview publishes BOTH tags, each walked up to its own nearest declarer",s.includes("fontRoleOfClassName")&&s.includes("typeScaleGroupOfClassName")&&s.includes("el.parentElement")),t("…and the element it reads from is one that declares EITHER tag",/partStyleSource\([\s\S]{0,200}fontRoleOfClassName[\s\S]{0,120}typeScaleGroupOfClassName/.test(s))}return d()}export{I as partStyleNaming};
@@ -1,8 +1,8 @@
1
- import{ruleChecks as P}from"./rule.js";const o=/<(Render|Puck|SiteEditorProvider|EditorStoreProvider|ScreenStoreProvider)\b[^<>]*\bdata=\{/,m=/migrateRenamedProps\s*\(/,g=/export function migrateRenamedProps\b/;function S(l,r){const{check:t,report:p}=P("puck document surfaces"),a=e=>l.find(s=>s.path===e)?.text;{const e=n=>`<${n}`;t("a mount is found",o.test(`${e("Render")} config={c} data={doc} />`)),t("\u2026across lines",o.test(`${e("SiteEditorProvider")}
1
+ import{ruleChecks as P}from"./rule.js";const o=/<(Render|Puck|SiteEditorProvider|EditorStoreProvider|ScreenStoreProvider)\b[^<>]*\bdata=\{/,m=/migrateRenamedProps\s*\(/,g=/export function migrateRenamedProps\b/;function S(l,r){const{check:t,report:p}=P("puck document surfaces"),a=e=>l.find(s=>s.path===e)?.text;{const e=n=>`<${n}`;t("a mount is found",o.test(`${e("Render")} config={c} data={doc} />`)),t("…across lines",o.test(`${e("SiteEditorProvider")}
2
2
  config={c}
3
3
  data={initial}
4
- >`)),t("\u2026and the screen's store provider is one under either of its names",["Editor","Screen"].every(n=>o.test(`${e(`${n}StoreProvider`)} config={c} data={doc}>`))),t("\u2026and a tag named in prose is not one",!o.test(`the ${e("Puck")}> mounts, then its data={x} is read`)),t("\u2026nor the composition API",!o.test(`${e("Puck.Preview")} />`));const s="migrateRenamedProps";t("a call is found",m.test(`const d = ${s}(stored);`)),t("\u2026and a mention is not one",!m.test(`// runs ${s} first`)),t("the declaration is found, generic as it is",g.test(`export function ${s}<T>(data: T): T {`))}const d=l.filter(e=>/\.tsx?$/.test(e.path));t(`the source scan reached the app (${d.length} TS/TSX files, at least ${r.minFiles})`,d.length>=r.minFiles);const c=d.filter(e=>o.test(e.text)).map(e=>e.path).sort(),$=Object.keys(r.mounts).sort(),u=c.filter(e=>!(e in r.mounts)),f=$.filter(e=>!c.includes(e));t(`every mount is declared (${c.length} found) \u2014 a NEW one must say whether its document comes from stored props (migrate it) or is a mock (say so)`,u.length===0,u.map(e=>`UNDECLARED: ${e}`).join(`
5
- `)),t("every declared mount is a real mount \u2014 a stale row would shelter a surface that later stops migrating",f.length===0,f.map(e=>`NOT FOUND: ${e}`).join(`
6
- `));const i=Object.entries(r.mounts).filter(([,e])=>e.migratedBy!==null),h=i.filter(([,e])=>!m.test(a(e.migratedBy)??""));if(t(`SURFACES=${i.length} \u2014 every mount fed from STORED props runs migrateRenamedProps, so a renamed prop key cannot silently reset a live section`,h.length===0,h.map(([e,s])=>`${e}
7
- \u2192 ${s.migratedBy} never calls migrateRenamedProps(`).join(`
8
- `)),r.handedMigrator){const{rules:e,entry:s}=r.handedMigrator,n=a(e)??"";t(`${e} carries the REAL migrateRenamedProps, not a stand-in`,/import \{ migrateRenamedProps \} from "@\/lib\/puck\/renamed-props";/.test(n)&&/^\s*migrateRenamedProps,$/m.test(n)),t(`\u2026${s} passes those rules to the screen's mount`,/<(?:EditorScreenMount|ScreenMount)\b[^>]*addressing=\{WEBSITE_ADDRESSING\}/.test(a(s)??""))}return t(`at least ${r.storedFloor} mount(s) fed from stored props (found ${i.length})`,i.length>=r.storedFloor,"if this drops, a surface was reclassified as a mock rather than fixed"),r.migrator&&t(`${r.migrator} still exports migrateRenamedProps \u2014 the function \xA72 hunts calls of is live`,g.test(a(r.migrator)??"")),p()}export{S as puckDocumentSurfaces};
4
+ >`)),t("…and the screen's store provider is one under either of its names",["Editor","Screen"].every(n=>o.test(`${e(`${n}StoreProvider`)} config={c} data={doc}>`))),t("…and a tag named in prose is not one",!o.test(`the ${e("Puck")}> mounts, then its data={x} is read`)),t("…nor the composition API",!o.test(`${e("Puck.Preview")} />`));const s="migrateRenamedProps";t("a call is found",m.test(`const d = ${s}(stored);`)),t("…and a mention is not one",!m.test(`// runs ${s} first`)),t("the declaration is found, generic as it is",g.test(`export function ${s}<T>(data: T): T {`))}const d=l.filter(e=>/\.tsx?$/.test(e.path));t(`the source scan reached the app (${d.length} TS/TSX files, at least ${r.minFiles})`,d.length>=r.minFiles);const c=d.filter(e=>o.test(e.text)).map(e=>e.path).sort(),$=Object.keys(r.mounts).sort(),u=c.filter(e=>!(e in r.mounts)),f=$.filter(e=>!c.includes(e));t(`every mount is declared (${c.length} found) — a NEW one must say whether its document comes from stored props (migrate it) or is a mock (say so)`,u.length===0,u.map(e=>`UNDECLARED: ${e}`).join(`
5
+ `)),t("every declared mount is a real mount — a stale row would shelter a surface that later stops migrating",f.length===0,f.map(e=>`NOT FOUND: ${e}`).join(`
6
+ `));const i=Object.entries(r.mounts).filter(([,e])=>e.migratedBy!==null),h=i.filter(([,e])=>!m.test(a(e.migratedBy)??""));if(t(`SURFACES=${i.length} — every mount fed from STORED props runs migrateRenamedProps, so a renamed prop key cannot silently reset a live section`,h.length===0,h.map(([e,s])=>`${e}
7
+ → ${s.migratedBy} never calls migrateRenamedProps(`).join(`
8
+ `)),r.handedMigrator){const{rules:e,entry:s}=r.handedMigrator,n=a(e)??"";t(`${e} carries the REAL migrateRenamedProps, not a stand-in`,/import \{ migrateRenamedProps \} from "@\/lib\/puck\/renamed-props";/.test(n)&&/^\s*migrateRenamedProps,$/m.test(n)),t(`…${s} passes those rules to the screen's mount`,/<(?:EditorScreenMount|ScreenMount)\b[^>]*addressing=\{WEBSITE_ADDRESSING\}/.test(a(s)??""))}return t(`at least ${r.storedFloor} mount(s) fed from stored props (found ${i.length})`,i.length>=r.storedFloor,"if this drops, a surface was reclassified as a mock rather than fixed"),r.migrator&&t(`${r.migrator} still exports migrateRenamedProps — the function §2 hunts calls of is live`,g.test(a(r.migrator)??"")),p()}export{S as puckDocumentSurfaces};
@@ -3,7 +3,7 @@ import{isRuleSource as C,ruleChecks as I}from"./rule.js";import{stripCommentsAnd
3
3
  `).entries())N.test(u)&&r.push(`${n.path}:${m+1} ${u.trim()}`);return r}function L(n,o){const{check:e,report:d}=I("reduced motion"),r=o.editorOnly??{},m=new Map(n.map(t=>[t.path,t]));{e("a rule that exists only in a comment is NOT counted",$(O(`/* the site should have a @media (prefers-reduced-motion: reduce) rule */
4
4
  a { color: red; }`))===null);const i="@media (prefers-reduced-motion: reduce) { * { animation-duration: 1ms !important; } }";e("the layer check fails on a layered copy and passes on an unlayered one",$(i)?.unlayered===!0&&$(`@layer base { ${i} }`)?.unlayered===!1&&$(`@layer base { a { color: red; } } ${i}`)?.unlayered===!0),e("valueOf reads a declaration and drops !important",p("animation-duration: 1ms !important;","animation-duration")==="1ms"&&p("transition-property: opacity, color !important;","transition-property")==="opacity, color"&&p("animation-duration: 1ms;","animation")===null&&p("scroll-behavior: auto;","animation-delay")===null);const a=f=>(f??"").split(",").map(v=>v.trim()),h=a(p("transition-property: opacity, transform !important;","transition-property")),w=a(p("transition-property: opacity, color !important;","transition-property"));e("the moving-property check fails on a copy that lets transform through, and passes a fade-only list",h.some(f=>R.includes(f))&&!w.some(f=>R.includes(f))),e("the !important scan flags a motion declaration and not one in a comment",B({path:"fixture.css",text:`/* transition: x !important */
5
5
  a { transition: opacity 1s !important; }`},null).length===1),e("the smooth-scroll regex bites, both ways",x.test('el.scrollIntoView({ behavior: "smooth" })')&&x.test("window.scrollTo({ top: 0, behavior: 'smooth' })")&&!x.test("el.scrollIntoView({ behavior: scrollBehavior() })")&&!x.test('window.scrollTo({ top: 0, behavior: "auto" })')),e("a commented example is not a breach",j({path:"fixture.ts",text:`// behavior: "smooth" in a comment
6
- const a = 1;`}).length===0)}const u=n.filter(t=>t.path.endsWith(".css")),g=n.filter(t=>/\.tsx?$/.test(t.path)&&!t.path.endsWith(".d.ts")&&!C(t.path));let s=null;if(o.globalSheet!==void 0){const t=m.get(o.globalSheet),i=t?O(t.text):"";s=$(i);const a=o.globalSheet;e(`${a} was actually read`,i.length>1e4,`expected the real stylesheet, got ${i.length} chars`),e(`${a} has a real prefers-reduced-motion rule, not just prose about one`,s!==null&&s.decls.trim().length>0,"no `@media (prefers-reduced-motion: reduce)` block once comments are stripped, or it is empty. Before this rule landed the sheet's ONLY match for that string was a comment, which is the state this asserts is over."),e("the rule is UNLAYERED, so Tailwind's utilities cannot outrank it",s?.unlayered===!0,"the reduced-motion rule sits inside an @layer. Tailwind's utilities live in a later layer, so every `transition-*` class would beat it and the site would move for everyone, with nothing to show for it.");const h=s?.selector??"";e("the rule applies to every element and both generated boxes",/\*/.test(h)&&/::before/.test(h)&&/::after/.test(h),`selector \`${h}\` \u2014 it must start from \`*\` (a narrower one covers only what somebody remembered) and cover \`::before\` and \`::after\`: a generated box animates like any other`);const w=(s?.decls??"").split(";").map(l=>l.trim()).filter(l=>l.length>0),f=w.filter(l=>!/!important\s*$/i.test(l)).map(l=>l.split(":")[0].trim());e("every declaration carries !important",w.length>=4&&f.length===0,`${w.length} declaration(s), without !important: ${f.join(", ")||"(none)"}. Being unlayered beats Tailwind, but not the unlayered class rules in the same sheet and not an inline style.`);const v=p(s?.decls??"","transition-property"),E=(v??"").split(",").map(l=>l.trim().toLowerCase()),T=E.filter(l=>R.includes(l));e("the transition allow-list lets nothing move",v!==null&&T.length===0,v===null?"the rule must set `transition-property` \u2014 that is how movement is sorted from fades":`movement is back on the allow-list: ${T.join(", ")}. Anything on that list still animates for a visitor who asked for no motion.`);const M=D.filter(l=>!E.includes(l));e("the transition allow-list still lets fades through",M.length===0,`\`${M.join("`, `")}\` must stay transitionable. A fade carries no movement, helps people follow what changed, and harms nobody \u2014 dropping it is the crude rule this one was written instead of.`);const k=p(s?.decls??"","animation-duration");e("animations are collapsed to a short NON-ZERO time",k!==null&&/^(0?\.\d+|[1-9]\d*)m?s$/.test(k),`animation-duration is \`${k}\`; the rule must cap it (CSS cannot sort a keyframe list by what it moves), and exactly 0s stops \`animationend\` firing in some engines and hangs any component waiting on it.`),e("a loop that still repeats is still a loop: animation-iteration-count is 1",p(s?.decls??"","animation-iteration-count")==="1"),e("animations are not collapsed with `animation: none`, which can hide content",p(s?.decls??"","animation")===null,"an element whose static styles are invisible, made visible by a `forwards` fill, would never appear. Run the animation to its end instead."),e("a scroll the visitor did not ask for is movement too: scroll-behavior is auto",p(s?.decls??"","scroll-behavior")==="auto","the sheet sets `scroll-behavior: smooth` on html inside @layer base; this must turn it off."),e("the escape hatch exists and is spelled the one way",/\[data-motion-keep\]/.test(h),"the exemption attribute must appear in the selector, so one grep finds every exception.")}for(const t of o.stylesheets??[])e(`read the stylesheet ${t}`,m.has(t)&&t.endsWith(".css"));const y=u.flatMap(t=>B(t,t.path===o.globalSheet&&s?s.body:null));e(`nothing in the stylesheets can outrank the rule (${u.length} sheet(s))`,u.length>0&&y.length===0,u.length===0?"read no stylesheet \u2014 this check is not measuring anything":`!important motion declaration(s) outside the reduced-motion rule:
6
+ const a = 1;`}).length===0)}const u=n.filter(t=>t.path.endsWith(".css")),g=n.filter(t=>/\.tsx?$/.test(t.path)&&!t.path.endsWith(".d.ts")&&!C(t.path));let s=null;if(o.globalSheet!==void 0){const t=m.get(o.globalSheet),i=t?O(t.text):"";s=$(i);const a=o.globalSheet;e(`${a} was actually read`,i.length>1e4,`expected the real stylesheet, got ${i.length} chars`),e(`${a} has a real prefers-reduced-motion rule, not just prose about one`,s!==null&&s.decls.trim().length>0,"no `@media (prefers-reduced-motion: reduce)` block once comments are stripped, or it is empty. Before this rule landed the sheet's ONLY match for that string was a comment, which is the state this asserts is over."),e("the rule is UNLAYERED, so Tailwind's utilities cannot outrank it",s?.unlayered===!0,"the reduced-motion rule sits inside an @layer. Tailwind's utilities live in a later layer, so every `transition-*` class would beat it and the site would move for everyone, with nothing to show for it.");const h=s?.selector??"";e("the rule applies to every element and both generated boxes",/\*/.test(h)&&/::before/.test(h)&&/::after/.test(h),`selector \`${h}\` — it must start from \`*\` (a narrower one covers only what somebody remembered) and cover \`::before\` and \`::after\`: a generated box animates like any other`);const w=(s?.decls??"").split(";").map(l=>l.trim()).filter(l=>l.length>0),f=w.filter(l=>!/!important\s*$/i.test(l)).map(l=>l.split(":")[0].trim());e("every declaration carries !important",w.length>=4&&f.length===0,`${w.length} declaration(s), without !important: ${f.join(", ")||"(none)"}. Being unlayered beats Tailwind, but not the unlayered class rules in the same sheet and not an inline style.`);const v=p(s?.decls??"","transition-property"),E=(v??"").split(",").map(l=>l.trim().toLowerCase()),T=E.filter(l=>R.includes(l));e("the transition allow-list lets nothing move",v!==null&&T.length===0,v===null?"the rule must set `transition-property` — that is how movement is sorted from fades":`movement is back on the allow-list: ${T.join(", ")}. Anything on that list still animates for a visitor who asked for no motion.`);const M=D.filter(l=>!E.includes(l));e("the transition allow-list still lets fades through",M.length===0,`\`${M.join("`, `")}\` must stay transitionable. A fade carries no movement, helps people follow what changed, and harms nobody — dropping it is the crude rule this one was written instead of.`);const k=p(s?.decls??"","animation-duration");e("animations are collapsed to a short NON-ZERO time",k!==null&&/^(0?\.\d+|[1-9]\d*)m?s$/.test(k),`animation-duration is \`${k}\`; the rule must cap it (CSS cannot sort a keyframe list by what it moves), and exactly 0s stops \`animationend\` firing in some engines and hangs any component waiting on it.`),e("a loop that still repeats is still a loop: animation-iteration-count is 1",p(s?.decls??"","animation-iteration-count")==="1"),e("animations are not collapsed with `animation: none`, which can hide content",p(s?.decls??"","animation")===null,"an element whose static styles are invisible, made visible by a `forwards` fill, would never appear. Run the animation to its end instead."),e("a scroll the visitor did not ask for is movement too: scroll-behavior is auto",p(s?.decls??"","scroll-behavior")==="auto","the sheet sets `scroll-behavior: smooth` on html inside @layer base; this must turn it off."),e("the escape hatch exists and is spelled the one way",/\[data-motion-keep\]/.test(h),"the exemption attribute must appear in the selector, so one grep finds every exception.")}for(const t of o.stylesheets??[])e(`read the stylesheet ${t}`,m.has(t)&&t.endsWith(".css"));const y=u.flatMap(t=>B(t,t.path===o.globalSheet&&s?s.body:null));e(`nothing in the stylesheets can outrank the rule (${u.length} sheet(s))`,u.length>0&&y.length===0,u.length===0?"read no stylesheet — this check is not measuring anything":`!important motion declaration(s) outside the reduced-motion rule:
7
7
  `+y.join(`
8
8
  `)),e(`read the scripts (${g.length} .ts/.tsx files, at least ${o.minScripts})`,g.length>=o.minScripts);const b=g.flatMap(j),c=b.filter(t=>!(t.file in r));e("no shopper-facing code hard-codes a smooth scroll",c.length===0,'an explicit `behavior: "smooth"` beats the stylesheet, so this scroll animates for a visitor who asked for no motion:\n '+c.map(t=>`${t.file}:${t.line} ${t.text}`).join(`
9
- `)+"\n Call `scrollBehavior()` from components/useReducedMotion.ts instead. It still scrolls \u2014 it just arrives instead of travelling.");for(const[t,i]of Object.entries(r)){const a=m.get(t);e(`the editor waiver is still real: ${t} (${i})`,!!a&&/editor/i.test(a.text.slice(0,400))&&b.some(h=>h.file===t),a?"its own opening no longer says it is the editor's, or it no longer holds the literal \u2014 the waiver covers nothing":"the file is gone \u2014 remove the stale entry")}for(const[t,i]of Object.entries(o.askingSites??{})){const a=m.get(t),h=a?(S(a.text).match(/behaviou?r:\s*scrollBehavior\(\)/g)??[]).length:-1;e(`${t} still asks scrollBehavior() ${i} time(s)`,h===i,a?`found ${h}. A shopper-facing scroll stopped asking \u2014 if a site was deliberately removed, change the number and say which.`:"the file is gone")}if(o.hook!==void 0){const t=m.get(o.hook),i=t?S(t.text):"";e(`${o.hook} exports scrollBehavior()`,/export function scrollBehavior\(\)/.test(i)),e(`${o.hook} names the media query once and shares it`,/export const REDUCED_MOTION_QUERY/.test(i),"the media query must be named once and shared, not typed out per reader");const a=(i.match(/prefers-reduced-motion/g)??[]).length;e(`the query string appears exactly once in ${o.hook}`,a===1,`it appears ${a} times. Two spellings of the same question can disagree; there must be exactly one.`)}return d()}export{x as HARD_CODED_SMOOTH,R as MOVING_PROPERTIES,D as REQUIRED_FADES,j as hardCodedSmoothScrolls,$ as readReduceBlock,L as reducedMotion,p as valueOf};
9
+ `)+"\n Call `scrollBehavior()` from components/useReducedMotion.ts instead. It still scrolls — it just arrives instead of travelling.");for(const[t,i]of Object.entries(r)){const a=m.get(t);e(`the editor waiver is still real: ${t} (${i})`,!!a&&/editor/i.test(a.text.slice(0,400))&&b.some(h=>h.file===t),a?"its own opening no longer says it is the editor's, or it no longer holds the literal — the waiver covers nothing":"the file is gone — remove the stale entry")}for(const[t,i]of Object.entries(o.askingSites??{})){const a=m.get(t),h=a?(S(a.text).match(/behaviou?r:\s*scrollBehavior\(\)/g)??[]).length:-1;e(`${t} still asks scrollBehavior() ${i} time(s)`,h===i,a?`found ${h}. A shopper-facing scroll stopped asking — if a site was deliberately removed, change the number and say which.`:"the file is gone")}if(o.hook!==void 0){const t=m.get(o.hook),i=t?S(t.text):"";e(`${o.hook} exports scrollBehavior()`,/export function scrollBehavior\(\)/.test(i)),e(`${o.hook} names the media query once and shares it`,/export const REDUCED_MOTION_QUERY/.test(i),"the media query must be named once and shared, not typed out per reader");const a=(i.match(/prefers-reduced-motion/g)??[]).length;e(`the query string appears exactly once in ${o.hook}`,a===1,`it appears ${a} times. Two spellings of the same question can disagree; there must be exactly one.`)}return d()}export{x as HARD_CODED_SMOOTH,R as MOVING_PROPERTIES,D as REQUIRED_FADES,j as hardCodedSmoothScrolls,$ as readReduceBlock,L as reducedMotion,p as valueOf};
@@ -1,5 +1,5 @@
1
1
  import{CHROME_WIDTH_CAP_CLASS as v,PAGE_WIDTH as o,SITE_WIDTH_CAP_CLASS as u,SITE_WIDTH_VAR as $}from"../content/page-width.js";import{ruleChecks as C}from"./rule.js";const i=/max-w-\[1\d{3}px\]/,f=s=>s.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),E=s=>/^\s*(\/\/|\*|\/\*)/.test(s);function x(s){const n=[];return s.text.split(`
2
2
  `).forEach((t,h)=>{if(!E(t))for(const r of t.matchAll(new RegExp(i,"g")))n.push({file:s.path,line:h+1,cls:r[0]})}),n}const b=s=>s.cap==="chrome"?v:u;function S(s,n){const{check:t,report:h}=C("route content width"),r=n.measures??[],g=[...r,...n.ownedElsewhere??[]],m=n.moved??[];t("a site's width starts at 1000px",o.min===1e3,`PAGE_WIDTH.min is ${o.min}`),t("the band holds the narrowest site width",i.test(`max-w-[${o.min}px]`)),t("the band holds the built-in site width",i.test(`max-w-[${o.default}px]`)),t("a cap below the band is never looked at",!i.test("max-w-[999px]")&&!i.test("max-w-[680px]")),t("the replacement class is not itself a literal in the band",!i.test(u));{const e=x({path:"fixture.tsx",text:["// a doc line naming max-w-[1100px] is not a use",'<div className="mx-auto max-w-[1100px]" />'].join(`
3
- `)});t("the reader finds a cap in code and skips a comment line",e.length===1&&e[0].line===2,JSON.stringify(e))}const c=s.filter(e=>/\.tsx?$/.test(e.path));t(`read the source (${c.length} TS/TSX files, at least ${n.minFiles})`,c.length>=n.minFiles,"fewer means the walk broke, and the census below is vacuous");const d=c.flatMap(x).filter(e=>!g.some(a=>a.file===e.file&&a.cls===e.cls));t("no route layout types its own content column (no literal cap in the 1000\u20131999px band)",d.length===0,`${d.length} cap(s) in the band are a hand-typed content column. Each should read ${u} \u2014 or, if it is a reading measure, be named as one with the reason it is one:
3
+ `)});t("the reader finds a cap in code and skips a comment line",e.length===1&&e[0].line===2,JSON.stringify(e))}const c=s.filter(e=>/\.tsx?$/.test(e.path));t(`read the source (${c.length} TS/TSX files, at least ${n.minFiles})`,c.length>=n.minFiles,"fewer means the walk broke, and the census below is vacuous");const d=c.flatMap(x).filter(e=>!g.some(a=>a.file===e.file&&a.cls===e.cls));t("no route layout types its own content column (no literal cap in the 1000–1999px band)",d.length===0,`${d.length} cap(s) in the band are a hand-typed content column. Each should read ${u} — or, if it is a reading measure, be named as one with the reason it is one:
4
4
  `+d.map(e=>`${e.file}:${e.line} ${e.cls}`).join(`
5
- `));const p=new Map(c.map(e=>[e.path,e.text]));for(const e of r)t(`the measure is still there: ${e.file} ${e.cls}`,new RegExp(f(e.cls)).test(p.get(e.file)??""),"delete that measure rather than leaving a hole the next hard-coded column can sit in"),t(`\u2026and it carries its reason: ${e.file} ${e.cls}`,e.why.trim().length>40,"a measure needs its reason");for(const e of m){const a=p.get(e.file);if(!t(`read the moved column's file ${e.file} (${e.what})`,a!==void 0,"the file is gone \u2014 delete its row"))continue;const l=b(e);t(`${e.file} (${e.what}) declares a cap that reads ${$}`,l.includes($),l);const w=[...a.matchAll(new RegExp(f(l),"g"))].length;t(`${e.file} (${e.what}) carries ${e.caps} ${e.cap==="chrome"?"chrome":"site"} width cap(s)`,w===e.caps,`found ${w}`),t(`${e.file} (${e.what}) no longer types its old ${e.from}px column`,!new RegExp(f(`max-w-[${e.from}px]`)).test(a)),t(`${e.file} (${e.what}) gets wider at the default width, never narrower`,o.default>e.from,`it would NARROW by ${e.from-o.default}px`)}if(n.movedColumns!==void 0){const e=m.reduce((a,l)=>a+l.moved,0);t(`the moved list and the census agree: ${n.movedColumns} column(s) moved across ${m.length} file(s)`,e===n.movedColumns,`the rows add up to ${e} \u2014 a column moved without a row stops the report of what a merchant will SEE change matching the code`)}return h()}export{S as routeContentWidth};
5
+ `));const p=new Map(c.map(e=>[e.path,e.text]));for(const e of r)t(`the measure is still there: ${e.file} ${e.cls}`,new RegExp(f(e.cls)).test(p.get(e.file)??""),"delete that measure rather than leaving a hole the next hard-coded column can sit in"),t(`…and it carries its reason: ${e.file} ${e.cls}`,e.why.trim().length>40,"a measure needs its reason");for(const e of m){const a=p.get(e.file);if(!t(`read the moved column's file ${e.file} (${e.what})`,a!==void 0,"the file is gone — delete its row"))continue;const l=b(e);t(`${e.file} (${e.what}) declares a cap that reads ${$}`,l.includes($),l);const w=[...a.matchAll(new RegExp(f(l),"g"))].length;t(`${e.file} (${e.what}) carries ${e.caps} ${e.cap==="chrome"?"chrome":"site"} width cap(s)`,w===e.caps,`found ${w}`),t(`${e.file} (${e.what}) no longer types its old ${e.from}px column`,!new RegExp(f(`max-w-[${e.from}px]`)).test(a)),t(`${e.file} (${e.what}) gets wider at the default width, never narrower`,o.default>e.from,`it would NARROW by ${e.from-o.default}px`)}if(n.movedColumns!==void 0){const e=m.reduce((a,l)=>a+l.moved,0);t(`the moved list and the census agree: ${n.movedColumns} column(s) moved across ${m.length} file(s)`,e===n.movedColumns,`the rows add up to ${e} — a column moved without a row stops the report of what a merchant will SEE change matching the code`)}return h()}export{S as routeContentWidth};
@@ -1 +1 @@
1
- const u="lib/source-rules/";function c(r){return r.startsWith(u)}function s(r){const e=[];return{check(n,t,o){return e.push(t||o===void 0?{name:n,ok:t}:{name:n,ok:t,detail:o}),t},report(){return e.length===0&&e.push({name:`${r} checked nothing`,ok:!1}),{rule:r,checks:e}}}}function i(r){return r.checks.filter(e=>!e.ok)}function h(r){return r.checks.map(e=>e.ok?` ok ${e.name}`:` FAIL ${e.name}${e.detail?` \u2014 ${e.detail}`:""}`)}export{u as RULES_DIR,i as failedChecks,c as isRuleSource,h as reportLines,s as ruleChecks};
1
+ const u="lib/source-rules/";function c(r){return r.startsWith(u)}function s(r){const e=[];return{check(n,t,o){return e.push(t||o===void 0?{name:n,ok:t}:{name:n,ok:t,detail:o}),t},report(){return e.length===0&&e.push({name:`${r} checked nothing`,ok:!1}),{rule:r,checks:e}}}}function i(r){return r.checks.filter(e=>!e.ok)}function h(r){return r.checks.map(e=>e.ok?` ok ${e.name}`:` FAIL ${e.name}${e.detail?` — ${e.detail}`:""}`)}export{u as RULES_DIR,i as failedChecks,c as isRuleSource,h as reportLines,s as ruleChecks};