efficient-web-builder 2.0.0 → 3.0.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 (145) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +7 -0
  3. package/dist/components/BlogPostSource.js +1 -1
  4. package/dist/components/Breadcrumb.js +1 -1
  5. package/dist/components/Btn.js +1 -1
  6. package/dist/components/CardShadowField.js +1 -1
  7. package/dist/components/CategoryBar.js +1 -1
  8. package/dist/components/ChartSection.js +1 -1
  9. package/dist/components/ChatSettingsPanel.js +1 -1
  10. package/dist/components/ClearColoursDialog.js +1 -1
  11. package/dist/components/ContactLocations.js +5 -5
  12. package/dist/components/CornersField.js +1 -1
  13. package/dist/components/DocBody.js +1 -1
  14. package/dist/components/DuplicateToPageMenu.js +1 -1
  15. package/dist/components/FeaturedCover.js +1 -1
  16. package/dist/components/FieldGlyph.js +1 -1
  17. package/dist/components/Footprint.js +1 -1
  18. package/dist/components/HeadingAudit.js +1 -1
  19. package/dist/components/HeroSection.js +1 -1
  20. package/dist/components/HoverEffectField.js +1 -1
  21. package/dist/components/ImageBlock.js +1 -1
  22. package/dist/components/ImageFocusRow.js +1 -1
  23. package/dist/components/LocationsGroupedField.js +1 -1
  24. package/dist/components/LongRead.js +1 -1
  25. package/dist/components/MediaUploadField.js +1 -1
  26. package/dist/components/MockupSection.js +1 -1
  27. package/dist/components/PagesPanel.js +1 -1
  28. package/dist/components/PartInkControl.js +1 -1
  29. package/dist/components/PartResetDot.js +1 -1
  30. package/dist/components/PartSettingsPanel.js +1 -1
  31. package/dist/components/PostCard.js +1 -1
  32. package/dist/components/ProcessSteps.js +1 -1
  33. package/dist/components/RMark.js +1 -1
  34. package/dist/components/Reel.js +1 -1
  35. package/dist/components/SectionEditor.js +1 -1
  36. package/dist/components/SectionHeader.js +1 -1
  37. package/dist/components/SectionThumbnail.js +1 -1
  38. package/dist/components/SlotChildRows.js +1 -1
  39. package/dist/components/SourceStatus.js +1 -1
  40. package/dist/components/Split.js +1 -1
  41. package/dist/components/StandaloneSectionEditor.js +1 -1
  42. package/dist/components/StylesPanel.js +1 -1
  43. package/dist/components/SwitchToBoxFooter.js +1 -1
  44. package/dist/components/Testimonials.js +1 -1
  45. package/dist/components/TextStyleRow.js +1 -1
  46. package/dist/components/TextStylesField.js +1 -1
  47. package/dist/components/ThemeCard.js +1 -1
  48. package/dist/components/ThemeColourField.js +1 -1
  49. package/dist/components/ThemeField.js +1 -1
  50. package/dist/components/TickerSettingsPanel.js +1 -1
  51. package/dist/components/Timeline.js +1 -1
  52. package/dist/components/TimelineTrack.js +1 -1
  53. package/dist/components/TitleWithButtons.js +1 -1
  54. package/dist/components/WebsiteScreenParts.js +1 -1
  55. package/dist/components/WebsiteSettingsPanel.d.ts +5 -9
  56. package/dist/components/WebsiteSettingsPanel.js +1 -1
  57. package/dist/components/goldPhrases.js +1 -1
  58. package/dist/components/storefront-editor.css +262 -542
  59. package/dist/lib/content/contact.js +3 -3
  60. package/dist/lib/content/subscribe.js +1 -1
  61. package/dist/lib/puck/ColourControl.d.ts +5 -0
  62. package/dist/lib/puck/ColourControl.js +1 -1
  63. package/dist/lib/puck/MarkupToolbar.js +1 -1
  64. package/dist/lib/puck/blocks/general/CategoryBar/fields.js +1 -1
  65. package/dist/lib/puck/blocks/general/Chart/fields.js +1 -1
  66. package/dist/lib/puck/blocks/general/FAQ/render.js +1 -1
  67. package/dist/lib/puck/blocks/general/FeaturedCover/render.js +1 -1
  68. package/dist/lib/puck/blocks/general/Footprint/fields.js +1 -1
  69. package/dist/lib/puck/blocks/general/IntroBleed/render.js +1 -1
  70. package/dist/lib/puck/blocks/general/LogoBlock/fields.js +1 -1
  71. package/dist/lib/puck/blocks/general/LongRead/render.js +1 -1
  72. package/dist/lib/puck/blocks/general/Masthead/render.js +1 -1
  73. package/dist/lib/puck/blocks/general/Mockup/fields.js +1 -1
  74. package/dist/lib/puck/blocks/general/OrderSummary/render.js +1 -1
  75. package/dist/lib/puck/blocks/general/ParagraphsA/render.js +1 -1
  76. package/dist/lib/puck/blocks/general/PostCard/render.js +1 -1
  77. package/dist/lib/puck/blocks/general/ProcessSteps/render.js +1 -1
  78. package/dist/lib/puck/blocks/general/Series/render.js +1 -1
  79. package/dist/lib/puck/blocks/general/Split/render.js +1 -1
  80. package/dist/lib/puck/blocks/general/Testimonials/fields.js +1 -1
  81. package/dist/lib/puck/blocks/general/Testimonials/render.js +1 -1
  82. package/dist/lib/puck/blocks/general/TimelineTrack/render.js +1 -1
  83. package/dist/lib/puck/blocks/placeholders.js +1 -1
  84. package/dist/lib/puck/controls.d.ts +0 -10
  85. package/dist/lib/puck/controls.js +1 -1
  86. package/dist/lib/puck/field-groups.js +1 -1
  87. package/dist/lib/puck/footer-block-row-name.js +1 -1
  88. package/dist/lib/puck/geocode-resolve.js +1 -1
  89. package/dist/lib/puck/locations-tree.js +2 -2
  90. package/dist/lib/puck/neutralization-contracts.js +1 -1
  91. package/dist/lib/puck/part-rows.js +1 -1
  92. package/dist/lib/puck/section-catalog.js +1 -1
  93. package/dist/lib/puck/section-descriptions.js +1 -1
  94. package/dist/lib/sections/divider-style.js +1 -1
  95. package/dist/lib/sections/product-card-style.js +1 -1
  96. package/dist/lib/sections/ticker-style.js +1 -1
  97. package/dist/lib/source-rules/accent-token-name.js +2 -2
  98. package/dist/lib/source-rules/button-corners.js +1 -1
  99. package/dist/lib/source-rules/editor-chrome-note.js +2 -2
  100. package/dist/lib/source-rules/editor-context-menu.js +2 -2
  101. package/dist/lib/source-rules/editor-puck-drag-off.js +2 -2
  102. package/dist/lib/source-rules/efficient-easel-version.js +1 -1
  103. package/dist/lib/source-rules/every-button-follows-the-button-colour.js +4 -4
  104. package/dist/lib/source-rules/flat-theme-palette.js +2 -2
  105. package/dist/lib/source-rules/full-width-means-full.js +1 -1
  106. package/dist/lib/source-rules/heading-ceiling-sweep.js +3 -3
  107. package/dist/lib/source-rules/label-hints.js +4 -4
  108. package/dist/lib/source-rules/label-vocabulary.js +5 -5
  109. package/dist/lib/source-rules/mirrored-rows-agree.js +4 -4
  110. package/dist/lib/source-rules/no-comment-in-jsx.js +2 -2
  111. package/dist/lib/source-rules/no-glyph-rows.js +2 -2
  112. package/dist/lib/source-rules/one-colour-control.js +2 -2
  113. package/dist/lib/source-rules/one-dropdown.js +2 -2
  114. package/dist/lib/source-rules/one-editable-list.js +3 -3
  115. package/dist/lib/source-rules/one-editor-list.js +1 -1
  116. package/dist/lib/source-rules/one-right-click.js +2 -2
  117. package/dist/lib/source-rules/one-switch.js +1 -1
  118. package/dist/lib/source-rules/one-up-level-link.js +7 -7
  119. package/dist/lib/source-rules/panel-colour-tokens.js +7 -7
  120. package/dist/lib/source-rules/panel-no-divider-lines.js +5 -5
  121. package/dist/lib/source-rules/panel-one-arrow.js +1 -1
  122. package/dist/lib/source-rules/panel-one-hover.js +2 -2
  123. package/dist/lib/source-rules/panel-rows-use-the-shared-row.js +5 -5
  124. package/dist/lib/source-rules/part-slider-fills-row.js +8 -8
  125. package/dist/lib/source-rules/part-style-naming.js +1 -1
  126. package/dist/lib/source-rules/puck-document-surfaces.js +6 -6
  127. package/dist/lib/source-rules/reduced-motion.js +2 -2
  128. package/dist/lib/source-rules/route-content-width.js +2 -2
  129. package/dist/lib/source-rules/rule.js +1 -1
  130. package/dist/lib/source-rules/section-theme-preview.js +2 -2
  131. package/dist/lib/source-rules/site-square-by-default.js +1 -1
  132. package/dist/lib/source-rules/site-width.js +2 -2
  133. package/dist/lib/source-rules/size-token-paints.js +2 -2
  134. package/dist/lib/source-rules/social-icons-import.js +1 -1
  135. package/dist/lib/source-rules/source-status-is-shared.js +1 -1
  136. package/dist/lib/source-rules/style-face-matches-size.js +3 -3
  137. package/dist/lib/source-rules/support/label-extraction.js +5 -5
  138. package/dist/lib/source-rules/support/part-slider-call-sites.js +4 -4
  139. package/dist/lib/source-rules/support/registry-source.js +1 -1
  140. package/dist/lib/source-rules/theme-mode-controls-gone.js +3 -3
  141. package/dist/lib/source-rules/upload-intake-guard.js +2 -2
  142. package/dist/lib/source-rules/word-styles.js +2 -2
  143. package/package.json +7 -7
  144. package/dist/lib/puck/slider-typing.d.ts +0 -9
  145. package/dist/lib/puck/slider-typing.js +0 -1
@@ -1 +1 @@
1
- import{anchorFields as m,bucketHeader as p,cardMediaFields as B,cardShowFields as E,cardStyleFields as w,foldMetaOf as y,group as O,heightFields as T,sectionAnimationFields as F,sectionShapeFields as I,sectionThemeFields as x,spacingFields as A,widthField as H}from"./controls.js";import{deviceField as R,deviceMetaOf as D}from"./device-fields.js";import{SETTINGS_BUCKETS as $}from"efficient-easel/panel";import{BUCKET_KEY as i}from"./field-buckets.js";const s=$,V=[i.Layout,...Object.keys(A({phone:!0})),...Object.keys(H("full")),...Object.keys(T()),...Object.keys(m())],z=[i.Style,...Object.keys(x())],X=[i.Style,...Object.keys(I())],Z=[i.Animation,...Object.keys(F())],K=Object.keys(B()),M=new Set(["cardImage","imageBackground"]),ee=[i.Style,...K.filter(o=>o.startsWith("_g")||M.has(o))],te=Object.keys(w()),oe=Object.keys(E({bottomDefault:!0})),P="Editor only \u2014 not shown live",v={"Archive link":"Content",Article:"Content",Boxes:"Content",Blocks:"Content","Blog posts":"Content","Browser mock":"Content",Button:"Content","Call to action":"Content","Car prompt":"Content","Card link":"Content",Cards:"Content","Claims bars":"Content","Contact info":"Content",Content:"Content","Content source":"Content",Contents:"Content",Filters:"Content","Show on a card":"Content","Opening filter":"Content",Footnote:"Content",Form:"Content",Founders:"Content","Gallery A":"Content","Gallery B":"Content","Gallery link":"Content",Header:"Content","Header link":"Content","Image caption":"Content","In this issue":"Content",Items:"Content","Kanban columns":"Content",Lead:"Content","Left panel":"Content",Link:"Content",Links:"Content","Message form":"Content",Metadata:"Content",Milestones:"Content","Mockup card":"Content","Newsletter panel":"Content",Offices:"Content",Options:"Content","Others gallery":"Content","Overlay button":"Content",Product:"Content","Product page":"Content",Products:"Content","Pull quote":"Content",Questions:"Content","Recent issues":"Content","Recent posts":"Content",Reviews:"Content","Right panel \xB7 actions":"Content","Right panel \xB7 choices":"Content","Right panel \xB7 form":"Content","Routing groups":"Content",Rows:"Content",Section:"Content","Section header":"Content",Sections:"Content","Signup form":"Content","Source product":"Content","Stat & note":"Content",Stats:"Content","Submit button":"Content","Success state":"Content",Wheel:"Content",Terminal:"Content",Text:"Content","Text column":"Content","Timeline entries":"Content","Top row":"Content",Total:"Content","Trailing note":"Content","\u201COther\u201D reason":"Content","Background images":"Style",Gallery:"Style",Image:"Style","Image column":"Style",Images:"Style",Logo:"Style",Media:"Style","Background image":"Style",Color:"Style","Image tint":"Style",Style:"Style",Shape:"Style",Texture:"Style","Text style":"Style",Motion:"Animation",Animation:"Animation",Anchor:"Layout",Arrangement:"Layout","Auto-slide":"Layout",Bar:"Layout","Cell width":"Layout",Behavior:"Layout",Controls:"Layout",Diagram:"Layout",[P]:"Layout","Filters shown":"Layout","Slide picker":"Layout",Arrows:"Layout","On a phone":"Layout",Nudge:"Layout","Image gallery":"Layout",Grid:"Layout",Layout:"Layout",Padding:"Layout",Price:"Layout",Preview:"Layout",Setup:"Layout","Show / hide elements":"Layout","Show in the buy box":"Layout",Visibility:"Layout",Width:"Layout",Height:"Layout"},_={"AlloyWheelsListing._gCards":"Style","ProductGrid._gCards":"Style"};function j(o,t,n){return _[`${o}.${t}`]??v[n]}const G={"ImageBlock._gStyle":"duplicated the Style bucket header above it (2026-09-04)","ButtonBlock._gStyle":"duplicated the Style bucket header above it (2026-09-04)","HeroGroup._gContent":"duplicated the Content bucket header above it (2026-09-04)","HeroGroup._gLayout":"duplicated the Layout bucket header above it (2026-09-04)","Footer._gLayout":"duplicated the Layout bucket header above it (2026-09-04)","FooterLogo._gContent":"duplicated the Content bucket header above it (2026-09-04)","FooterLogo._gStyle":"duplicated the Style bucket header above it (2026-09-04)","FooterMenu._gContent":"duplicated the Content bucket header above it (2026-09-04)","FooterMenu._gStyle":"duplicated the Style bucket header above it (2026-09-04)","FooterGroup._gLayout":"duplicated the Layout bucket header above it (2026-09-04)","FooterText._gContent":"duplicated the Content bucket header above it (2026-09-04)","FooterText._gStyle":"duplicated the Style bucket header above it (2026-09-04)","FooterSocial._gStyle":"duplicated the Style bucket header above it (born tabbed, 2026-09-17)","FooterSocial._gLayout":"duplicated the Layout bucket header above it (born tabbed, 2026-09-17)","MenuBlock._gContent":"duplicated the Content bucket header above it (born tabbed, H.1)","MenuBlock._gStyle":"duplicated the Style bucket header above it (born tabbed, H.1)","EmailFormBlock._gContent":"duplicated the Content bucket header above it (born tabbed, H.1)","SocialLogosBlock._gStyle":"duplicated the Style bucket header above it (born tabbed, H.1)","SocialLogosBlock._gLayout":"duplicated the Layout bucket header above it (born tabbed, H.1)","SearchBlock._gStyle":"duplicated the Style bucket header above it (born tabbed, H.2)","CartBlock._gStyle":"duplicated the Style bucket header above it (born tabbed, H.2)","Split._gContent":"duplicated the Content bucket header above it (2026-09-04)","Gallery._gVehicleContent":"duplicated the Content bucket header above it (2026-09-04)","Gallery._gWheelContent":"duplicated the Content bucket header above it (2026-09-04)","Gallery._gOthersContent":"duplicated the Content bucket header above it (2026-09-04)","Subscribe._gHeadCopy":"duplicated the Content bucket header above it (2026-09-04)","Subscribe._gLeftCopy":"duplicated the Content bucket header above it (2026-09-04)","Subscribe._gRightCopy":"duplicated the Content bucket header above it (2026-09-04)","CollectionCard._gContent":"duplicated the Content bucket header above it (A Card Sets Its Own Corners)","HeroSlide._gContent":"duplicated the Content bucket header above it (Hero Slide Tabs And Theme)","BrandStrip._gContent":"duplicated the Content bucket header above it (66338)","BrandStrip._gStyle":"duplicated the Style bucket header above it (66338)","BrandStrip._gLayout":"duplicated the Layout bucket header above it (66338)","KeyFeatures._gContent":"duplicated the Content bucket header above it (66049)","KeyFeatures._gStyle":"duplicated the Style bucket header above it (66049)","ParagraphsA._gContent":"duplicated the Content bucket header above it (66049)","ParagraphsA._gStyle":"duplicated the Style bucket header above it (66049)","Chart._gContent":"duplicated the Content bucket header above it (66049)","Chart._gStyle":"duplicated the Style bucket header above it (66049)","Logos._gContent":"duplicated the Content bucket header above it (66049)","Logos._gStyle":"duplicated the Style bucket header above it (66049)","Mockup._gContent":"duplicated the Content bucket header above it (66049)","Mockup._gStyle":"duplicated the Style bucket header above it (66049)","Stats._gContent":"duplicated the Content bucket header above it (66049)","Stats._gStyle":"duplicated the Style bucket header above it (66049)","StatsParagraph._gContent":"duplicated the Content bucket header above it (66049)","StatsParagraph._gStyle":"duplicated the Style bucket header above it (66049)","Testimonials._gContent":"duplicated the Content bucket header above it (66049)","Testimonials._gStyle":"duplicated the Style bucket header above it (66049)","AppointmentBooking._gContent":"duplicated the Content bucket header above it (66050)","BookingBox._gContent":"duplicated the Content bucket header above it ([SPLIT] Booking Form Into Three Boxes) \u2014 the same fault its own section retired in 66050, one level down","FAQ._gContent":"duplicated the Content bucket header above it (66050, family sweep)","FAQ._gLayout":"duplicated the Layout bucket header above it (66050, family sweep)","Form._gContent":"duplicated the Content bucket header above it (66050)","ReviewForm._gContent":"duplicated the Content bucket header above it (66050)","SubmitBuild._gContent":"duplicated the Content bucket header above it (66050)","SubmitBox._gContent":"duplicated the Content bucket header above it ([SPLIT] SubmitBuild Keeps Its Two Columns) \u2014 the same fault its own section retired in 66050, one level down","CheckoutCTA._gContent":"duplicated the Content bucket header above it (66051)","CheckoutCTA._gLayout":"duplicated the Layout bucket header above it (66051)","DeliveryOptions._gContent":"duplicated the Content bucket header above it (66051)","DeliveryOptions._gLayout":"duplicated the Layout bucket header above it (66051)","OrderSummary._gContent":"duplicated the Content bucket header above it (66051)","OrderSummary._gLayout":"duplicated the Layout bucket header above it (66051)","PaymentBadges._gContent":"duplicated the Content bucket header above it (66051)","PaymentBadges._gLayout":"duplicated the Layout bucket header above it (66051)","Collections._gContent":"duplicated the Content bucket header above it (66052)","Collections._gLayout":"duplicated the Layout bucket header above it (66052)","DocMasthead._gContent":"duplicated the Content bucket header above it (66053)","Events._gContent":"duplicated the Content bucket header above it (66054)","Events._gLayout":"duplicated the Layout bucket header above it (66054)","FeaturedCover._gContent":"duplicated the Content bucket header above it (66054)","Series._gStyle":"duplicated the Style bucket header above it (66054)","Flex._gContent":"duplicated the Content bucket header above it (66055)","Flex._gLayout":"duplicated the Layout bucket header above it (66055)","SectionHeader._gContent":"duplicated the Content bucket header above it (66055)","Breadcrumb._gContent":"duplicated the Content bucket header above it (66056)","CategoryBar._gStyle":"duplicated the Style bucket header above it (66056)","Hero._gContent":"duplicated the Content bucket header above it (66056)","Hero._gStyle":"duplicated the Style bucket header above it (66056)","HeroSingle._gStyle":"duplicated the Style bucket header above it (66056)","IntroBleed._gContent":"duplicated the Content bucket header above it (66056)","IntroBleed._gLayout":"duplicated the Layout bucket header above it (66056)","BundleOffer._gLayout":"duplicated the Layout bucket header above it (66059)","ProcessSteps._gContent":"duplicated the Content bucket header above it (66059)","Banner._gContent":"duplicated the Content bucket header above it (66060.1)","Banner._gLayout":"duplicated the Layout bucket header above it, and governed no controls (66060.1)","Manifesto._gContent":"duplicated the Content bucket header above it (66060.1)","SubscribeBlogs._gHeadCopy":"duplicated the Content bucket header above it (66060.1)","SubscribeBlogs._gLeftCopy":"duplicated the Content bucket header above it (66060.1)","SubscribeBlogs._gRightCopy":"duplicated the Content bucket header above it (66060.1)","Ticker._gContent":"duplicated the Content bucket header above it (66060.1)","Ticker._gStyle":"duplicated the Style bucket header above it (66060.1)","Timeline._gContent":"duplicated the Content bucket header above it (66060.1)","TitleWithButtons._gContent":"duplicated the Content bucket header above it (66060.1)","TitleWithButtons._gStyle":"duplicated the Style bucket header above it (66060.1)","Cards._gContent":"duplicated the Content bucket header above it (66218)","Cards._gStyle":"duplicated the Style bucket header above it (66218)","Prose._gContent":"duplicated the Content bucket header above it (66218)","Reviews._gContent":"duplicated the Content bucket header above it (66218)","Reviews._gStyle":"duplicated the Style bucket header above it (66218)"};function ne(o){const t=new Set(Object.values(i));for(const[n,d]of Object.entries(o))if(!t.has(n)&&y(d)?.level===1)return 1;return 2}function Y(o){const t=`${o}.`;return Object.keys(G).filter(n=>n.startsWith(t)).map(n=>n.slice(t.length))}function N(o,t){const n=D(o),d=O(t);return n?R(n.tier,d):d}function ae(o,t){const n=[],d=new Set(Y(t)),g=new Set;let h=null;for(const[e,r]of Object.entries(o)){const a=y(r);if(!a){h||n.push(h={bucket:"Content",entries:[]}),h.entries.push([e,r]);continue}if(d.has(e)){const l=v[a.label];if(a.level!==2||!l||a.label!==l)throw new Error(`${t}.${e}: RETIRED (lib/puck/field-groups.ts) lists this sub-heading as one whose name duplicates its bucket, but it is a level-${a.level} "${a.label}" header in the ${l??"(unclassified)"} bucket. Only a level-2 header whose label IS its bucket's name may be retired \u2014 anything else is a heading a merchant reads for its own sake. Fix the row or drop it.`);g.add(e),n.push(h={bucket:l,entries:[]});continue}const c=j(t,e,a.label);if(!c)throw new Error(`${t}.${e}: the inspector group "${a.label}" is not classified in LABEL_BUCKET (lib/puck/field-groups.ts), so there is no way to say which of ${s.join(" / ")} its controls belong under. Classify it \u2014 an unclassified group silently lands wherever it was declared, which is the whole bug.`);const u=e===i[c]&&a.level===1;if(!u&&e===i[c])throw new Error(`${t}.${e}: that key is reserved for the ${c} bucket header, but this field is a level-${a.level} "${a.label}" header, so it cannot serve as one. Rename the section's own field.`);n.push(h={bucket:c,entries:[[e,u||a.level===2?r:N(r,a.label)]],isBucketHeader:u})}const f=new Set(Object.entries(o).filter(([,e])=>y(e)).map(([e])=>e)),k=Object.keys(_).filter(e=>e.startsWith(`${t}.`)).filter(e=>!f.has(e.slice(t.length+1)));if(k.length)throw new Error(`${t}: KEY_BUCKET (lib/puck/field-groups.ts) lists ${JSON.stringify(k)}, but no fold header by that key is in this section's panel. Either the heading was renamed or removed and the row is stale, or the row names the wrong key \u2014 an override that quietly matches nothing is worse than no override.`);const b=n.some(e=>e.isBucketHeader)?[]:[...d].filter(e=>!g.has(e));if(b.length)throw new Error(`${t}: RETIRED (lib/puck/field-groups.ts) lists ${JSON.stringify(b)}, but this section has no such fold header${b.length>1?"s":""}. Either the heading was renamed or removed and the row is stale, or the row names the wrong section \u2014 a list that quietly matches nothing is worse than no list.`);for(const e of s){const r=i[e];if(r in o&&!n.some(a=>a.isBucketHeader&&a.entries[0]?.[0]===r))throw new Error(`${t}.${r} is reserved for the ${e} bucket header but is not a level-1 "${e}" fold header. Rename the section's own field.`)}const S=s.filter(e=>n.some(r=>r.bucket===e)),C={};for(const e of s){const r=n.filter(u=>u.bucket===e);if(!r.length)continue;const a=r.findIndex(u=>u.isBucketHeader);a>0&&r.unshift(...r.splice(a,1));const c=p(e,e===S[0]?S:null);a<0&&(C[i[e]]=c);for(const u of r)for(const[l,L]of u.entries)C[l]=u.isBucketHeader&&l===i[e]?c:L}return C}function ie(o){return i.Content in o?o:{[i.Content]:p("Content",null),...o}}function re(o){return s.some(t=>i[t]in o)}function de(o){const t=s.filter(d=>i[d]in o);if(t.length===0)return o;const n={...o};for(const d of t)n[i[d]]=p(d,d===t[0]?t:null);return n}export{ee as BOUND_CARD_MEDIA_FIELD_KEYS,s as BUCKETS,i as BUCKET_KEY,K as CARD_MEDIA_FIELD_KEYS,oe as CARD_SHOW_FIELD_KEYS,te as CARD_STYLE_FIELD_KEYS,P as EDITOR_ONLY_GROUP,_ as KEY_BUCKET,v as LABEL_BUCKET,V as LAYOUT_FIELD_KEYS,G as RETIRED,Z as SECTION_ANIM_FIELD_KEYS,X as SECTION_SHAPE_FIELD_KEYS,z as SECTION_THEME_FIELD_KEYS,ae as assignSectionGroups,j as bucketOf,ne as foldTopTier,re as hasBucketTabs,de as reseatBucketTabs,Y as retiredHeadings,ie as seatContentBucket};
1
+ import{anchorFields as m,bucketHeader as p,cardMediaFields as B,cardShowFields as E,cardStyleFields as w,foldMetaOf as y,group as O,heightFields as T,sectionAnimationFields as F,sectionShapeFields as I,sectionThemeFields as x,spacingFields as A,widthField as H}from"./controls.js";import{deviceField as R,deviceMetaOf as D}from"./device-fields.js";import{SETTINGS_BUCKETS as $}from"efficient-easel/panel";import{BUCKET_KEY as i}from"./field-buckets.js";const s=$,V=[i.Layout,...Object.keys(A({phone:!0})),...Object.keys(H("full")),...Object.keys(T()),...Object.keys(m())],z=[i.Style,...Object.keys(x())],X=[i.Style,...Object.keys(I())],Z=[i.Animation,...Object.keys(F())],K=Object.keys(B()),M=new Set(["cardImage","imageBackground"]),ee=[i.Style,...K.filter(o=>o.startsWith("_g")||M.has(o))],te=Object.keys(w()),oe=Object.keys(E({bottomDefault:!0})),P="Editor only — not shown live",v={"Archive link":"Content",Article:"Content",Boxes:"Content",Blocks:"Content","Blog posts":"Content","Browser mock":"Content",Button:"Content","Call to action":"Content","Car prompt":"Content","Card link":"Content",Cards:"Content","Claims bars":"Content","Contact info":"Content",Content:"Content","Content source":"Content",Contents:"Content",Filters:"Content","Show on a card":"Content","Opening filter":"Content",Footnote:"Content",Form:"Content",Founders:"Content","Gallery A":"Content","Gallery B":"Content","Gallery link":"Content",Header:"Content","Header link":"Content","Image caption":"Content","In this issue":"Content",Items:"Content","Kanban columns":"Content",Lead:"Content","Left panel":"Content",Link:"Content",Links:"Content","Message form":"Content",Metadata:"Content",Milestones:"Content","Mockup card":"Content","Newsletter panel":"Content",Offices:"Content",Options:"Content","Others gallery":"Content","Overlay button":"Content",Product:"Content","Product page":"Content",Products:"Content","Pull quote":"Content",Questions:"Content","Recent issues":"Content","Recent posts":"Content",Reviews:"Content","Right panel · actions":"Content","Right panel · choices":"Content","Right panel · form":"Content","Routing groups":"Content",Rows:"Content",Section:"Content","Section header":"Content",Sections:"Content","Signup form":"Content","Source product":"Content","Stat & note":"Content",Stats:"Content","Submit button":"Content","Success state":"Content",Wheel:"Content",Terminal:"Content",Text:"Content","Text column":"Content","Timeline entries":"Content","Top row":"Content",Total:"Content","Trailing note":"Content","“Other” reason":"Content","Background images":"Style",Gallery:"Style",Image:"Style","Image column":"Style",Images:"Style",Logo:"Style",Media:"Style","Background image":"Style",Color:"Style","Image tint":"Style",Style:"Style",Shape:"Style",Texture:"Style","Text style":"Style",Motion:"Animation",Animation:"Animation",Anchor:"Layout",Arrangement:"Layout","Auto-slide":"Layout",Bar:"Layout","Cell width":"Layout",Behavior:"Layout",Controls:"Layout",Diagram:"Layout",[P]:"Layout","Filters shown":"Layout","Slide picker":"Layout",Arrows:"Layout","On a phone":"Layout",Nudge:"Layout","Image gallery":"Layout",Grid:"Layout",Layout:"Layout",Padding:"Layout",Price:"Layout",Preview:"Layout",Setup:"Layout","Show / hide elements":"Layout","Show in the buy box":"Layout",Visibility:"Layout",Width:"Layout",Height:"Layout"},_={"AlloyWheelsListing._gCards":"Style","ProductGrid._gCards":"Style"};function j(o,t,n){return _[`${o}.${t}`]??v[n]}const G={"ImageBlock._gStyle":"duplicated the Style bucket header above it (2026-09-04)","ButtonBlock._gStyle":"duplicated the Style bucket header above it (2026-09-04)","HeroGroup._gContent":"duplicated the Content bucket header above it (2026-09-04)","HeroGroup._gLayout":"duplicated the Layout bucket header above it (2026-09-04)","Footer._gLayout":"duplicated the Layout bucket header above it (2026-09-04)","FooterLogo._gContent":"duplicated the Content bucket header above it (2026-09-04)","FooterLogo._gStyle":"duplicated the Style bucket header above it (2026-09-04)","FooterMenu._gContent":"duplicated the Content bucket header above it (2026-09-04)","FooterMenu._gStyle":"duplicated the Style bucket header above it (2026-09-04)","FooterGroup._gLayout":"duplicated the Layout bucket header above it (2026-09-04)","FooterText._gContent":"duplicated the Content bucket header above it (2026-09-04)","FooterText._gStyle":"duplicated the Style bucket header above it (2026-09-04)","FooterSocial._gStyle":"duplicated the Style bucket header above it (born tabbed, 2026-09-17)","FooterSocial._gLayout":"duplicated the Layout bucket header above it (born tabbed, 2026-09-17)","MenuBlock._gContent":"duplicated the Content bucket header above it (born tabbed, H.1)","MenuBlock._gStyle":"duplicated the Style bucket header above it (born tabbed, H.1)","EmailFormBlock._gContent":"duplicated the Content bucket header above it (born tabbed, H.1)","SocialLogosBlock._gStyle":"duplicated the Style bucket header above it (born tabbed, H.1)","SocialLogosBlock._gLayout":"duplicated the Layout bucket header above it (born tabbed, H.1)","SearchBlock._gStyle":"duplicated the Style bucket header above it (born tabbed, H.2)","CartBlock._gStyle":"duplicated the Style bucket header above it (born tabbed, H.2)","Split._gContent":"duplicated the Content bucket header above it (2026-09-04)","Gallery._gVehicleContent":"duplicated the Content bucket header above it (2026-09-04)","Gallery._gWheelContent":"duplicated the Content bucket header above it (2026-09-04)","Gallery._gOthersContent":"duplicated the Content bucket header above it (2026-09-04)","Subscribe._gHeadCopy":"duplicated the Content bucket header above it (2026-09-04)","Subscribe._gLeftCopy":"duplicated the Content bucket header above it (2026-09-04)","Subscribe._gRightCopy":"duplicated the Content bucket header above it (2026-09-04)","CollectionCard._gContent":"duplicated the Content bucket header above it (A Card Sets Its Own Corners)","HeroSlide._gContent":"duplicated the Content bucket header above it (Hero Slide Tabs And Theme)","BrandStrip._gContent":"duplicated the Content bucket header above it (66338)","BrandStrip._gStyle":"duplicated the Style bucket header above it (66338)","BrandStrip._gLayout":"duplicated the Layout bucket header above it (66338)","KeyFeatures._gContent":"duplicated the Content bucket header above it (66049)","KeyFeatures._gStyle":"duplicated the Style bucket header above it (66049)","ParagraphsA._gContent":"duplicated the Content bucket header above it (66049)","ParagraphsA._gStyle":"duplicated the Style bucket header above it (66049)","Chart._gContent":"duplicated the Content bucket header above it (66049)","Chart._gStyle":"duplicated the Style bucket header above it (66049)","Logos._gContent":"duplicated the Content bucket header above it (66049)","Logos._gStyle":"duplicated the Style bucket header above it (66049)","Mockup._gContent":"duplicated the Content bucket header above it (66049)","Mockup._gStyle":"duplicated the Style bucket header above it (66049)","Stats._gContent":"duplicated the Content bucket header above it (66049)","Stats._gStyle":"duplicated the Style bucket header above it (66049)","StatsParagraph._gContent":"duplicated the Content bucket header above it (66049)","StatsParagraph._gStyle":"duplicated the Style bucket header above it (66049)","Testimonials._gContent":"duplicated the Content bucket header above it (66049)","Testimonials._gStyle":"duplicated the Style bucket header above it (66049)","AppointmentBooking._gContent":"duplicated the Content bucket header above it (66050)","BookingBox._gContent":"duplicated the Content bucket header above it ([SPLIT] Booking Form Into Three Boxes) — the same fault its own section retired in 66050, one level down","FAQ._gContent":"duplicated the Content bucket header above it (66050, family sweep)","FAQ._gLayout":"duplicated the Layout bucket header above it (66050, family sweep)","Form._gContent":"duplicated the Content bucket header above it (66050)","ReviewForm._gContent":"duplicated the Content bucket header above it (66050)","SubmitBuild._gContent":"duplicated the Content bucket header above it (66050)","SubmitBox._gContent":"duplicated the Content bucket header above it ([SPLIT] SubmitBuild Keeps Its Two Columns) — the same fault its own section retired in 66050, one level down","CheckoutCTA._gContent":"duplicated the Content bucket header above it (66051)","CheckoutCTA._gLayout":"duplicated the Layout bucket header above it (66051)","DeliveryOptions._gContent":"duplicated the Content bucket header above it (66051)","DeliveryOptions._gLayout":"duplicated the Layout bucket header above it (66051)","OrderSummary._gContent":"duplicated the Content bucket header above it (66051)","OrderSummary._gLayout":"duplicated the Layout bucket header above it (66051)","PaymentBadges._gContent":"duplicated the Content bucket header above it (66051)","PaymentBadges._gLayout":"duplicated the Layout bucket header above it (66051)","Collections._gContent":"duplicated the Content bucket header above it (66052)","Collections._gLayout":"duplicated the Layout bucket header above it (66052)","DocMasthead._gContent":"duplicated the Content bucket header above it (66053)","Events._gContent":"duplicated the Content bucket header above it (66054)","Events._gLayout":"duplicated the Layout bucket header above it (66054)","FeaturedCover._gContent":"duplicated the Content bucket header above it (66054)","Series._gStyle":"duplicated the Style bucket header above it (66054)","Flex._gContent":"duplicated the Content bucket header above it (66055)","Flex._gLayout":"duplicated the Layout bucket header above it (66055)","SectionHeader._gContent":"duplicated the Content bucket header above it (66055)","Breadcrumb._gContent":"duplicated the Content bucket header above it (66056)","CategoryBar._gStyle":"duplicated the Style bucket header above it (66056)","Hero._gContent":"duplicated the Content bucket header above it (66056)","Hero._gStyle":"duplicated the Style bucket header above it (66056)","HeroSingle._gStyle":"duplicated the Style bucket header above it (66056)","IntroBleed._gContent":"duplicated the Content bucket header above it (66056)","IntroBleed._gLayout":"duplicated the Layout bucket header above it (66056)","BundleOffer._gLayout":"duplicated the Layout bucket header above it (66059)","ProcessSteps._gContent":"duplicated the Content bucket header above it (66059)","Banner._gContent":"duplicated the Content bucket header above it (66060.1)","Banner._gLayout":"duplicated the Layout bucket header above it, and governed no controls (66060.1)","Manifesto._gContent":"duplicated the Content bucket header above it (66060.1)","SubscribeBlogs._gHeadCopy":"duplicated the Content bucket header above it (66060.1)","SubscribeBlogs._gLeftCopy":"duplicated the Content bucket header above it (66060.1)","SubscribeBlogs._gRightCopy":"duplicated the Content bucket header above it (66060.1)","Ticker._gContent":"duplicated the Content bucket header above it (66060.1)","Ticker._gStyle":"duplicated the Style bucket header above it (66060.1)","Timeline._gContent":"duplicated the Content bucket header above it (66060.1)","TitleWithButtons._gContent":"duplicated the Content bucket header above it (66060.1)","TitleWithButtons._gStyle":"duplicated the Style bucket header above it (66060.1)","Cards._gContent":"duplicated the Content bucket header above it (66218)","Cards._gStyle":"duplicated the Style bucket header above it (66218)","Prose._gContent":"duplicated the Content bucket header above it (66218)","Reviews._gContent":"duplicated the Content bucket header above it (66218)","Reviews._gStyle":"duplicated the Style bucket header above it (66218)"};function ne(o){const t=new Set(Object.values(i));for(const[n,d]of Object.entries(o))if(!t.has(n)&&y(d)?.level===1)return 1;return 2}function Y(o){const t=`${o}.`;return Object.keys(G).filter(n=>n.startsWith(t)).map(n=>n.slice(t.length))}function N(o,t){const n=D(o),d=O(t);return n?R(n.tier,d):d}function ae(o,t){const n=[],d=new Set(Y(t)),g=new Set;let h=null;for(const[e,r]of Object.entries(o)){const a=y(r);if(!a){h||n.push(h={bucket:"Content",entries:[]}),h.entries.push([e,r]);continue}if(d.has(e)){const l=v[a.label];if(a.level!==2||!l||a.label!==l)throw new Error(`${t}.${e}: RETIRED (lib/puck/field-groups.ts) lists this sub-heading as one whose name duplicates its bucket, but it is a level-${a.level} "${a.label}" header in the ${l??"(unclassified)"} bucket. Only a level-2 header whose label IS its bucket's name may be retired — anything else is a heading a merchant reads for its own sake. Fix the row or drop it.`);g.add(e),n.push(h={bucket:l,entries:[]});continue}const c=j(t,e,a.label);if(!c)throw new Error(`${t}.${e}: the inspector group "${a.label}" is not classified in LABEL_BUCKET (lib/puck/field-groups.ts), so there is no way to say which of ${s.join(" / ")} its controls belong under. Classify it — an unclassified group silently lands wherever it was declared, which is the whole bug.`);const u=e===i[c]&&a.level===1;if(!u&&e===i[c])throw new Error(`${t}.${e}: that key is reserved for the ${c} bucket header, but this field is a level-${a.level} "${a.label}" header, so it cannot serve as one. Rename the section's own field.`);n.push(h={bucket:c,entries:[[e,u||a.level===2?r:N(r,a.label)]],isBucketHeader:u})}const f=new Set(Object.entries(o).filter(([,e])=>y(e)).map(([e])=>e)),k=Object.keys(_).filter(e=>e.startsWith(`${t}.`)).filter(e=>!f.has(e.slice(t.length+1)));if(k.length)throw new Error(`${t}: KEY_BUCKET (lib/puck/field-groups.ts) lists ${JSON.stringify(k)}, but no fold header by that key is in this section's panel. Either the heading was renamed or removed and the row is stale, or the row names the wrong key — an override that quietly matches nothing is worse than no override.`);const b=n.some(e=>e.isBucketHeader)?[]:[...d].filter(e=>!g.has(e));if(b.length)throw new Error(`${t}: RETIRED (lib/puck/field-groups.ts) lists ${JSON.stringify(b)}, but this section has no such fold header${b.length>1?"s":""}. Either the heading was renamed or removed and the row is stale, or the row names the wrong section — a list that quietly matches nothing is worse than no list.`);for(const e of s){const r=i[e];if(r in o&&!n.some(a=>a.isBucketHeader&&a.entries[0]?.[0]===r))throw new Error(`${t}.${r} is reserved for the ${e} bucket header but is not a level-1 "${e}" fold header. Rename the section's own field.`)}const S=s.filter(e=>n.some(r=>r.bucket===e)),C={};for(const e of s){const r=n.filter(u=>u.bucket===e);if(!r.length)continue;const a=r.findIndex(u=>u.isBucketHeader);a>0&&r.unshift(...r.splice(a,1));const c=p(e,e===S[0]?S:null);a<0&&(C[i[e]]=c);for(const u of r)for(const[l,L]of u.entries)C[l]=u.isBucketHeader&&l===i[e]?c:L}return C}function ie(o){return i.Content in o?o:{[i.Content]:p("Content",null),...o}}function re(o){return s.some(t=>i[t]in o)}function de(o){const t=s.filter(d=>i[d]in o);if(t.length===0)return o;const n={...o};for(const d of t)n[i[d]]=p(d,d===t[0]?t:null);return n}export{ee as BOUND_CARD_MEDIA_FIELD_KEYS,s as BUCKETS,i as BUCKET_KEY,K as CARD_MEDIA_FIELD_KEYS,oe as CARD_SHOW_FIELD_KEYS,te as CARD_STYLE_FIELD_KEYS,P as EDITOR_ONLY_GROUP,_ as KEY_BUCKET,v as LABEL_BUCKET,V as LAYOUT_FIELD_KEYS,G as RETIRED,Z as SECTION_ANIM_FIELD_KEYS,X as SECTION_SHAPE_FIELD_KEYS,z as SECTION_THEME_FIELD_KEYS,ae as assignSectionGroups,j as bucketOf,ne as foldTopTier,re as hasBucketTabs,de as reseatBucketTabs,Y as retiredHeadings,ie as seatContentBucket};
@@ -1 +1 @@
1
- function r(t,o){const n=t.props;if(t.type==="FooterMenu"){const e=n.menu;return n.heading||e?.name||"Menu"}if(t.type==="FooterText"){const e=typeof n.text=="string"?n.text.trim():"";return e?e.length>28?`${e.slice(0,28)}\u2026`:e:"Text"}return t.type==="FooterEmailSignup"?n.heading||"Email signup":o(t.type)}export{r as footerBlockRowName};
1
+ function r(t,o){const n=t.props;if(t.type==="FooterMenu"){const e=n.menu;return n.heading||e?.name||"Menu"}if(t.type==="FooterText"){const e=typeof n.text=="string"?n.text.trim():"";return e?e.length>28?`${e.slice(0,28)}…`:e:"Text"}return t.type==="FooterEmailSignup"?n.heading||"Email signup":o(t.type)}export{r as footerBlockRowName};
@@ -1 +1 @@
1
- import{geocodeAddress as c}from"../geocode.js";const i=a=>typeof a=="number"&&Number.isFinite(a);function r(a){return a.some(g=>{const l=g?.geocode;return l?l.pending||l.manual&&l.lat===void 0&&l.lng===void 0?!0:i(l.lat)&&l.lat!==g.lat||i(l.lng)&&l.lng!==g.lng:!1})}async function m(a,g,{geocode:l=c,courtesyGapMs:s=1100}={}){const u=e=>new Promise(n=>setTimeout(n,e));let d=a.slice();if(a.some(e=>e?.geocode?.pending)){const e=[];let n=!1;for(const t of a){if(!t?.geocode?.pending){e.push(t);continue}n&&s>0&&await u(s),n=!0;try{const o=await l(g(t));e.push(o?{...t,lat:o.lat,lng:o.lng,geocode:{...t.geocode,pending:!1,ok:!0,status:`Found \u2014 ${o.label.split(",").slice(0,3).join(",").trim()}`,lat:o.lat,lng:o.lng}}:{...t,geocode:{...t.geocode,pending:!1,ok:!1,status:"No match \u2014 check the address and try again."}})}catch{e.push({...t,geocode:{...t.geocode,pending:!1,ok:!1,status:"Lookup busy \u2014 try again in a moment."}})}}d=e}return d.map(e=>{const n=e?.geocode;if(!n)return e;if(n.manual&&n.lat===void 0&&n.lng===void 0)return{...e,geocode:{...n,lat:e.lat,lng:e.lng}};const t=i(n.lat)&&n.lat!==e.lat?n.lat:e.lat,o=i(n.lng)&&n.lng!==e.lng?n.lng:e.lng;return t!==e.lat||o!==e.lng?{...e,lat:t,lng:o}:e})}export{r as needsGeocodeWork,m as resolveGeocodeRows};
1
+ import{geocodeAddress as c}from"../geocode.js";const i=a=>typeof a=="number"&&Number.isFinite(a);function r(a){return a.some(g=>{const l=g?.geocode;return l?l.pending||l.manual&&l.lat===void 0&&l.lng===void 0?!0:i(l.lat)&&l.lat!==g.lat||i(l.lng)&&l.lng!==g.lng:!1})}async function m(a,g,{geocode:l=c,courtesyGapMs:s=1100}={}){const u=e=>new Promise(n=>setTimeout(n,e));let d=a.slice();if(a.some(e=>e?.geocode?.pending)){const e=[];let n=!1;for(const t of a){if(!t?.geocode?.pending){e.push(t);continue}n&&s>0&&await u(s),n=!0;try{const o=await l(g(t));e.push(o?{...t,lat:o.lat,lng:o.lng,geocode:{...t.geocode,pending:!1,ok:!0,status:`Found — ${o.label.split(",").slice(0,3).join(",").trim()}`,lat:o.lat,lng:o.lng}}:{...t,geocode:{...t.geocode,pending:!1,ok:!1,status:"No match — check the address and try again."}})}catch{e.push({...t,geocode:{...t.geocode,pending:!1,ok:!1,status:"Lookup busy — try again in a moment."}})}}d=e}return d.map(e=>{const n=e?.geocode;if(!n)return e;if(n.manual&&n.lat===void 0&&n.lng===void 0)return{...e,geocode:{...n,lat:e.lat,lng:e.lng}};const t=i(n.lat)&&n.lat!==e.lat?n.lat:e.lat,o=i(n.lng)&&n.lng!==e.lng?n.lng:e.lng;return t!==e.lat||o!==e.lng?{...e,lat:t,lng:o}:e})}export{r as needsGeocodeWork,m as resolveGeocodeRows};
@@ -1,2 +1,2 @@
1
- import{bucketByGroup as h}from"../locations-grid.js";const s=t=>String(t?.group??""),d=new WeakMap;let x=0;function b(t){let e=d.get(t);return e||(e=`loc${++x}`,d.set(t,e)),e}const w=(t,e)=>e||t||"Ungrouped",y={photo:"",group:"",groupName:"",name:"City name",state:"State",country:"Country",timezone:"GMT \xB7 UTC +0",address:`Address line one
2
- Address line two`,phone:"+00 000 0000",hours:"Mon\u2013Fri 9:00\u201318:00",lat:0,lng:0,mapsUrl:""};function a(t){return h(t,s,e=>String(e?.groupName??"")).map(e=>({key:e.key,name:e.name,cards:e.items.map(n=>n.item)}))}const m=t=>t.flatMap(e=>e.cards),g=(t,e,n)=>Math.min(Math.max(t,e),n);function C(t,e,n){let r=0;for(let o=0;o<t.length;o++)if(s(t[o])===e){if(r===n)return o;r++}return-1}function G(t,e,n){const r=a(t);if(e<0||e>=r.length)return t;const[o]=r.splice(e,1),u=g(e<n?n-1:n,0,r.length);return r.splice(u,0,o),m(r)}function N(t,e,n,r,o){const u=a(t),c=u.find(f=>f.key===e),i=u.find(f=>f.key===r);if(!c||!i||n<0||n>=c.cards.length)return t;const[p]=c.cards.splice(n,1),l=e===r?p:{...p,group:i.key,groupName:i.name};l!==p&&d.set(l,b(p));const k=g(c===i&&n<o?o-1:o,0,i.cards.length);return i.cards.splice(k,0,l),m(u)}function S(t,e){const n=a(t),r=n.find(u=>u.key===e),o={...y,group:e,groupName:r?.name??""};return r?(r.cards.push(o),m(n)):[...t,o]}function O(t){const e=new Set(a(t).map(o=>o.key));let n=e.size+1,r=`Group ${n}`;for(;e.has(r);)r=`Group ${++n}`;return{cards:[...t,{...y,group:r,groupName:""}],key:r}}function _(t,e){return t.filter(n=>s(n)!==e)}function v(t,e,n){const r=C(t,e,n);return r<0?t:t.filter((o,u)=>u!==r)}function T(t,e,n){const r=t.flatMap((c,i)=>s(c)===e?[i]:[]),o=t.slice();r.forEach((c,i)=>{o[c]=n[i]});const u=n.slice(r.length);return[...o.filter(c=>c!==void 0),...u]}function U(t,e,n){return e.flatMap(r=>{const o=t.filter(u=>s(u)===r);return o.length?o:n(r)})}export{y as NEW_CITY,C as absCityIndex,S as addCity,O as addGroup,a as bucketsOf,b as cardKey,v as deleteCity,_ as deleteGroup,s as groupKeyOf,w as groupLabelOf,N as moveCity,G as moveGroup,T as writeGroupCities,U as writeGroupOrder};
1
+ import{bucketByGroup as h}from"../locations-grid.js";const s=t=>String(t?.group??""),d=new WeakMap;let x=0;function b(t){let e=d.get(t);return e||(e=`loc${++x}`,d.set(t,e)),e}const w=(t,e)=>e||t||"Ungrouped",y={photo:"",group:"",groupName:"",name:"City name",state:"State",country:"Country",timezone:"GMT · UTC +0",address:`Address line one
2
+ Address line two`,phone:"+00 000 0000",hours:"Mon–Fri 9:00–18:00",lat:0,lng:0,mapsUrl:""};function a(t){return h(t,s,e=>String(e?.groupName??"")).map(e=>({key:e.key,name:e.name,cards:e.items.map(n=>n.item)}))}const m=t=>t.flatMap(e=>e.cards),g=(t,e,n)=>Math.min(Math.max(t,e),n);function C(t,e,n){let r=0;for(let o=0;o<t.length;o++)if(s(t[o])===e){if(r===n)return o;r++}return-1}function G(t,e,n){const r=a(t);if(e<0||e>=r.length)return t;const[o]=r.splice(e,1),u=g(e<n?n-1:n,0,r.length);return r.splice(u,0,o),m(r)}function N(t,e,n,r,o){const u=a(t),c=u.find(f=>f.key===e),i=u.find(f=>f.key===r);if(!c||!i||n<0||n>=c.cards.length)return t;const[p]=c.cards.splice(n,1),l=e===r?p:{...p,group:i.key,groupName:i.name};l!==p&&d.set(l,b(p));const k=g(c===i&&n<o?o-1:o,0,i.cards.length);return i.cards.splice(k,0,l),m(u)}function S(t,e){const n=a(t),r=n.find(u=>u.key===e),o={...y,group:e,groupName:r?.name??""};return r?(r.cards.push(o),m(n)):[...t,o]}function O(t){const e=new Set(a(t).map(o=>o.key));let n=e.size+1,r=`Group ${n}`;for(;e.has(r);)r=`Group ${++n}`;return{cards:[...t,{...y,group:r,groupName:""}],key:r}}function _(t,e){return t.filter(n=>s(n)!==e)}function v(t,e,n){const r=C(t,e,n);return r<0?t:t.filter((o,u)=>u!==r)}function T(t,e,n){const r=t.flatMap((c,i)=>s(c)===e?[i]:[]),o=t.slice();r.forEach((c,i)=>{o[c]=n[i]});const u=n.slice(r.length);return[...o.filter(c=>c!==void 0),...u]}function U(t,e,n){return e.flatMap(r=>{const o=t.filter(u=>s(u)===r);return o.length?o:n(r)})}export{y as NEW_CITY,C as absCityIndex,S as addCity,O as addGroup,a as bucketsOf,b as cardKey,v as deleteCity,_ as deleteGroup,s as groupKeyOf,w as groupLabelOf,N as moveCity,G as moveGroup,T as writeGroupCities,U as writeGroupOrder};
@@ -1 +1 @@
1
- import{SNAPSHOT_OBJECTS as s,HYDRATED_LISTS as l}from"./snapshot-contracts.js";const b={heading:"Main heading",body:"Add supporting copy.",eyebrow:"Label",button:"Button label",alt:"Image description",caption:"Image caption",address:null,phone:null,email:null,legal:null,generic:"Label"},o=[{semantic:"alt",name:/(^|[a-z])alt$|^imageLabel$|Alt$/i,label:/\balt text\b/i},{semantic:"caption",name:/caption/i,label:/\bcaption\b/i},{semantic:"email",name:/email/i,label:/\be-?mail\b/i},{semantic:"phone",name:/phone|tel$|mobile/i,label:/\bphone\b|\btelephone\b/i},{semantic:"address",name:/address|street|postcode|suburb|^hours$|^timezone$/i,label:/\baddress\b|\bopening hours\b/i},{semantic:"legal",name:/consent|finePrint|legal|disclaimer|copyright|abn|acn/i,label:/\bconsent\b|\bfine print\b|\blegal\b|\bdisclaimer\b/i},{semantic:"button",name:/^cta|Cta|button|Button|submitText|linkText|readLabel|viewLabel|archiveText|subscribedText/i,label:/\bbutton\b|\bcta\b|\blink text\b/i},{semantic:"eyebrow",name:/eyebrow|kicker|badge|^tag$|^num$|Number$|^label$|Label$|^meta|Meta$|^value$/i,label:/\beyebrow\b|\bkicker\b|\bbadge\b|\blabel\b|\bnumber\b|\btag\b/i},{semantic:"heading",name:/heading|Heading|^title|Title$|titleTop|titleBottom|^name$|question/i,label:/\bheading\b|\btitle\b/i},{semantic:"body",name:/body|description|^desc$|^intro|^lead|^dek$|^deck$|blurb|^copy$|quote|tagline|answer|^note$|footnote|^text$/i,label:/\bbody\b|\bdescription\b|\bblurb\b|\bintro\b|\bparagraph\b/i}];function g(e,a="",r=!1){for(const t of o)if(t.name?.test(e))return t.semantic;for(const t of o)if(t.label?.test(a))return t.semantic;return r?"body":"generic"}const c=["anchor"],d=/^href$|Href$|^url$|Url$|^link$|^mailto$|^tel$/;function E(e){return c.includes(e)||d.test(e)}const T={Reviews:["designCode"],ReviewForm:["designCode"],Form:["formSlug"]},v={HeroSingle:[{path:"eyebrow",semantic:"eyebrow",shape:"hidable"},{path:"titleTop",semantic:"heading",shape:"hidable"}],Hero:[{path:"tickerItems",semantic:"eyebrow",shape:"textList"}],ContactLocations:[{path:"sectionNumber",semantic:"eyebrow"},{path:"heading",semantic:"heading"},{path:"tagline",semantic:"body"},{path:"galleryKicker",semantic:"eyebrow"},{path:"galleryHint",semantic:"body"},{path:"locations[].groupName",semantic:"eyebrow"},{path:"locations[].name",semantic:"heading",neutral:"City name"},{path:"locations[].address",semantic:"address"},{path:"locations[].phone",semantic:"phone"},{path:"locations[].email",semantic:"email"},{path:"locations[].state",semantic:"address"},{path:"locations[].country",semantic:"address"},{path:"locations[].region",semantic:"address"},{path:"locations[].hours",semantic:"address"},{path:"locations[].timezone",semantic:"address"}]},p={Gallery:{source:{verb:"snapshot",keep:[]}},ProductHero:{source:{verb:"snapshot",keep:[]}},SpecTable:{source:{verb:"snapshot",keep:[]},wheel:{verb:"reference"},finish:{verb:"reference"},size:{verb:"reference"}},CollectionCard:{collection:{verb:"snapshot",keep:[]}},FooterMenu:{menu:{verb:"snapshot",keep:[]}},MenuBlock:{menu:{verb:"snapshot",keep:[]}},BrandStrip:{picks:{verb:"reference"}},ProductGrid:{design_codes:{verb:"reference"}},ProductRef:{code:{verb:"reference"},finish:{verb:"reference"}},FitmentTable:{wheel:{verb:"reference"}},GalleryItem:{fromProduct:{verb:"reference"}},Footprint:{"offices[].group":{verb:"groupKey"},"offices[].lat":{verb:"pii"},"offices[].lng":{verb:"pii"},"offices[].geocode":{verb:"pii"}}};p.ContactLocations={"locations[].lat":{verb:"pii"},"locations[].lng":{verb:"pii"},"locations[].mapsUrl":{verb:"pii"},"locations[].geocode":{verb:"pii"}};const S=["previewProduct"],y=Object.fromEntries(Object.entries(s).map(([e,a])=>[e,Object.keys(a)])),_=Object.fromEntries(Object.entries(l).map(([e,a])=>[e,[...a]])),m=["email","phone","address"],u={email:"Email address",phone:"Phone number",address:"Address"},h=[{rule:"email address",re:/[\w.+-]+@[\w-]+\.[\w.]+/},{rule:"url or domain",re:/https?:\/\/|\bwww\.|\.(?:com|net|org|au|co|io)\b/i},{rule:"phone number",re:/(?:\+\d[\d\s-]{7,}|\b0\d[\d\s-]{7,})/},{rule:"street address",re:/\b\d+[a-z]?\s+[A-Z][a-z]+\s+(?:St|Street|Rd|Road|Ave|Avenue|Hwy|Highway|Dr|Drive)\b/}];function $(e,a){const r=(a??"").trim();if(r){const t=h.find(({re:i})=>i.test(r));if(!t)return{ok:!0,neutral:r,source:"declared"};const n=m.includes(e)?u[e]:void 0;return n?{ok:!0,neutral:n,source:"safe-substitute"}:{ok:!1,reason:`declared placeholder ${JSON.stringify(r)} looks like a ${t.rule}. A placeholder is copied verbatim into every platform template \u2014 make it generic.`}}return{ok:!0,neutral:b[e],source:"fallback"}}export{v as CUSTOM_FIELD_TEXT,p as CUSTOM_FIELD_VALUES,_ as HYDRATED_PROPS_FROM_MEDIA_CONTRACT,d as LINK_TEXT_PROP_RE,h as PLACEHOLDER_FORBIDDEN,T as REFERENCE_TEXT_FIELDS,S as ROOT_REFERENCE_PROPS,u as SAFE_SEMANTIC_PLACEHOLDER,m as SEMANTICS_WITH_UNSAFE_PLACEHOLDERS,b as SEMANTIC_FALLBACKS,y as SNAPSHOT_PROPS_FROM_MEDIA_CONTRACT,c as STRUCTURAL_TEXT_PROPS,g as classifyTextSemantic,E as isStructuralTextProp,$ as resolveNeutralValue};
1
+ import{SNAPSHOT_OBJECTS as s,HYDRATED_LISTS as l}from"./snapshot-contracts.js";const b={heading:"Main heading",body:"Add supporting copy.",eyebrow:"Label",button:"Button label",alt:"Image description",caption:"Image caption",address:null,phone:null,email:null,legal:null,generic:"Label"},o=[{semantic:"alt",name:/(^|[a-z])alt$|^imageLabel$|Alt$/i,label:/\balt text\b/i},{semantic:"caption",name:/caption/i,label:/\bcaption\b/i},{semantic:"email",name:/email/i,label:/\be-?mail\b/i},{semantic:"phone",name:/phone|tel$|mobile/i,label:/\bphone\b|\btelephone\b/i},{semantic:"address",name:/address|street|postcode|suburb|^hours$|^timezone$/i,label:/\baddress\b|\bopening hours\b/i},{semantic:"legal",name:/consent|finePrint|legal|disclaimer|copyright|abn|acn/i,label:/\bconsent\b|\bfine print\b|\blegal\b|\bdisclaimer\b/i},{semantic:"button",name:/^cta|Cta|button|Button|submitText|linkText|readLabel|viewLabel|archiveText|subscribedText/i,label:/\bbutton\b|\bcta\b|\blink text\b/i},{semantic:"eyebrow",name:/eyebrow|kicker|badge|^tag$|^num$|Number$|^label$|Label$|^meta|Meta$|^value$/i,label:/\beyebrow\b|\bkicker\b|\bbadge\b|\blabel\b|\bnumber\b|\btag\b/i},{semantic:"heading",name:/heading|Heading|^title|Title$|titleTop|titleBottom|^name$|question/i,label:/\bheading\b|\btitle\b/i},{semantic:"body",name:/body|description|^desc$|^intro|^lead|^dek$|^deck$|blurb|^copy$|quote|tagline|answer|^note$|footnote|^text$/i,label:/\bbody\b|\bdescription\b|\bblurb\b|\bintro\b|\bparagraph\b/i}];function g(e,a="",r=!1){for(const t of o)if(t.name?.test(e))return t.semantic;for(const t of o)if(t.label?.test(a))return t.semantic;return r?"body":"generic"}const c=["anchor"],d=/^href$|Href$|^url$|Url$|^link$|^mailto$|^tel$/;function E(e){return c.includes(e)||d.test(e)}const T={Reviews:["designCode"],ReviewForm:["designCode"],Form:["formSlug"]},v={HeroSingle:[{path:"eyebrow",semantic:"eyebrow",shape:"hidable"},{path:"titleTop",semantic:"heading",shape:"hidable"}],Hero:[{path:"tickerItems",semantic:"eyebrow",shape:"textList"}],ContactLocations:[{path:"sectionNumber",semantic:"eyebrow"},{path:"heading",semantic:"heading"},{path:"tagline",semantic:"body"},{path:"galleryKicker",semantic:"eyebrow"},{path:"galleryHint",semantic:"body"},{path:"locations[].groupName",semantic:"eyebrow"},{path:"locations[].name",semantic:"heading",neutral:"City name"},{path:"locations[].address",semantic:"address"},{path:"locations[].phone",semantic:"phone"},{path:"locations[].email",semantic:"email"},{path:"locations[].state",semantic:"address"},{path:"locations[].country",semantic:"address"},{path:"locations[].region",semantic:"address"},{path:"locations[].hours",semantic:"address"},{path:"locations[].timezone",semantic:"address"}]},p={Gallery:{source:{verb:"snapshot",keep:[]}},ProductHero:{source:{verb:"snapshot",keep:[]}},SpecTable:{source:{verb:"snapshot",keep:[]},wheel:{verb:"reference"},finish:{verb:"reference"},size:{verb:"reference"}},CollectionCard:{collection:{verb:"snapshot",keep:[]}},FooterMenu:{menu:{verb:"snapshot",keep:[]}},MenuBlock:{menu:{verb:"snapshot",keep:[]}},BrandStrip:{picks:{verb:"reference"}},ProductGrid:{design_codes:{verb:"reference"}},ProductRef:{code:{verb:"reference"},finish:{verb:"reference"}},FitmentTable:{wheel:{verb:"reference"}},GalleryItem:{fromProduct:{verb:"reference"}},Footprint:{"offices[].group":{verb:"groupKey"},"offices[].lat":{verb:"pii"},"offices[].lng":{verb:"pii"},"offices[].geocode":{verb:"pii"}}};p.ContactLocations={"locations[].lat":{verb:"pii"},"locations[].lng":{verb:"pii"},"locations[].mapsUrl":{verb:"pii"},"locations[].geocode":{verb:"pii"}};const S=["previewProduct"],y=Object.fromEntries(Object.entries(s).map(([e,a])=>[e,Object.keys(a)])),_=Object.fromEntries(Object.entries(l).map(([e,a])=>[e,[...a]])),m=["email","phone","address"],u={email:"Email address",phone:"Phone number",address:"Address"},h=[{rule:"email address",re:/[\w.+-]+@[\w-]+\.[\w.]+/},{rule:"url or domain",re:/https?:\/\/|\bwww\.|\.(?:com|net|org|au|co|io)\b/i},{rule:"phone number",re:/(?:\+\d[\d\s-]{7,}|\b0\d[\d\s-]{7,})/},{rule:"street address",re:/\b\d+[a-z]?\s+[A-Z][a-z]+\s+(?:St|Street|Rd|Road|Ave|Avenue|Hwy|Highway|Dr|Drive)\b/}];function $(e,a){const r=(a??"").trim();if(r){const t=h.find(({re:i})=>i.test(r));if(!t)return{ok:!0,neutral:r,source:"declared"};const n=m.includes(e)?u[e]:void 0;return n?{ok:!0,neutral:n,source:"safe-substitute"}:{ok:!1,reason:`declared placeholder ${JSON.stringify(r)} looks like a ${t.rule}. A placeholder is copied verbatim into every platform template — make it generic.`}}return{ok:!0,neutral:b[e],source:"fallback"}}export{v as CUSTOM_FIELD_TEXT,p as CUSTOM_FIELD_VALUES,_ as HYDRATED_PROPS_FROM_MEDIA_CONTRACT,d as LINK_TEXT_PROP_RE,h as PLACEHOLDER_FORBIDDEN,T as REFERENCE_TEXT_FIELDS,S as ROOT_REFERENCE_PROPS,u as SAFE_SEMANTIC_PLACEHOLDER,m as SEMANTICS_WITH_UNSAFE_PLACEHOLDERS,b as SEMANTIC_FALLBACKS,y as SNAPSHOT_PROPS_FROM_MEDIA_CONTRACT,c as STRUCTURAL_TEXT_PROPS,g as classifyTextSemantic,E as isStructuralTextProp,$ as resolveNeutralValue};
@@ -1 +1 @@
1
- import{TEXT_FIELD_MARKER as s}from"./field-markers.js";import{partText as p,partValueWithText as x,typeInPlaceKind as y}from"./part-text.js";import{decodePartKey as P,encodePartKey as b}from"./part-key.js";function d(t){if(!(!t||typeof t!="object"))return t[s]}function l(t){if(!t||typeof t!="object")return null;const r=t.arrayFields;return r&&typeof r=="object"?r:null}function A(t,r){if(!Array.isArray(r))return[];const e=d(t),i=[];if(e?.control==="textList"){const n=e.itemKey??"text";return r.forEach((u,a)=>{const c=u?.[n];typeof c=="string"&&c&&i.push({row:{index:a,rowKey:n},text:c})}),i}const o=l(t);return o?(r.forEach((n,u)=>{for(const[a,c]of Object.entries(o)){if(!y(c))continue;const f=p(n?.[a]);f&&i.push({row:{index:u,rowKey:a},text:f})}}),i):[]}function w(t,r){if(Array.isArray(t))return t[r.index]?.[r.rowKey]}function K(t,r,e,i){const o=t[r];return!Array.isArray(o)||e.index<0||e.index>=o.length?null:{...t,[r]:o.map((n,u)=>u===e.index?{...n,[e.rowKey]:i}:n)}}function R(t,r,e,i,o){const n=w(t[r],e);return p(n)===o?null:K(t,r,e,x(i,n,o))}function T(t,r){return r?`${t} \xB7 row ${r.index+1}`:t}export{l as arrayFieldsOf,P as decodePartKey,b as encodePartKey,R as partPropsWithRowText,K as partPropsWithRowValue,T as partRowHeadLabel,A as partRowTexts,w as partRowValue,d as textMeta};
1
+ import{TEXT_FIELD_MARKER as s}from"./field-markers.js";import{partText as p,partValueWithText as x,typeInPlaceKind as y}from"./part-text.js";import{decodePartKey as P,encodePartKey as b}from"./part-key.js";function d(t){if(!(!t||typeof t!="object"))return t[s]}function l(t){if(!t||typeof t!="object")return null;const r=t.arrayFields;return r&&typeof r=="object"?r:null}function A(t,r){if(!Array.isArray(r))return[];const e=d(t),i=[];if(e?.control==="textList"){const n=e.itemKey??"text";return r.forEach((u,a)=>{const c=u?.[n];typeof c=="string"&&c&&i.push({row:{index:a,rowKey:n},text:c})}),i}const o=l(t);return o?(r.forEach((n,u)=>{for(const[a,c]of Object.entries(o)){if(!y(c))continue;const f=p(n?.[a]);f&&i.push({row:{index:u,rowKey:a},text:f})}}),i):[]}function w(t,r){if(Array.isArray(t))return t[r.index]?.[r.rowKey]}function K(t,r,e,i){const o=t[r];return!Array.isArray(o)||e.index<0||e.index>=o.length?null:{...t,[r]:o.map((n,u)=>u===e.index?{...n,[e.rowKey]:i}:n)}}function R(t,r,e,i,o){const n=w(t[r],e);return p(n)===o?null:K(t,r,e,x(i,n,o))}function T(t,r){return r?`${t} · row ${r.index+1}`:t}export{l as arrayFieldsOf,P as decodePartKey,b as encodePartKey,R as partPropsWithRowText,K as partPropsWithRowValue,T as partRowHeadLabel,A as partRowTexts,w as partRowValue,d as textMeta};
@@ -1 +1 @@
1
- const p=["general","commerce","service"],o=[{key:"openers",title:"Hero & intros",defaultExpanded:!0,templateable:!0,sections:[{type:"Hero",label:"Hero Slideshow",set:"general"},{type:"HeroSingle",label:"Hero",set:"general"},{type:"IntroBleed",label:"Scroll-Over Text",set:"general"}]},{key:"shop",title:"Shop & galleries",templateable:!0,sections:[{type:"Collections",label:"Collections",set:"commerce"},{type:"BrandStrip",label:"Brand Strip",set:"commerce"},{type:"AlloyWheelsListing",label:"List",set:"commerce"},{type:"CatalogFilterBar",label:"Catalog Filter Bar",set:"commerce"},{type:"ProductGrid",label:"Product Grid",set:"commerce"},{type:"FitmentFinder",label:"Fitment Form",set:"commerce"},{type:"Gallery",label:"Tabbed Gallery",set:"commerce"},{type:"GalleryB",label:"Customer Photo Grid",set:"commerce"},{type:"SpecStrip",label:"Spec Strip",set:"general"}]},{key:"product",title:"Product page",templateable:!0,sections:[{type:"ProductHero",label:"Product Hero",set:"commerce"},{type:"SpecTable",label:"Spec Table",set:"commerce"},{type:"ProcessSteps",label:"Image & Text Columns",set:"general"},{type:"DetailRows",label:"Feature Rows",set:"commerce"},{type:"FitmentTable",label:"Fitment Table",set:"commerce"},{type:"TyreSizes",label:"Tyre Sizes",set:"commerce"},{type:"TyreFitment",label:"Tyre Fitment Form",set:"commerce"},{type:"EuTyreLabel",label:"EU Tyre Label",set:"commerce"},{type:"RelatedProducts",label:"Related Products",set:"commerce"},{type:"BundleOffer",label:"Bundle Products",set:"commerce"}]},{key:"checkout",title:"Checkout & ordering",templateable:!0,sections:[{type:"OrderSummary",label:"Order Table",set:"general"},{type:"DeliveryOptions",label:"Delivery Options",set:"commerce"},{type:"PaymentBadges",label:"Payment Methods",set:"general"},{type:"CheckoutCTA",label:"Ready-to-Order Banner",set:"commerce"}]},{key:"about",title:"About & brand story",templateable:!0,sections:[{type:"Manifesto",label:"Brand Text",set:"general"},{type:"Timeline",label:"Story Timeline",set:"general"},{type:"TimelineTrack",label:"Milestone Timeline",set:"general"},{type:"Footprint",label:"Locations Map",set:"general"}]},{key:"content",title:"Text & content",templateable:!0,sections:[{type:"Prose",label:"Editorial Text",set:"general"},{type:"ParagraphsA",label:"Lead Text",set:"general"},{type:"Split",label:"Image & Text Split",set:"general"},{type:"Cards",label:"Cards",set:"general"},{type:"KeyFeatures",label:"Key Features",set:"general"}]},{key:"proof",title:"Stats & social proof",templateable:!0,sections:[{type:"Stats",label:"Stats",set:"general"},{type:"StatsParagraph",label:"Stats with Text",set:"general"},{type:"Chart",label:"Chart",set:"general"},{type:"Mockup",label:"Mockup Card",set:"general"},{type:"Testimonials",label:"Testimonials",set:"general"},{type:"Reviews",label:"Reviews",set:"service"},{type:"Logos",label:"Logos",set:"general"}]},{key:"promo",title:"Promotions & sign-ups",templateable:!0,sections:[{type:"Banner",label:"Banner",set:"general"},{type:"TitleWithButtons",label:"Call-to-Action Heading",set:"general"},{type:"Ticker",label:"Scrolling Bar",set:"general"},{type:"Subscribe",label:"Signup Form",set:"service"}]},{key:"blog",title:"Blog & journal",templateable:!0,sections:[{type:"Masthead",label:"Issue Header",set:"general"},{type:"FeaturedCover",label:"Feature Banner",set:"general"},{type:"LongRead",label:"Article Text",set:"general"},{type:"Series",label:"Recurring Columns",set:"general"},{type:"Events",label:"Latest Posts",set:"service"}]},{key:"navigation",title:"Navigation & wayfinding",templateable:!0,sections:[{type:"Breadcrumb",label:"Breadcrumb",set:"general"},{type:"CategoryBar",label:"Category Bar",set:"general"},{type:"DocTabs",label:"Document Tabs",set:"general"}]},{key:"legal",title:"Legal & documents",templateable:!0,sections:[{type:"DocMasthead",label:"Document Header",set:"general"},{type:"DocBody",label:"Document Text",set:"general"},{type:"DocMetaCards",label:"Document Detail Cards",set:"general"}]},{key:"contact",title:"Contact, FAQ & forms",templateable:!0,sections:[{type:"FAQ",label:"Q&A",set:"general"},{type:"ContactRouting",label:"Contact Form",set:"service"},{type:"ContactLocations",label:"Contact / Locations",set:"general"},{type:"SubmitBuild",label:"Photo Submission Form",set:"service"},{type:"ReviewForm",label:"Review Form",set:"service"},{type:"Form",label:"Form",set:"service"},{type:"AppointmentBooking",label:"Booking Form",set:"service"}]},{key:"structure",title:"Layout, structure & embeds",templateable:!1,sections:[{type:"SectionHeader",label:"Section Header",set:"general"},{type:"Flex",label:"Box",set:"general"},{type:"Spacer",label:"Spacer",set:"general"},{type:"Divider",label:"Divider",set:"general"},{type:"CustomHTML",label:"Custom HTML",set:"general"}]}];function h(){return Object.fromEntries(o.map(e=>[e.key,{title:e.title,components:e.sections.map(t=>t.type),...e.defaultExpanded?{defaultExpanded:!0}:{}}]))}const l={Grid:'Lewis, 2026-09-14: Box and Grid duplicate each other and it feels confusing. 2026-09-15 he chose the order: give Box uneven widths FIRST, then merge Grid into Box. Shown three ways to pick the widths (mockup "Uneven Box Widths"), he chose A \u2014 shape pictures on the Box\'s `Block widths` row, the same "a row is a shape, pick a picture of it" rule he set for the photo wall on 2026-09-07.\n\nWHAT REPLACES IT: a Box set to One row, with `Block widths` at Equal or a shape (Big left, Big right, Bigger left, Bigger right) and its own `Columns on phone`. Measured on PRODUCTION 2026-09-16 ~09:28 AEST (database `efficient-system`, all 10 tenant schemas, 128 storefront pages, both `layout` and `published_layout` \u2014 256 documents): 2 Grid nodes, and they are ONE Grid counted twice \u2014 the 9DIEM homepage on 9diem.com (`xtreme_mudder` schema), once as draft and once as published. It is top-level, holds three `ImageBlock`s at `columns` 3 \u2014 equal thirds \u2014 and NO block sets `Positions`. A Box draws that with `Block widths` at Equal.\n\n\u26A0\uFE0F THE COUNT MOVES; THE RULE MUST NOT DEPEND ON IT. This entry has already carried three different readings. A local-clone snapshot said no Grid used `Positions`. A PRODUCTION reading on 2026-09-15 ~13:15 AEST found 6 nodes on two sites, 4 of them spanned \u2014 hartesmetal.com.au homepage and about, 4 columns, spans 1 + 3. That one was TRUE when it was taken and stopped being true the same day: those two pages were rebuilt on Box (`Block widths: Bigger right`), and on 2026-09-16 they hold zero Grid nodes and are built from `Flex`. \u26D4 So the rule is NOT "there are still N spanned Grids". NEVER delete the Grid component or its `gridSpan` handling, whatever a census says \u2014 Grid still renders, still edits, still drags, and a merchant may rebuild a page either way on any day. Re-run the census on production before anyone PROPOSES deleting either, and read any number written here as the reading of its own day, not as the state of the world.\n\nWHAT A MERCHANT LOSES: a new Grid, and with it a fixed "N per row" that wraps by itself past three across, and the per-block `Positions` number. Box wraps with `Wrap`, but by content width, not by a fixed count.\n\nTHE TYPE KEY STAYS AND SO DOES THE COMPONENT. Every page holding a Grid still renders through it and can still be edited, dragged and added to; no document is rewritten and NO MIGRATION RUNS \u2014 the owner redoes live sections himself.\n\n\u26A0\uFE0F AND IT STAYS DROPPABLE WHERE IT LIVES. The drop rule in lib/puck/config.tsx read the nestable containers off the catalogue\'s `structure` row, so leaving the catalogue would have made every open slot \u2014 a Box included \u2014 refuse a Grid already stored inside one. `scripts/test-retired-grid-stays-editable.tsx` \xA73 pins it.',CompatibleTyres:'Lewis, 2026-09-09: "Do not bother counting. Delete Compatible Tyres and use the new one. The new one needs complete settings \u2014 it must have every setting our cards have." Shown three ways to finish it, he chose C: retire from the menu now, and take styling the card text as its own card.\n\nWHAT REPLACES IT: `RelatedProducts` with its `Show` dropdown set to "Tyres that fit this product". That block is the GENERIC relationship section from card 1128 \u2014 it knows nothing about wheels or tyres, it asks the registry for a relationship by name. The two have rendered through the SAME renderer (`DynamicRelatedProducts`) since that card; this one is the pre-registry one-off that was never removed.\n\n\u26A0\uFE0F THE SWAP WAS NOT SAFE UNTIL #1305, AND SAYING SO IS THE POINT. The first old-versus-new table put in front of the owner was WRONG. `DynamicRelatedProducts` returns EARLY on a tyre answer and draws this component, so the seven card settings, the head split and the Width control all stopped at that call \u2014 nine settings, measured NO EFFECT, on precisely the relationship that replaces this block. #1283 gave the new block the card settings, #1299 the head split and its own Width, #1305 carried all of it onto the tyre branch. Only then was the claim true.\n\nWHAT WAS MEASURED BEFORE RETIRING, four ways, because the premise decides the card:\n \u2022 settable text kinds (SECTION_TEXT_KINDS): both blocks ABSENT \u2014 the map holds only TimelineTrack and Footprint, both AUTHORED lists\n \u2022 read-only text regions: neither `components/CompatibleTyres` nor `components/TyreCard` stamps one\n \u2022 panel rows through resolveFields: this block 31, `RelatedProducts` 42. This block\'s only style-ish key is `_sStyle`, the bucket HEADER, not a control\n \u2022 head text (num/title/description) is authored on both, so `_partStyles` reaches it identically\nSo on the CARD runs the two are zero against zero \u2014 neither can style them \u2014 and everywhere else the new block is ahead. Styling those runs needs a section-level prop family and a paint path for DERIVED text, which no section has yet: a part is anchored by matching an AUTHORED prop\'s string against the DOM, and `lib/puck/source-bound-parts` keeps hydrated copy unreachable on purpose so a merchant cannot type into a copy of a product name and lose it at the next sync. That is its own card, and `lib/sections/text-regions` is the read-only half that already ships.\n\nTHE TYPE KEY STAYS AND SO DOES THE COMPONENT, the same contract Subscribe, Split and SubscribeBlogs have. Every page holding a `CompatibleTyres` still renders through it, no published document is rewritten, and NO MIGRATION RUNS \u2014 the owner\'s standing ruling of 2026-09-05: he redoes live sections himself and deletes the old ones when he does. He also said explicitly not to count them first.\n\n\u26A0\uFE0F AND THIS COMPONENT IS LOAD-BEARING FOR ITS OWN REPLACEMENT: `DynamicRelatedProducts` draws it for EVERY tyre answer. Deleting it would blank the block that replaces it, not just the pages still holding this one.',SubscribeBlogs:"card 66396 \u2014 folded into `Subscribe`, which now chooses its rail's content with a `Type` control (the newsletter's own issues, or live blog posts). The two shared 21 of their fields and rendered THE SAME BYTES from equivalent rows, so offering both asked a merchant to choose between two names for one section. The type key stays because it is stored on published pages, and the component stays because those pages still render through it."},m=["Grid"],y=Array.from(new Set([...o.flatMap(e=>e.sections.map(t=>t.type)),...Object.keys(l)])),u=Object.fromEntries(o.flatMap(e=>e.sections.map(t=>[t.type,t.label]))),i={Heading:"general",Text:"general",Eyebrow:"general",ImageBlock:"general",LogoBlock:"general",ButtonBlock:"general",PostCard:"general",HeroSlide:"general",HeroGroup:"general",Integrations:"general",Grid:"general",CollectionCard:"commerce",GalleryItem:"commerce",ProductRef:"commerce",CompatibleTyres:"commerce",Header:"commerce",SearchBlock:"commerce",CartBlock:"commerce",CountrySwitchBlock:"commerce",SubmitBox:"service",BookingBox:"service",SubscribeBlogs:"service",Footer:"service",FooterLogo:"service",FooterMenu:"service",FooterDivider:"service",FooterGroup:"service",FooterText:"service",FooterEmailSignup:"service",FooterSocial:"service",MenuBlock:"service",EmailFormBlock:"service",SocialLogosBlock:"service",LegalLineBlock:"service",SiteHeader:"service",SiteFooter:"service"},a={...i,...Object.fromEntries(o.flatMap(e=>e.sections.map(t=>[t.type,t.set])))};function b(e){return Object.prototype.hasOwnProperty.call(a,e)?a[e]:void 0}function r(e){return Object.keys(a).filter(t=>a[t]===e).sort()}const g=r("general"),T=r("commerce"),S=r("service"),c="Flex",s="Box",E=`${s}es`;function d(e){return e==="section"?"Blank section":s}function w(e,t,n){return e===c?d(t):n}export{y as ALL_SECTION_TYPES,p as BLOCK_SETS,s as BOX_LABEL,E as BOX_LABEL_PLURAL,T as COMMERCE_BLOCKS,g as GENERAL_BLOCKS,c as LEVEL_NAMED_TYPE,m as RETIRED_NESTABLE_SECTIONS,l as RETIRED_SECTIONS,o as SECTION_CATALOG,u as SECTION_LABELS,S as SERVICE_BLOCKS,i as UNLISTED_BLOCK_SETS,b as blockSetOf,r as blocksInSet,h as buildPuckCategories,w as labelAtLevel,d as labelForFlex};
1
+ const p=["general","commerce","service"],o=[{key:"openers",title:"Hero & intros",defaultExpanded:!0,templateable:!0,sections:[{type:"Hero",label:"Hero Slideshow",set:"general"},{type:"HeroSingle",label:"Hero",set:"general"},{type:"IntroBleed",label:"Scroll-Over Text",set:"general"}]},{key:"shop",title:"Shop & galleries",templateable:!0,sections:[{type:"Collections",label:"Collections",set:"commerce"},{type:"BrandStrip",label:"Brand Strip",set:"commerce"},{type:"AlloyWheelsListing",label:"List",set:"commerce"},{type:"CatalogFilterBar",label:"Catalog Filter Bar",set:"commerce"},{type:"ProductGrid",label:"Product Grid",set:"commerce"},{type:"FitmentFinder",label:"Fitment Form",set:"commerce"},{type:"Gallery",label:"Tabbed Gallery",set:"commerce"},{type:"GalleryB",label:"Customer Photo Grid",set:"commerce"},{type:"SpecStrip",label:"Spec Strip",set:"general"}]},{key:"product",title:"Product page",templateable:!0,sections:[{type:"ProductHero",label:"Product Hero",set:"commerce"},{type:"SpecTable",label:"Spec Table",set:"commerce"},{type:"ProcessSteps",label:"Image & Text Columns",set:"general"},{type:"DetailRows",label:"Feature Rows",set:"commerce"},{type:"FitmentTable",label:"Fitment Table",set:"commerce"},{type:"TyreSizes",label:"Tyre Sizes",set:"commerce"},{type:"TyreFitment",label:"Tyre Fitment Form",set:"commerce"},{type:"EuTyreLabel",label:"EU Tyre Label",set:"commerce"},{type:"RelatedProducts",label:"Related Products",set:"commerce"},{type:"BundleOffer",label:"Bundle Products",set:"commerce"}]},{key:"checkout",title:"Checkout & ordering",templateable:!0,sections:[{type:"OrderSummary",label:"Order Table",set:"general"},{type:"DeliveryOptions",label:"Delivery Options",set:"commerce"},{type:"PaymentBadges",label:"Payment Methods",set:"general"},{type:"CheckoutCTA",label:"Ready-to-Order Banner",set:"commerce"}]},{key:"about",title:"About & brand story",templateable:!0,sections:[{type:"Manifesto",label:"Brand Text",set:"general"},{type:"Timeline",label:"Story Timeline",set:"general"},{type:"TimelineTrack",label:"Milestone Timeline",set:"general"},{type:"Footprint",label:"Locations Map",set:"general"}]},{key:"content",title:"Text & content",templateable:!0,sections:[{type:"Prose",label:"Editorial Text",set:"general"},{type:"ParagraphsA",label:"Lead Text",set:"general"},{type:"Split",label:"Image & Text Split",set:"general"},{type:"Cards",label:"Cards",set:"general"},{type:"KeyFeatures",label:"Key Features",set:"general"}]},{key:"proof",title:"Stats & social proof",templateable:!0,sections:[{type:"Stats",label:"Stats",set:"general"},{type:"StatsParagraph",label:"Stats with Text",set:"general"},{type:"Chart",label:"Chart",set:"general"},{type:"Mockup",label:"Mockup Card",set:"general"},{type:"Testimonials",label:"Testimonials",set:"general"},{type:"Reviews",label:"Reviews",set:"service"},{type:"Logos",label:"Logos",set:"general"}]},{key:"promo",title:"Promotions & sign-ups",templateable:!0,sections:[{type:"Banner",label:"Banner",set:"general"},{type:"TitleWithButtons",label:"Call-to-Action Heading",set:"general"},{type:"Ticker",label:"Scrolling Bar",set:"general"},{type:"Subscribe",label:"Signup Form",set:"service"}]},{key:"blog",title:"Blog & journal",templateable:!0,sections:[{type:"Masthead",label:"Issue Header",set:"general"},{type:"FeaturedCover",label:"Feature Banner",set:"general"},{type:"LongRead",label:"Article Text",set:"general"},{type:"Series",label:"Recurring Columns",set:"general"},{type:"Events",label:"Latest Posts",set:"service"}]},{key:"navigation",title:"Navigation & wayfinding",templateable:!0,sections:[{type:"Breadcrumb",label:"Breadcrumb",set:"general"},{type:"CategoryBar",label:"Category Bar",set:"general"},{type:"DocTabs",label:"Document Tabs",set:"general"}]},{key:"legal",title:"Legal & documents",templateable:!0,sections:[{type:"DocMasthead",label:"Document Header",set:"general"},{type:"DocBody",label:"Document Text",set:"general"},{type:"DocMetaCards",label:"Document Detail Cards",set:"general"}]},{key:"contact",title:"Contact, FAQ & forms",templateable:!0,sections:[{type:"FAQ",label:"Q&A",set:"general"},{type:"ContactRouting",label:"Contact Form",set:"service"},{type:"ContactLocations",label:"Contact / Locations",set:"general"},{type:"SubmitBuild",label:"Photo Submission Form",set:"service"},{type:"ReviewForm",label:"Review Form",set:"service"},{type:"Form",label:"Form",set:"service"},{type:"AppointmentBooking",label:"Booking Form",set:"service"}]},{key:"structure",title:"Layout, structure & embeds",templateable:!1,sections:[{type:"SectionHeader",label:"Section Header",set:"general"},{type:"Flex",label:"Box",set:"general"},{type:"Spacer",label:"Spacer",set:"general"},{type:"Divider",label:"Divider",set:"general"},{type:"CustomHTML",label:"Custom HTML",set:"general"}]}];function h(){return Object.fromEntries(o.map(e=>[e.key,{title:e.title,components:e.sections.map(t=>t.type),...e.defaultExpanded?{defaultExpanded:!0}:{}}]))}const l={Grid:'Lewis, 2026-09-14: Box and Grid duplicate each other and it feels confusing. 2026-09-15 he chose the order: give Box uneven widths FIRST, then merge Grid into Box. Shown three ways to pick the widths (mockup "Uneven Box Widths"), he chose A — shape pictures on the Box\'s `Block widths` row, the same "a row is a shape, pick a picture of it" rule he set for the photo wall on 2026-09-07.\n\nWHAT REPLACES IT: a Box set to One row, with `Block widths` at Equal or a shape (Big left, Big right, Bigger left, Bigger right) and its own `Columns on phone`. Measured on PRODUCTION 2026-09-16 ~09:28 AEST (database `efficient-system`, all 10 tenant schemas, 128 storefront pages, both `layout` and `published_layout` — 256 documents): 2 Grid nodes, and they are ONE Grid counted twice — the 9DIEM homepage on 9diem.com (`xtreme_mudder` schema), once as draft and once as published. It is top-level, holds three `ImageBlock`s at `columns` 3 — equal thirds — and NO block sets `Positions`. A Box draws that with `Block widths` at Equal.\n\n⚠️ THE COUNT MOVES; THE RULE MUST NOT DEPEND ON IT. This entry has already carried three different readings. A local-clone snapshot said no Grid used `Positions`. A PRODUCTION reading on 2026-09-15 ~13:15 AEST found 6 nodes on two sites, 4 of them spanned — hartesmetal.com.au homepage and about, 4 columns, spans 1 + 3. That one was TRUE when it was taken and stopped being true the same day: those two pages were rebuilt on Box (`Block widths: Bigger right`), and on 2026-09-16 they hold zero Grid nodes and are built from `Flex`. ⛔ So the rule is NOT "there are still N spanned Grids". NEVER delete the Grid component or its `gridSpan` handling, whatever a census says — Grid still renders, still edits, still drags, and a merchant may rebuild a page either way on any day. Re-run the census on production before anyone PROPOSES deleting either, and read any number written here as the reading of its own day, not as the state of the world.\n\nWHAT A MERCHANT LOSES: a new Grid, and with it a fixed "N per row" that wraps by itself past three across, and the per-block `Positions` number. Box wraps with `Wrap`, but by content width, not by a fixed count.\n\nTHE TYPE KEY STAYS AND SO DOES THE COMPONENT. Every page holding a Grid still renders through it and can still be edited, dragged and added to; no document is rewritten and NO MIGRATION RUNS — the owner redoes live sections himself.\n\n⚠️ AND IT STAYS DROPPABLE WHERE IT LIVES. The drop rule in lib/puck/config.tsx read the nestable containers off the catalogue\'s `structure` row, so leaving the catalogue would have made every open slot — a Box included — refuse a Grid already stored inside one. `scripts/test-retired-grid-stays-editable.tsx` §3 pins it.',CompatibleTyres:'Lewis, 2026-09-09: "Do not bother counting. Delete Compatible Tyres and use the new one. The new one needs complete settings — it must have every setting our cards have." Shown three ways to finish it, he chose C: retire from the menu now, and take styling the card text as its own card.\n\nWHAT REPLACES IT: `RelatedProducts` with its `Show` dropdown set to "Tyres that fit this product". That block is the GENERIC relationship section from card 1128 — it knows nothing about wheels or tyres, it asks the registry for a relationship by name. The two have rendered through the SAME renderer (`DynamicRelatedProducts`) since that card; this one is the pre-registry one-off that was never removed.\n\n⚠️ THE SWAP WAS NOT SAFE UNTIL #1305, AND SAYING SO IS THE POINT. The first old-versus-new table put in front of the owner was WRONG. `DynamicRelatedProducts` returns EARLY on a tyre answer and draws this component, so the seven card settings, the head split and the Width control all stopped at that call — nine settings, measured NO EFFECT, on precisely the relationship that replaces this block. #1283 gave the new block the card settings, #1299 the head split and its own Width, #1305 carried all of it onto the tyre branch. Only then was the claim true.\n\nWHAT WAS MEASURED BEFORE RETIRING, four ways, because the premise decides the card:\n • settable text kinds (SECTION_TEXT_KINDS): both blocks ABSENT — the map holds only TimelineTrack and Footprint, both AUTHORED lists\n • read-only text regions: neither `components/CompatibleTyres` nor `components/TyreCard` stamps one\n • panel rows through resolveFields: this block 31, `RelatedProducts` 42. This block\'s only style-ish key is `_sStyle`, the bucket HEADER, not a control\n • head text (num/title/description) is authored on both, so `_partStyles` reaches it identically\nSo on the CARD runs the two are zero against zero — neither can style them — and everywhere else the new block is ahead. Styling those runs needs a section-level prop family and a paint path for DERIVED text, which no section has yet: a part is anchored by matching an AUTHORED prop\'s string against the DOM, and `lib/puck/source-bound-parts` keeps hydrated copy unreachable on purpose so a merchant cannot type into a copy of a product name and lose it at the next sync. That is its own card, and `lib/sections/text-regions` is the read-only half that already ships.\n\nTHE TYPE KEY STAYS AND SO DOES THE COMPONENT, the same contract Subscribe, Split and SubscribeBlogs have. Every page holding a `CompatibleTyres` still renders through it, no published document is rewritten, and NO MIGRATION RUNS — the owner\'s standing ruling of 2026-09-05: he redoes live sections himself and deletes the old ones when he does. He also said explicitly not to count them first.\n\n⚠️ AND THIS COMPONENT IS LOAD-BEARING FOR ITS OWN REPLACEMENT: `DynamicRelatedProducts` draws it for EVERY tyre answer. Deleting it would blank the block that replaces it, not just the pages still holding this one.',SubscribeBlogs:"card 66396 — folded into `Subscribe`, which now chooses its rail's content with a `Type` control (the newsletter's own issues, or live blog posts). The two shared 21 of their fields and rendered THE SAME BYTES from equivalent rows, so offering both asked a merchant to choose between two names for one section. The type key stays because it is stored on published pages, and the component stays because those pages still render through it."},m=["Grid"],y=Array.from(new Set([...o.flatMap(e=>e.sections.map(t=>t.type)),...Object.keys(l)])),u=Object.fromEntries(o.flatMap(e=>e.sections.map(t=>[t.type,t.label]))),i={Heading:"general",Text:"general",Eyebrow:"general",ImageBlock:"general",LogoBlock:"general",ButtonBlock:"general",PostCard:"general",HeroSlide:"general",HeroGroup:"general",Integrations:"general",Grid:"general",CollectionCard:"commerce",GalleryItem:"commerce",ProductRef:"commerce",CompatibleTyres:"commerce",Header:"commerce",SearchBlock:"commerce",CartBlock:"commerce",CountrySwitchBlock:"commerce",SubmitBox:"service",BookingBox:"service",SubscribeBlogs:"service",Footer:"service",FooterLogo:"service",FooterMenu:"service",FooterDivider:"service",FooterGroup:"service",FooterText:"service",FooterEmailSignup:"service",FooterSocial:"service",MenuBlock:"service",EmailFormBlock:"service",SocialLogosBlock:"service",LegalLineBlock:"service",SiteHeader:"service",SiteFooter:"service"},a={...i,...Object.fromEntries(o.flatMap(e=>e.sections.map(t=>[t.type,t.set])))};function b(e){return Object.prototype.hasOwnProperty.call(a,e)?a[e]:void 0}function r(e){return Object.keys(a).filter(t=>a[t]===e).sort()}const g=r("general"),T=r("commerce"),S=r("service"),c="Flex",s="Box",E=`${s}es`;function d(e){return e==="section"?"Blank section":s}function w(e,t,n){return e===c?d(t):n}export{y as ALL_SECTION_TYPES,p as BLOCK_SETS,s as BOX_LABEL,E as BOX_LABEL_PLURAL,T as COMMERCE_BLOCKS,g as GENERAL_BLOCKS,c as LEVEL_NAMED_TYPE,m as RETIRED_NESTABLE_SECTIONS,l as RETIRED_SECTIONS,o as SECTION_CATALOG,u as SECTION_LABELS,S as SERVICE_BLOCKS,i as UNLISTED_BLOCK_SETS,b as blockSetOf,r as blocksInSet,h as buildPuckCategories,w as labelAtLevel,d as labelForFlex};
@@ -1 +1 @@
1
- const e={Hero:"Full-screen opener with rotating slides.",HeroSingle:"Full-screen image or video with a headline.",IntroBleed:"Full-bleed intro image the next section scrolls over.",Collections:"Product collections in a tidy grid.",AlloyWheelsListing:"A full product-listing page: filter rail, sortable card grid and pagination, from the live catalog. Choose which product types it shows.",CatalogFilterBar:"Filters the collection grids below it \u2014 a sticky row of Pills (the default), or Columns, one per filter you switch on.",ProductGrid:"Hand-picked products in a tidy grid.",FitmentFinder:"A vehicle picker \u2014 make, model, year and trim resolve the OEM stock fitment live.",Gallery:"Two image galleries with tabs and filters.",GalleryB:"Customer photos in a card grid with a click-to-open lightbox.",SpecStrip:"A thin one-line spec strip \u2014 small-caps items spread across the width, as a dark band or a bare hairline row.",ProductHero:"A product hero \u2014 image beside price, bullets and a buy button.",SpecTable:"A spec sheet \u2014 labeled rows of product specifications.",ProcessSteps:"A heading over columns of image, title, blurb and specs.",DetailRows:"Repeating full-width rows \u2014 eyebrow, title and copy beside a numbered close-up image, alternating sides each row.",FitmentTable:"A fitment table \u2014 per-size bolt patterns, offsets and load ratings, filterable by diameter.",CompatibleTyres:"The current product's compatible tyres \u2014 image, size, key specs and price per tyre, with an inquiry link. Display only.",RelatedProducts:"The current product's related items in a tidy row \u2014 the product page's built-in 'Pair with' cross-sell.",OrderSummary:"A display order-summary panel \u2014 items, totals and a note.",DeliveryOptions:"Delivery tiers as cards \u2014 name, window and price, one highlighted.",PaymentBadges:"A payment-methods trust strip with reassurance lines.",CheckoutCTA:"A centered 'ready to order?' headline with the checkout button.",Manifesto:"A bold brand statement beside a full-height image, with a stat strip.",ParagraphsA:"An editorial text panel with a stat strip and founders' note.",StatsParagraph:"An editorial heading and paragraph over a row of theme-color-accented stat cards.",Chart:"A titled chart \u2014 bars, line or donut \u2014 drawn from your own labeled data points.",Mockup:"A headline beside a stylized product-UI mockup card \u2014 spec sheet, fitment, portal, kanban and more.",Prose:"A plain editorial text section \u2014 eyebrow, heading and body copy.",Timeline:"An editorial aside beside a rail-marked timeline of dated entries.",TimelineTrack:"A horizontal year track of milestones \u2014 company history or roadmap.",Footprint:"A world map of your locations with a legend \u2014 designed here, sold worldwide.",TitleWithButtons:"A big headline beside a stack of call-to-action buttons.",Split:"Side-by-side image and text in two columns.",Cards:"A heading over a grid of free-text title-and-blurb cards.",FAQ:"A frequently-asked-questions accordion with a header.",Testimonials:"Customer quotes in a grid \u2014 the quote, a name and a role.",Masthead:"A journal issue header with a lead statement and contents.",Breadcrumb:"A slim trail showing where the reader is (Home / Journal\u2026).",CategoryBar:"A sticky row of category tabs with counts and links.",FeaturedCover:"A featured article \u2014 big cover image beside a headline and dek.",LongRead:"A long-form article spread with a pull-quote and cover image.",Series:"A section heading over a grid of column cards.",Subscribe:"A newsletter signup beside a list of recent issues.",SubscribeBlogs:"A newsletter signup beside your latest blog posts, pulled live.",Events:"Latest journal posts in a three-card row.",DocMasthead:"A document header \u2014 headline, metadata and standfirst.",DocTabs:"A sticky tab bar to switch between documents.",DocBody:"Long-form document body with an 'On this page' contents list.",DocMetaCards:"A three-up meta strip for the foot of a document.",Banner:"Wide promo strip with background image.",Stats:"Highlight key numbers \u2014 years, designs.",KeyFeatures:"A theme-color chip and heading over a grid of short feature labels.",Logos:"A row of partner or brand logos.",Ticker:"A scrolling marquee of short phrases, full width.",Reviews:"Approved customer reviews in a star-rated card grid, pulled live.",SubmitBuild:"A 'Show us yours.' headline with a photo-submission form.",ReviewForm:"A write-a-review form \u2014 submissions wait for your approval before they show.",ContactRouting:"Pick who to reach, then a form that routes the message.",ContactLocations:"Your locations as a city gallery plus address and map cards.",SectionHeader:"A numbered section title with optional link.",Grid:"Multi-column container for blocks.",Flex:"An empty section. Add whatever you want inside.",Spacer:"Vertical breathing room between sections.",Divider:"A thin horizontal rule between sections.",CustomHTML:"Drop in your own sanitized HTML.",Heading:"A single heading line.",Text:"A paragraph of body copy.",ImageBlock:"A standalone image, at the width you set.",ButtonBlock:"A call-to-action button."};export{e as SECTION_DESCRIPTIONS};
1
+ const e={Hero:"Full-screen opener with rotating slides.",HeroSingle:"Full-screen image or video with a headline.",IntroBleed:"Full-bleed intro image the next section scrolls over.",Collections:"Product collections in a tidy grid.",AlloyWheelsListing:"A full product-listing page: filter rail, sortable card grid and pagination, from the live catalog. Choose which product types it shows.",CatalogFilterBar:"Filters the collection grids below it — a sticky row of Pills (the default), or Columns, one per filter you switch on.",ProductGrid:"Hand-picked products in a tidy grid.",FitmentFinder:"A vehicle picker — make, model, year and trim resolve the OEM stock fitment live.",Gallery:"Two image galleries with tabs and filters.",GalleryB:"Customer photos in a card grid with a click-to-open lightbox.",SpecStrip:"A thin one-line spec strip — small-caps items spread across the width, as a dark band or a bare hairline row.",ProductHero:"A product hero — image beside price, bullets and a buy button.",SpecTable:"A spec sheet — labeled rows of product specifications.",ProcessSteps:"A heading over columns of image, title, blurb and specs.",DetailRows:"Repeating full-width rows — eyebrow, title and copy beside a numbered close-up image, alternating sides each row.",FitmentTable:"A fitment table — per-size bolt patterns, offsets and load ratings, filterable by diameter.",CompatibleTyres:"The current product's compatible tyres — image, size, key specs and price per tyre, with an inquiry link. Display only.",RelatedProducts:"The current product's related items in a tidy row — the product page's built-in 'Pair with' cross-sell.",OrderSummary:"A display order-summary panel — items, totals and a note.",DeliveryOptions:"Delivery tiers as cards — name, window and price, one highlighted.",PaymentBadges:"A payment-methods trust strip with reassurance lines.",CheckoutCTA:"A centered 'ready to order?' headline with the checkout button.",Manifesto:"A bold brand statement beside a full-height image, with a stat strip.",ParagraphsA:"An editorial text panel with a stat strip and founders' note.",StatsParagraph:"An editorial heading and paragraph over a row of theme-color-accented stat cards.",Chart:"A titled chart — bars, line or donut — drawn from your own labeled data points.",Mockup:"A headline beside a stylized product-UI mockup card — spec sheet, fitment, portal, kanban and more.",Prose:"A plain editorial text section — eyebrow, heading and body copy.",Timeline:"An editorial aside beside a rail-marked timeline of dated entries.",TimelineTrack:"A horizontal year track of milestones — company history or roadmap.",Footprint:"A world map of your locations with a legend — designed here, sold worldwide.",TitleWithButtons:"A big headline beside a stack of call-to-action buttons.",Split:"Side-by-side image and text in two columns.",Cards:"A heading over a grid of free-text title-and-blurb cards.",FAQ:"A frequently-asked-questions accordion with a header.",Testimonials:"Customer quotes in a grid — the quote, a name and a role.",Masthead:"A journal issue header with a lead statement and contents.",Breadcrumb:"A slim trail showing where the reader is (Home / Journal…).",CategoryBar:"A sticky row of category tabs with counts and links.",FeaturedCover:"A featured article — big cover image beside a headline and dek.",LongRead:"A long-form article spread with a pull-quote and cover image.",Series:"A section heading over a grid of column cards.",Subscribe:"A newsletter signup beside a list of recent issues.",SubscribeBlogs:"A newsletter signup beside your latest blog posts, pulled live.",Events:"Latest journal posts in a three-card row.",DocMasthead:"A document header — headline, metadata and standfirst.",DocTabs:"A sticky tab bar to switch between documents.",DocBody:"Long-form document body with an 'On this page' contents list.",DocMetaCards:"A three-up meta strip for the foot of a document.",Banner:"Wide promo strip with background image.",Stats:"Highlight key numbers — years, designs.",KeyFeatures:"A theme-color chip and heading over a grid of short feature labels.",Logos:"A row of partner or brand logos.",Ticker:"A scrolling marquee of short phrases, full width.",Reviews:"Approved customer reviews in a star-rated card grid, pulled live.",SubmitBuild:"A 'Show us yours.' headline with a photo-submission form.",ReviewForm:"A write-a-review form — submissions wait for your approval before they show.",ContactRouting:"Pick who to reach, then a form that routes the message.",ContactLocations:"Your locations as a city gallery plus address and map cards.",SectionHeader:"A numbered section title with optional link.",Grid:"Multi-column container for blocks.",Flex:"An empty section. Add whatever you want inside.",Spacer:"Vertical breathing room between sections.",Divider:"A thin horizontal rule between sections.",CustomHTML:"Drop in your own sanitized HTML.",Heading:"A single heading line.",Text:"A paragraph of body copy.",ImageBlock:"A standalone image, at the width you set.",ButtonBlock:"A call-to-action button."};export{e as SECTION_DESCRIPTIONS};
@@ -1 +1 @@
1
- import{parseOption as t}from"../parse-option.js";const n="line",o=[{label:"Line",value:"line"},{label:"Stripes \xB7 fine",value:"stripes-fine"},{label:"Stripes \xB7 medium",value:"stripes-medium"},{label:"Stripes \xB7 bold",value:"stripes-bold"}],p={"stripes-fine":{ink:4,period:12},"stripes-medium":{ink:8,period:18},"stripes-bold":{ink:14,period:28}},d=28,s="-45deg",l="color-mix(in oklab, var(--accent) 50%, var(--bg))";function a(e){return e!=="line"}function c(e){return t(o,n,e)}function E(e){if(!a(e))return null;const{ink:r,period:i}=p[e];return`repeating-linear-gradient(${s}, ${l} 0 ${r}px, transparent ${r}px ${i}px)`}export{n as DEFAULT_DIVIDER_STYLE,o as DIVIDER_STYLE_OPTIONS,s as STRIPE_ANGLE,d as STRIPE_BAND_HEIGHT_PX,l as STRIPE_INK,a as isStriped,c as parseDividerStyle,E as stripeBackground};
1
+ import{parseOption as t}from"../parse-option.js";const n="line",o=[{label:"Line",value:"line"},{label:"Stripes · fine",value:"stripes-fine"},{label:"Stripes · medium",value:"stripes-medium"},{label:"Stripes · bold",value:"stripes-bold"}],p={"stripes-fine":{ink:4,period:12},"stripes-medium":{ink:8,period:18},"stripes-bold":{ink:14,period:28}},d=28,s="-45deg",l="color-mix(in oklab, var(--accent) 50%, var(--bg))";function a(e){return e!=="line"}function c(e){return t(o,n,e)}function E(e){if(!a(e))return null;const{ink:r,period:i}=p[e];return`repeating-linear-gradient(${s}, ${l} 0 ${r}px, transparent ${r}px ${i}px)`}export{n as DEFAULT_DIVIDER_STYLE,o as DIVIDER_STYLE_OPTIONS,s as STRIPE_ANGLE,d as STRIPE_BAND_HEIGHT_PX,l as STRIPE_INK,a as isStriped,c as parseDividerStyle,E as stripeBackground};
@@ -1 +1 @@
1
- import{parseOption as o}from"../parse-option.js";const a="off",l="none",i=[{label:"None",value:"off"},{label:"1, 2, 3",value:"plain"},{label:"01, 02, 03",value:"padded"},{label:"N\xB001, N\xB002",value:"numero"}],c=[{label:"None",value:"none"},{label:"Hairline",value:"hairline"}];function u(e){return o(i,a,e)}function x(e){return o(c,l,e)}const t="sf-card-index",n="sf-card-index",N="pointer-events-none absolute left-[18px] top-[18px] z-10 border border-line bg-bg/70 px-2 py-1 font-mono text-[length:var(--size-label,10px)] uppercase tracking-[0.16em] text-fg-dim",d={plain:`counter(${n})`,padded:`counter(${n}, decimal-leading-zero)`,numero:`"N\xB0" counter(${n}, decimal-leading-zero)`};function f(e,r){return r==="off"?"":`.${e}{counter-reset:${n}}.${e} .${t}{counter-increment:${n}}.${e} .${t}::before{content:${d[r]}}`}function _(e){return e==="hairline"?"border border-line hover:border-accent":""}export{N as CARD_INDEX_BADGE_CLASS,t as CARD_INDEX_CLASS,i as CARD_INDEX_OPTIONS,c as CARD_OUTLINE_OPTIONS,a as DEFAULT_CARD_INDEX_FORMAT,l as DEFAULT_CARD_OUTLINE,f as cardIndexCss,_ as cardOutlineClass,u as parseCardIndexFormat,x as parseCardOutline};
1
+ import{parseOption as o}from"../parse-option.js";const a="off",l="none",i=[{label:"None",value:"off"},{label:"1, 2, 3",value:"plain"},{label:"01, 02, 03",value:"padded"},{label:"N°01, N°02",value:"numero"}],c=[{label:"None",value:"none"},{label:"Hairline",value:"hairline"}];function u(e){return o(i,a,e)}function x(e){return o(c,l,e)}const t="sf-card-index",n="sf-card-index",N="pointer-events-none absolute left-[18px] top-[18px] z-10 border border-line bg-bg/70 px-2 py-1 font-mono text-[length:var(--size-label,10px)] uppercase tracking-[0.16em] text-fg-dim",d={plain:`counter(${n})`,padded:`counter(${n}, decimal-leading-zero)`,numero:`"N°" counter(${n}, decimal-leading-zero)`};function f(e,r){return r==="off"?"":`.${e}{counter-reset:${n}}.${e} .${t}{counter-increment:${n}}.${e} .${t}::before{content:${d[r]}}`}function _(e){return e==="hairline"?"border border-line hover:border-accent":""}export{N as CARD_INDEX_BADGE_CLASS,t as CARD_INDEX_CLASS,i as CARD_INDEX_OPTIONS,c as CARD_OUTLINE_OPTIONS,a as DEFAULT_CARD_INDEX_FORMAT,l as DEFAULT_CARD_OUTLINE,f as cardIndexCss,_ as cardOutlineClass,u as parseCardIndexFormat,x as parseCardOutline};
@@ -1 +1 @@
1
- import{parseOption as t}from"../parse-option.js";const l="small",n="dot",s=[{label:"Small",value:"small"},{label:"Display",value:"display"}],o=[{label:"Dot \u25CF",value:"dot"},{label:"Cross \u2715",value:"cross"},{label:"Slash /",value:"slash"},{label:"Triangle \u25BC",value:"triangle"},{label:"None",value:"none"}],x={dot:"\u25CF",cross:"\u2715",slash:"/",triangle:"\u25BC",none:""},r={small:"font-mono text-[length:var(--size-label,14px)] uppercase tracking-[0.25em] max-[800px]:text-[length:var(--size-label,12px)]",display:"font-display font-medium text-[length:var(--size-headingMedium,44px)] uppercase leading-none tracking-[0.005em] max-[800px]:text-[length:var(--size-headingMedium,28px)]"},i={small:{dot:"",cross:"text-[0.77em]",slash:"text-[0.77em]",triangle:"",none:""},display:{dot:"text-[0.3em]",cross:"text-[0.64em]",slash:"text-[0.64em]",triangle:"text-[0.3em]",none:""}};function c(e){return t(s,l,e)}function m(e){return t(o,n,e)}function u(e){return r[e]}function T(e,a){return i[e][a]}export{n as DEFAULT_TICKER_SEPARATOR,l as DEFAULT_TICKER_SIZE,x as SEPARATOR_GLYPH,o as TICKER_SEPARATOR_OPTIONS,s as TICKER_SIZE_OPTIONS,m as parseTickerSeparator,c as parseTickerSize,T as separatorScaleClass,u as tickerTypeClass};
1
+ import{parseOption as t}from"../parse-option.js";const l="small",n="dot",s=[{label:"Small",value:"small"},{label:"Display",value:"display"}],o=[{label:"Dot ●",value:"dot"},{label:"Cross ✕",value:"cross"},{label:"Slash /",value:"slash"},{label:"Triangle ▼",value:"triangle"},{label:"None",value:"none"}],x={dot:"●",cross:"✕",slash:"/",triangle:"▼",none:""},r={small:"font-mono text-[length:var(--size-label,14px)] uppercase tracking-[0.25em] max-[800px]:text-[length:var(--size-label,12px)]",display:"font-display font-medium text-[length:var(--size-headingMedium,44px)] uppercase leading-none tracking-[0.005em] max-[800px]:text-[length:var(--size-headingMedium,28px)]"},i={small:{dot:"",cross:"text-[0.77em]",slash:"text-[0.77em]",triangle:"",none:""},display:{dot:"text-[0.3em]",cross:"text-[0.64em]",slash:"text-[0.64em]",triangle:"text-[0.3em]",none:""}};function c(e){return t(s,l,e)}function m(e){return t(o,n,e)}function u(e){return r[e]}function T(e,a){return i[e][a]}export{n as DEFAULT_TICKER_SEPARATOR,l as DEFAULT_TICKER_SIZE,x as SEPARATOR_GLYPH,o as TICKER_SEPARATOR_OPTIONS,s as TICKER_SIZE_OPTIONS,m as parseTickerSeparator,c as parseTickerSize,T as separatorScaleClass,u as tickerTypeClass};
@@ -1,4 +1,4 @@
1
1
  import{ruleChecks as x}from"./rule.js";import{rootsLongestFirst as w}from"./support/accent-rename-bytes.js";const f=[".ts",".tsx",".css",".mjs",".js"],$=/--[a-z0-9-]*gold[a-z0-9-]*/gi,b=new RegExp(String.raw`(?<![a-z0-9-])`+String.raw`(?:[a-z0-9@_.\-\[\]\/]+:)*`+`(?:${w().join("|")})`+String.raw`-gold(?:-2)?`+String.raw`(?:\/(?:\d{1,3}|\[[^\]\s]*\]))?`+String.raw`(?![a-z0-9-])`,"gi");function p(n){const s=[];return n.text.split(`
2
- `).forEach((o,h)=>{for(const[c,l]of[[$,"custom-property"],[b,"utility-class"]]){c.lastIndex=0;let i;for(;(i=c.exec(o))!==null;)s.push({file:n.path,line:h+1,text:i[0].trim(),kind:l})}}),s}const u=n=>p({path:"fixture.tsx",text:n});function k(n,s){const{check:o,report:h}=x("accent token name"),c=s.exempt??{};{const e=[["bare token"," --gold: var(--gold-base);"],["second stop"," --gold-2: var(--theme-accent-2);"],["dim variant"," --gold-dim: #9a8050;"],["theme bridge"," --color-gold: var(--gold);"],["var() reference"," outline: 2px solid var(--gold);"],["var() with a fallback",'className="text-[var(--gold,#c8a24c)]"'],["text utility",'<span className="text-gold">'],["bg utility",'<div className="bg-gold px-4">'],["directional border",'className="border-t-2 border-t-gold"'],["opacity modifier",'className="bg-gold/10"'],["variant prefix",'className="hover:text-gold"'],["second-stop utility",'className="bg-gold-2"'],["arbitrary opacity",'className="bg-gold/[0.08]"'],["variant + arbitrary opacity",'className="hover:bg-gold/[0.04]"'],["named group variant",'className="group-hover/kf:text-gold"'],["inside a regex literal","assert.match(html, /whitespace-nowrap text-gold/)"]],t=e.filter(([,r])=>u(r).length===0);o(`the scanner flags every shape the rename must remove (${e.length} shapes)`,t.length===0,`it walked past: ${t.map(([r,m])=>`${r} (${m})`).join("; ")}`);const a=[["the new token"," --accent: var(--accent-base);"],["the new utility",'<span className="text-accent">'],["PayPal's own API value",'style: { color: "gold", label: "paypal" }'],["a product finish name",'{ label: "Gloss Gold", value: "gloss-gold" }'],["the phrase renderer",'import { renderGold } from "./goldPhrases";'],["prose in a docblock"," * a gold-italic accent word, dark-on-gold"],["a longer word that starts the same",'className="text-golden-ratio"'],["a hyphen-joined word ending in gold","// see the dark-on-gold treatment"]],g=a.filter(([,r])=>u(r).length>0);o(`the scanner stays quiet on things that merely share the word (${a.length} shapes)`,g.length===0,`it flagged: ${g.map(([r,m])=>`${r} (${m})`).join("; ")}`)}const l=n.filter(e=>f.some(t=>e.path.endsWith(t)));o(`read the source (${l.length} ${f.join("/")} files, at least ${s.minFiles})`,l.length>=s.minFiles,"fewer means the walk lost a folder, and an absence over a missing folder is free");const i=l.filter(e=>!(e.path in c)).flatMap(e=>p(e)),d=new Map;for(const e of i)d.set(e.file,[...d.get(e.file)??[],e]);o(`no file names the colour gold \u2014 the colour is called "accent" (${l.length} files)`,i.length===0,`${i.length} reference(s) to the old colour name across ${d.size} file(s):
2
+ `).forEach((o,h)=>{for(const[c,l]of[[$,"custom-property"],[b,"utility-class"]]){c.lastIndex=0;let i;for(;(i=c.exec(o))!==null;)s.push({file:n.path,line:h+1,text:i[0].trim(),kind:l})}}),s}const u=n=>p({path:"fixture.tsx",text:n});function k(n,s){const{check:o,report:h}=x("accent token name"),c=s.exempt??{};{const e=[["bare token"," --gold: var(--gold-base);"],["second stop"," --gold-2: var(--theme-accent-2);"],["dim variant"," --gold-dim: #9a8050;"],["theme bridge"," --color-gold: var(--gold);"],["var() reference"," outline: 2px solid var(--gold);"],["var() with a fallback",'className="text-[var(--gold,#c8a24c)]"'],["text utility",'<span className="text-gold">'],["bg utility",'<div className="bg-gold px-4">'],["directional border",'className="border-t-2 border-t-gold"'],["opacity modifier",'className="bg-gold/10"'],["variant prefix",'className="hover:text-gold"'],["second-stop utility",'className="bg-gold-2"'],["arbitrary opacity",'className="bg-gold/[0.08]"'],["variant + arbitrary opacity",'className="hover:bg-gold/[0.04]"'],["named group variant",'className="group-hover/kf:text-gold"'],["inside a regex literal","assert.match(html, /whitespace-nowrap text-gold/)"]],t=e.filter(([,r])=>u(r).length===0);o(`the scanner flags every shape the rename must remove (${e.length} shapes)`,t.length===0,`it walked past: ${t.map(([r,m])=>`${r} (${m})`).join("; ")}`);const a=[["the new token"," --accent: var(--accent-base);"],["the new utility",'<span className="text-accent">'],["PayPal's own API value",'style: { color: "gold", label: "paypal" }'],["a product finish name",'{ label: "Gloss Gold", value: "gloss-gold" }'],["the phrase renderer",'import { renderGold } from "./goldPhrases";'],["prose in a docblock"," * a gold-italic accent word, dark-on-gold"],["a longer word that starts the same",'className="text-golden-ratio"'],["a hyphen-joined word ending in gold","// see the dark-on-gold treatment"]],g=a.filter(([,r])=>u(r).length>0);o(`the scanner stays quiet on things that merely share the word (${a.length} shapes)`,g.length===0,`it flagged: ${g.map(([r,m])=>`${r} (${m})`).join("; ")}`)}const l=n.filter(e=>f.some(t=>e.path.endsWith(t)));o(`read the source (${l.length} ${f.join("/")} files, at least ${s.minFiles})`,l.length>=s.minFiles,"fewer means the walk lost a folder, and an absence over a missing folder is free");const i=l.filter(e=>!(e.path in c)).flatMap(e=>p(e)),d=new Map;for(const e of i)d.set(e.file,[...d.get(e.file)??[],e]);o(`no file names the colour gold — the colour is called "accent" (${l.length} files)`,i.length===0,`${i.length} reference(s) to the old colour name across ${d.size} file(s):
3
3
  `+[...d.entries()].sort((e,t)=>t[1].length-e[1].length).map(([e,t])=>` ${e} (${t.length}): ${[...new Set(t.map(a=>a.text))].slice(0,6).join(", ")}`).join(`
4
- `));for(const[e,t]of Object.entries(c)){const a=l.find(g=>g.path===e);o(`the exemption is still real: ${e} (${t})`,!!a&&p(a).length>0,"the file is gone or no longer names the old colour \u2014 remove the stale entry")}for(const e of s.keptNames??[]){const t=n.find(a=>a.path===e.path);o(`a deliberate exclusion is still here: ${e.path} keeps ${e.pattern}`,!!t&&t.text.search(e.pattern)!==-1,t?e.why:`${e.path} was not among the files read \u2014 ${e.why}`)}return h()}export{f as ACCENT_TOKEN_EXTENSIONS,k as accentTokenName,p as goldTokenUses};
4
+ `));for(const[e,t]of Object.entries(c)){const a=l.find(g=>g.path===e);o(`the exemption is still real: ${e} (${t})`,!!a&&p(a).length>0,"the file is gone or no longer names the old colour — remove the stale entry")}for(const e of s.keptNames??[]){const t=n.find(a=>a.path===e.path);o(`a deliberate exclusion is still here: ${e.path} keeps ${e.pattern}`,!!t&&t.text.search(e.pattern)!==-1,t?e.why:`${e.path} was not among the files read — ${e.why}`)}return h()}export{f as ACCENT_TOKEN_EXTENSIONS,k as accentTokenName,p as goldTokenUses};
@@ -1,2 +1,2 @@
1
1
  import{ruleChecks as m}from"./rule.js";const l="--sf-button-radius",f="sf-button",p=i=>i.replace(/\/\*[\s\S]*?\*\//g,e=>" ".repeat(e.length)),b=i=>/^import\s+Btn\s+from\s+["'](?:@\/components\/Btn|\.{1,2}\/(?:[\w-]+\/)*Btn)["'];?$/m.test(i);function g(i){const e=[],n=/<Btn\b[^>]*className=(?:"([^"]*)"|\{`([^`]*)`\})/g;let u;for(;(u=n.exec(i.text))!==null;){const h=u[1]??u[2]??"";/\brounded-/.test(h)&&e.push(`${i.path}: ${h}`)}return e}function y(i,e){const{check:n,report:u}=m("button corners"),h=t=>i.find(s=>s.path===t);{const t={path:"fixture.tsx",text:['import Btn from "@/components/Btn";','<Btn href="/x" className="mt-4 rounded-lg">Go</Btn>',"<Btn className={`mt-2`}>Go</Btn>"].join(`
2
- `)};n("\xA76/\xA77 the readers find a Btn import and a rounded-* className handed to it",b(t.text)&&g(t).length===1)}if(e.stylesheet!==void 0){const t=h(e.stylesheet);if(n(`\xA71 read the stylesheet ${e.stylesheet}`,!!t)){const s=/\.sf-button\s*\{([^}]*)\}/.exec(p(t.text));n(`\xA71 ${e.stylesheet} carries a \`.sf-button\` rule`,s!==null,"without it the class is decoration and the setting reaches no button"),s&&(n(`\xA71 the .sf-button rule reads \`var(${l}, 0)\``,/border-radius:\s*var\(--sf-button-radius,\s*0\)/.test(s[1]),`the property for the setting, and the \`0\` for every site that never chose one \u2014 got: ${s[1].trim()}`),n("\xA71 the .sf-button rule does not clip",!/overflow/.test(s[1]),`the trailing arrow translates 3px right on hover and would be shaved \u2014 got: ${s[1].trim()}`))}}if(e.button!==void 0){const t=h(e.button);n(`\xA72 read the shared button ${e.button}`,!!t)&&n(`\xA72 ${e.button} writes no \`rounded-*\` utility`,!/\brounded-(?!full\b)[a-z0-9[]/.test(t.text.replace(/\/\*[\s\S]*?\*\/|\/\/.*$/gm,"")),"every one carries a built-in value, so an untouched site would jump from square to a radius the day it shipped");const s=e.rendered??[];n(`\xA72 the button was rendered (${s.length} shapes, at least 4)`,s.length>=4,"both variants as a link and as a button");for(const[r,a]of s)n(`\xA72 ${r}: the rendered button carries \`${f}\``,new RegExp(`class="${f}\\b`).test(a),`it is the element's only ear for Corners \u2014 got: ${a.slice(0,140)}`)}const o=e.cornerRadiusTokens;if(o){n("\xA73 a site that never chose a corner publishes NO button radius",!(l in o(null)),`that absence is what leaves it square, byte for byte as before \u2014 got: ${JSON.stringify(o(null))}`),n("\xA73 `0` is an ANSWER, not an absence: it publishes a flattened radius",o(0)[l]==="0",JSON.stringify(o(0)));for(const[d,c]of[[0,"0"],[9,"0.28125rem"],[16,"0.5rem"],[40,"1.25rem"]])n(`\xA73 ${d}px publishes ${l}: ${c}`,o(d)[l]===c,`a button is half the card, which is what inputs and panels beside it read \u2014 got: ${String(o(d)[l])}`);const[t,s]=e.radiusRange??[Number.NaN,Number.NaN];n(`\xA73 both ends of the track (${t}, ${s}) publish a button radius`,e.radiusRange!==void 0&&o(t)[l]!==void 0&&o(s)[l]!==void 0);const r=[null,0,9,40].map(d=>o(d)[l]??"0");n("\xA74 not-set and square both DRAW a square button",r[0]==="0"&&r[1]==="0",`they differ in what they publish, not in today's picture \u2014 got: ${JSON.stringify(r)}`),n("\xA74 9 and 40 draw two different NON-square buttons",r[2]!=="0"&&r[3]!=="0"&&r[2]!==r[3],`a setting that names buttons and moves none of them is this card's defect restored \u2014 got: ${JSON.stringify(r)}`);const a="--radius-full";for(const d of[0,9,16,40])n(`\xA75 ${d}px does not publish the fully-round token`,!(a in o(d)),"swatches, toggle knobs and status dots are a SHAPE, not a corner")}{const t=[];for(const s of e.callers){const r=h(s);r&&t.push(...g(r))}n(`\xA76 no caller hands \`Btn\` a \`rounded-*\` class (${e.callers.length} callers)`,t.length===0,`\`.sf-button\` is unlayered and would silently beat it; give the caller its own property instead \u2014 ${t.join(" | ")}`)}{const t=i.filter(a=>a.path.endsWith(".tsx")&&b(a.text)).map(a=>a.path).sort();n(`\xA77 the tree sweep found ${t.length} file(s) importing Btn (at least ${e.callerFloor})`,t.length>=e.callerFloor&&t.length>0,`too few to be real, so \xA77 is measuring nothing \u2014 ${JSON.stringify(t)}`);const s=t.filter(a=>!e.callers.includes(a));n("\xA77 the callers list names every file that imports `Btn`",s.length===0,`${JSON.stringify(s)} import(s) Btn and ${s.length===1?"is":"are"} not listed \u2014 a caller missing from it is a caller \xA76 never looks at. Add the path \u2014 the list is kept by hand on purpose, so a new caller is a visible line in a diff.`);const r=e.callers.filter(a=>!t.includes(a));n("\xA77 the callers list names no file that stopped importing `Btn`",r.length===0,`${JSON.stringify(r)} ${r.length===1?"is":"are"} listed and no longer import Btn \u2014 a dead row is a sweep over nothing. Drop the path, or say in a comment why the file is still worth sweeping.`)}return u()}export{y as buttonCorners};
2
+ `)};n("§6/§7 the readers find a Btn import and a rounded-* className handed to it",b(t.text)&&g(t).length===1)}if(e.stylesheet!==void 0){const t=h(e.stylesheet);if(n(`§1 read the stylesheet ${e.stylesheet}`,!!t)){const s=/\.sf-button\s*\{([^}]*)\}/.exec(p(t.text));n(`§1 ${e.stylesheet} carries a \`.sf-button\` rule`,s!==null,"without it the class is decoration and the setting reaches no button"),s&&(n(`§1 the .sf-button rule reads \`var(${l}, 0)\``,/border-radius:\s*var\(--sf-button-radius,\s*0\)/.test(s[1]),`the property for the setting, and the \`0\` for every site that never chose one — got: ${s[1].trim()}`),n("§1 the .sf-button rule does not clip",!/overflow/.test(s[1]),`the trailing arrow translates 3px right on hover and would be shaved — got: ${s[1].trim()}`))}}if(e.button!==void 0){const t=h(e.button);n(`§2 read the shared button ${e.button}`,!!t)&&n(`§2 ${e.button} writes no \`rounded-*\` utility`,!/\brounded-(?!full\b)[a-z0-9[]/.test(t.text.replace(/\/\*[\s\S]*?\*\/|\/\/.*$/gm,"")),"every one carries a built-in value, so an untouched site would jump from square to a radius the day it shipped");const s=e.rendered??[];n(`§2 the button was rendered (${s.length} shapes, at least 4)`,s.length>=4,"both variants as a link and as a button");for(const[r,a]of s)n(`§2 ${r}: the rendered button carries \`${f}\``,new RegExp(`class="${f}\\b`).test(a),`it is the element's only ear for Corners — got: ${a.slice(0,140)}`)}const o=e.cornerRadiusTokens;if(o){n("§3 a site that never chose a corner publishes NO button radius",!(l in o(null)),`that absence is what leaves it square, byte for byte as before — got: ${JSON.stringify(o(null))}`),n("§3 `0` is an ANSWER, not an absence: it publishes a flattened radius",o(0)[l]==="0",JSON.stringify(o(0)));for(const[d,c]of[[0,"0"],[9,"0.28125rem"],[16,"0.5rem"],[40,"1.25rem"]])n(`§3 ${d}px publishes ${l}: ${c}`,o(d)[l]===c,`a button is half the card, which is what inputs and panels beside it read — got: ${String(o(d)[l])}`);const[t,s]=e.radiusRange??[Number.NaN,Number.NaN];n(`§3 both ends of the track (${t}, ${s}) publish a button radius`,e.radiusRange!==void 0&&o(t)[l]!==void 0&&o(s)[l]!==void 0);const r=[null,0,9,40].map(d=>o(d)[l]??"0");n("§4 not-set and square both DRAW a square button",r[0]==="0"&&r[1]==="0",`they differ in what they publish, not in today's picture — got: ${JSON.stringify(r)}`),n("§4 9 and 40 draw two different NON-square buttons",r[2]!=="0"&&r[3]!=="0"&&r[2]!==r[3],`a setting that names buttons and moves none of them is this card's defect restored — got: ${JSON.stringify(r)}`);const a="--radius-full";for(const d of[0,9,16,40])n(`§5 ${d}px does not publish the fully-round token`,!(a in o(d)),"swatches, toggle knobs and status dots are a SHAPE, not a corner")}{const t=[];for(const s of e.callers){const r=h(s);r&&t.push(...g(r))}n(`§6 no caller hands \`Btn\` a \`rounded-*\` class (${e.callers.length} callers)`,t.length===0,`\`.sf-button\` is unlayered and would silently beat it; give the caller its own property instead — ${t.join(" | ")}`)}{const t=i.filter(a=>a.path.endsWith(".tsx")&&b(a.text)).map(a=>a.path).sort();n(`§7 the tree sweep found ${t.length} file(s) importing Btn (at least ${e.callerFloor})`,t.length>=e.callerFloor&&t.length>0,`too few to be real, so §7 is measuring nothing — ${JSON.stringify(t)}`);const s=t.filter(a=>!e.callers.includes(a));n("§7 the callers list names every file that imports `Btn`",s.length===0,`${JSON.stringify(s)} import(s) Btn and ${s.length===1?"is":"are"} not listed — a caller missing from it is a caller §6 never looks at. Add the path — the list is kept by hand on purpose, so a new caller is a visible line in a diff.`);const r=e.callers.filter(a=>!t.includes(a));n("§7 the callers list names no file that stopped importing `Btn`",r.length===0,`${JSON.stringify(r)} ${r.length===1?"is":"are"} listed and no longer import Btn — a dead row is a sweep over nothing. Drop the path, or say in a comment why the file is still worth sweeping.`)}return u()}export{y as buttonCorners};
@@ -7,7 +7,7 @@ import{ruleChecks as z}from"./rule.js";const g="sf-note";function y(o){const i=[
7
7
  }`);t("the walker reads a rule's own declarations",s.length===1&&s[0].selector===".sf-note"&&s[0].line===1&&m(s[0].body,"font-size")==="13px"&&m(s[0].body,"padding")===null,JSON.stringify(s)),t("font-size is not confused with a longhand that ends in it",m(` font-size-adjust: 0.5;
8
8
  font-size: 12px;
9
9
  `,"font-size")==="12px"&&m(` font-size-adjust: 0.5;
10
- `,"font-size")===null),t("a grouping block reports its children, not itself",p(y("@keyframes sf-x { from { opacity: 0; } to { opacity: 1; } }").map(h=>h.selector),["from","to"])),t("braces in comments and strings do not shift nesting",p(y('/* .a { x } */ .b { content: "}"; } .c { color: red; }').map(h=>h.selector),[".b",".c"])),t("modifiers and descendants report the base note class",p([...u(".sf-note--error")],["sf-note"])&&p([...u(".sf-note p + p")],["sf-note"])&&p([...u(`.${e("pages")}, .${e("sm")}`)],[e("pages"),e("sm")])&&u(".sf-notebook").size===0);const f=[`/* Two homes were rejected: \`.${e("pages")}\` belongs to the chrome. */`,`// \u2026and .${e("sm")} is the sitemap's own.`,'const url = "https://x/y";','export const Note = () => <div className="sf-note">hi</div>;'].join(`
11
- `);t("a class a file only TALKS about is not a class it renders, and a `//` in a URL is not a comment",p([...u(b(f))],["sf-note"])&&/https:\/\/x\/y/.test(b(f)))}let r=new Set([g]);if(i.stylesheet){const e=o.find(s=>s.path===i.stylesheet);if(t(`read the stylesheet ${i.stylesheet}`,!!e)&&e){const s=y(e.text);r=new Set;for(const l of s)for(const d of u(l.selector))r.add(d);t(`the stylesheet parses (${s.length} rules, more than 400)`,s.length>400,"the walker looks broken"),t(`the scan is not vacuous: it finds a note class (${r.size})`,r.size>0,"the selector scan looks broken"),t(`the chrome declares exactly ONE note class, .${g}`,p([...r].sort(),[g]),`it declares ${[...r].sort().map(l=>`.${l}`).join(", ")}. One quiet sentence, one class: a note that needs a different INSET takes it from the block it sits in (\`.sf-pages-list > .sf-note\`), never from a new class`);const f=new Map;for(const l of r){const d=s.find(x=>x.selector===`.${l}`),w=d?m(d.body,"font-size"):null;t(`.${l} has a type rule of its own`,!!d,"it is a modifier without a base"),t(`.${l} states its own font-size`,w!==null,"it inherits one \u2014 that is a size nobody chose"),f.set(l,w)}const h=new Set(f.values());t("every note class states the same size",h.size<=1,`the chrome's notes are set at ${h.size} sizes: ${[...f].map(([l,d])=>`.${l} ${d}`).join(", ")}`);const $=s.find(l=>l.selector===`.${g}`);t(`the note still reads as a note: .${g} is var(--sf-muted)`,!!$&&m($.body,"color")==="var(--sf-muted)",$?`color: ${m($.body,"color")}`:"the chrome no longer declares it")}}const n=o.filter(e=>/\.(tsx?|jsx?|mjs)$/.test(e.path));t(`read the source (${n.length} JS/TS files, at least ${i.minFiles})`,n.length>=i.minFiles,"fewer means the walk broke, and the check below is vacuous");const a=[];for(const e of n)for(const s of u(b(e.text)))r.has(s)||a.push(`${e.path} ${s}`);return t(`no source file names a note class the chrome does not declare (${[...r].map(e=>`.${e}`).join(", ")})`,a.length===0,`a \`.sf-*\` class is scoped under \`.sf-editor\` in the PARENT document, so a missing rule is not a compile error \u2014 the text simply renders at whatever it inherits:
10
+ `,"font-size")===null),t("a grouping block reports its children, not itself",p(y("@keyframes sf-x { from { opacity: 0; } to { opacity: 1; } }").map(h=>h.selector),["from","to"])),t("braces in comments and strings do not shift nesting",p(y('/* .a { x } */ .b { content: "}"; } .c { color: red; }').map(h=>h.selector),[".b",".c"])),t("modifiers and descendants report the base note class",p([...u(".sf-note--error")],["sf-note"])&&p([...u(".sf-note p + p")],["sf-note"])&&p([...u(`.${e("pages")}, .${e("sm")}`)],[e("pages"),e("sm")])&&u(".sf-notebook").size===0);const f=[`/* Two homes were rejected: \`.${e("pages")}\` belongs to the chrome. */`,`// …and .${e("sm")} is the sitemap's own.`,'const url = "https://x/y";','export const Note = () => <div className="sf-note">hi</div>;'].join(`
11
+ `);t("a class a file only TALKS about is not a class it renders, and a `//` in a URL is not a comment",p([...u(b(f))],["sf-note"])&&/https:\/\/x\/y/.test(b(f)))}let r=new Set([g]);if(i.stylesheet){const e=o.find(s=>s.path===i.stylesheet);if(t(`read the stylesheet ${i.stylesheet}`,!!e)&&e){const s=y(e.text);r=new Set;for(const l of s)for(const d of u(l.selector))r.add(d);t(`the stylesheet parses (${s.length} rules, more than 400)`,s.length>400,"the walker looks broken"),t(`the scan is not vacuous: it finds a note class (${r.size})`,r.size>0,"the selector scan looks broken"),t(`the chrome declares exactly ONE note class, .${g}`,p([...r].sort(),[g]),`it declares ${[...r].sort().map(l=>`.${l}`).join(", ")}. One quiet sentence, one class: a note that needs a different INSET takes it from the block it sits in (\`.sf-pages-list > .sf-note\`), never from a new class`);const f=new Map;for(const l of r){const d=s.find(x=>x.selector===`.${l}`),w=d?m(d.body,"font-size"):null;t(`.${l} has a type rule of its own`,!!d,"it is a modifier without a base"),t(`.${l} states its own font-size`,w!==null,"it inherits one — that is a size nobody chose"),f.set(l,w)}const h=new Set(f.values());t("every note class states the same size",h.size<=1,`the chrome's notes are set at ${h.size} sizes: ${[...f].map(([l,d])=>`.${l} ${d}`).join(", ")}`);const $=s.find(l=>l.selector===`.${g}`);t(`the note still reads as a note: .${g} is var(--sf-muted)`,!!$&&m($.body,"color")==="var(--sf-muted)",$?`color: ${m($.body,"color")}`:"the chrome no longer declares it")}}const n=o.filter(e=>/\.(tsx?|jsx?|mjs)$/.test(e.path));t(`read the source (${n.length} JS/TS files, at least ${i.minFiles})`,n.length>=i.minFiles,"fewer means the walk broke, and the check below is vacuous");const a=[];for(const e of n)for(const s of u(b(e.text)))r.has(s)||a.push(`${e.path} ${s}`);return t(`no source file names a note class the chrome does not declare (${[...r].map(e=>`.${e}`).join(", ")})`,a.length===0,`a \`.sf-*\` class is scoped under \`.sf-editor\` in the PARENT document, so a missing rule is not a compile error — the text simply renders at whatever it inherits:
12
12
  ${a.join(`
13
13
  `)}`),c()}export{g as CHROME_NOTE_CLASS,m as declaration,S as editorChromeNote,y as leafRules,u as noteClasses,b as withoutComments};
@@ -1,4 +1,4 @@
1
1
  import{ruleChecks as c}from"./rule.js";const r="efficient-easel",i=`${r}/panel.css`,s=`${r}/menu.css`;function a(h){const n=h.filter(t=>t.path.endsWith(".tsx")&&t.text.includes(i));return{withPanel:n,missing:n.filter(t=>!t.text.includes(s)).map(t=>t.path)}}function p(h,n){const{check:t,report:l}=c("editor context menu");{const e=a([{path:"a.tsx",text:`import "${i}";
2
- import "${s}";`},{path:"b.tsx",text:`import "${i}";`},{path:"c.tsx",text:`import "${s}";`}]);t("\xA78's reader finds the entries that import the panel sheet, and the one lacking the menu sheet",e.withPanel.length===2&&e.missing.join()==="b.tsx")}{const{withPanel:e,missing:o}=a(h);t(`(l) \xA78 every entry that imports the panel sheet imports the menu sheet too (${e.length} entries, at least ${n.minEntries})`,e.length>=n.minEntries&&e.length>0&&o.length===0,o.length?`these mount the editor chrome but not \`${s}\`, so the menu draws as an unstyled full-width div there:
2
+ import "${s}";`},{path:"b.tsx",text:`import "${i}";`},{path:"c.tsx",text:`import "${s}";`}]);t("§8's reader finds the entries that import the panel sheet, and the one lacking the menu sheet",e.withPanel.length===2&&e.missing.join()==="b.tsx")}{const{withPanel:e,missing:o}=a(h);t(`(l) §8 every entry that imports the panel sheet imports the menu sheet too (${e.length} entries, at least ${n.minEntries})`,e.length>=n.minEntries&&e.length>0&&o.length===0,o.length?`these mount the editor chrome but not \`${s}\`, so the menu draws as an unstyled full-width div there:
3
3
  `+o.join(`
4
- `):`the walk found too few entries importing ${i} \u2014 it is not sweeping the editor at all, so this check is asking nothing.`)}return l()}export{p as editorContextMenu};
4
+ `):`the walk found too few entries importing ${i} — it is not sweeping the editor at all, so this check is asking nothing.`)}return l()}export{p as editorContextMenu};
@@ -1,4 +1,4 @@
1
- import{ruleChecks as g}from"./rule.js";const $=/<(?:Puck|SiteEditorProvider|EditorStoreProvider|ScreenStoreProvider)\b[^<>]*\bdata=\{[^<>]*>/g,S=/\bpermissions=\{\s*(?:documentPermissions\(|permissions\s*\})/,P=/permissions=\{\s*permissions\s*\}/,k=["delete","drag","duplicate","edit","insert"],d=o=>[...o.text.matchAll($)].map(n=>({file:o.path,tag:n[0]}));function u(o,n){const s=[];return S.test(o.tag)?P.test(o.tag)&&!/documentPermissions\(/.test(n)&&s.push("passes a `permissions` it does not build from documentPermissions"):s.push("mounts the editor without the edit lock's permissions"),s}function w(o,n){const{check:s,report:f}=g("editor puck drag off"),m=n.harnesses??{};for(const e of[!0,!1]){const t=n.documentPermissions(e);s(`documentPermissions(${e}).drag is false`,t.drag===!1,`got ${String(t.drag)}`),s(`documentPermissions(${e}) names all five keys \u2014 Puck MERGES this prop, so a missing key keeps its old value`,Object.keys(t).sort().join(",")===k.join(","),`got ${Object.keys(t).sort().join(",")}`),s(`documentPermissions(${e}).edit is ${e}`,t.edit===e)}{const e="Puck",t="SiteEditorProvider",i=a=>d({path:"fixture.tsx",text:a}).map(p=>u(p,a).length);s("the scanner flags a mount with no permissions",JSON.stringify(i(`<${e} config={c} data={d} />`))==="[1]"),s("the scanner passes a mount that calls the helper",JSON.stringify(i(`<${t} data={d} permissions={documentPermissions(!readOnly)}>`))==="[0]"),s("a local `permissions` passes only when the file builds it from the helper",JSON.stringify(i(`<${e} data={d} permissions={permissions} />`))==="[1]"&&JSON.stringify(i(`const permissions = documentPermissions(true);
2
- <${e} data={d} permissions={permissions} />`))==="[0]"),s("a tag with no `data=` is not a mount",i(`<${e}.Preview />`).length===0),s("the screen's store provider is a mount under either of its names",["Editor","Screen"].every(a=>JSON.stringify(i(`<${a}StoreProvider config={c} data={d} />`))==="[1]"))}const c=o.filter(e=>/\.tsx?$/.test(e.path));s(`read the source (${c.length} TS/TSX files, at least ${n.minFiles})`,c.length>=n.minFiles,"fewer means the walk broke, and every check below is vacuous");const r=[];for(const e of c)e.path in m||r.push(...d(e));for(const e of n.mustFind??[])s(`found the editor mount (data=\u2026) in ${e}`,r.some(t=>t.file===e));for(const[e,t]of Object.entries(m)){const i=c.find(a=>a.path===e);s(`the harness exemption is still real: ${e} (${t})`,!!i&&d(i).length>0,"remove the stale entry")}const h=new Map(c.map(e=>[e.path,e.text])),l=r.flatMap(e=>u(e,h.get(e.file)??"").map(t=>`${e.file} ${t}`));return s(`every editor mount turns Puck's own canvas drag off (${r.length} mount(s)${r.length?`: ${[...new Set(r.map(e=>e.file))].join(", ")}`:""})`,l.length===0,`without the edit lock's permissions a read-only viewer's canvas would take edits the save then refuses:
1
+ import{ruleChecks as g}from"./rule.js";const $=/<(?:Puck|SiteEditorProvider|EditorStoreProvider|ScreenStoreProvider)\b[^<>]*\bdata=\{[^<>]*>/g,S=/\bpermissions=\{\s*(?:documentPermissions\(|permissions\s*\})/,P=/permissions=\{\s*permissions\s*\}/,k=["delete","drag","duplicate","edit","insert"],d=o=>[...o.text.matchAll($)].map(n=>({file:o.path,tag:n[0]}));function u(o,n){const s=[];return S.test(o.tag)?P.test(o.tag)&&!/documentPermissions\(/.test(n)&&s.push("passes a `permissions` it does not build from documentPermissions"):s.push("mounts the editor without the edit lock's permissions"),s}function w(o,n){const{check:s,report:f}=g("editor puck drag off"),m=n.harnesses??{};for(const e of[!0,!1]){const t=n.documentPermissions(e);s(`documentPermissions(${e}).drag is false`,t.drag===!1,`got ${String(t.drag)}`),s(`documentPermissions(${e}) names all five keys — Puck MERGES this prop, so a missing key keeps its old value`,Object.keys(t).sort().join(",")===k.join(","),`got ${Object.keys(t).sort().join(",")}`),s(`documentPermissions(${e}).edit is ${e}`,t.edit===e)}{const e="Puck",t="SiteEditorProvider",i=a=>d({path:"fixture.tsx",text:a}).map(p=>u(p,a).length);s("the scanner flags a mount with no permissions",JSON.stringify(i(`<${e} config={c} data={d} />`))==="[1]"),s("the scanner passes a mount that calls the helper",JSON.stringify(i(`<${t} data={d} permissions={documentPermissions(!readOnly)}>`))==="[0]"),s("a local `permissions` passes only when the file builds it from the helper",JSON.stringify(i(`<${e} data={d} permissions={permissions} />`))==="[1]"&&JSON.stringify(i(`const permissions = documentPermissions(true);
2
+ <${e} data={d} permissions={permissions} />`))==="[0]"),s("a tag with no `data=` is not a mount",i(`<${e}.Preview />`).length===0),s("the screen's store provider is a mount under either of its names",["Editor","Screen"].every(a=>JSON.stringify(i(`<${a}StoreProvider config={c} data={d} />`))==="[1]"))}const c=o.filter(e=>/\.tsx?$/.test(e.path));s(`read the source (${c.length} TS/TSX files, at least ${n.minFiles})`,c.length>=n.minFiles,"fewer means the walk broke, and every check below is vacuous");const r=[];for(const e of c)e.path in m||r.push(...d(e));for(const e of n.mustFind??[])s(`found the editor mount (data=…) in ${e}`,r.some(t=>t.file===e));for(const[e,t]of Object.entries(m)){const i=c.find(a=>a.path===e);s(`the harness exemption is still real: ${e} (${t})`,!!i&&d(i).length>0,"remove the stale entry")}const h=new Map(c.map(e=>[e.path,e.text])),l=r.flatMap(e=>u(e,h.get(e.file)??"").map(t=>`${e.file} ${t}`));return s(`every editor mount turns Puck's own canvas drag off (${r.length} mount(s)${r.length?`: ${[...new Set(r.map(e=>e.file))].join(", ")}`:""})`,l.length===0,`without the edit lock's permissions a read-only viewer's canvas would take edits the save then refuses:
3
3
  ${l.join(`
4
4
  `)}`),f()}export{w as editorPuckDragOff};
@@ -1,2 +1,2 @@
1
1
  import{ruleChecks as b}from"./rule.js";const p=n=>JSON.stringify(n),t="efficient-easel",u=`${t}/panel.css`;function m(n,r){const s=/^\^(\d+)\.(\d+)\.(\d+)$/.exec(r.trim());if(!s)throw new Error(`only \`^x.y.z\` pins are understood here, got "${r}"`);const a=/^(\d+)\.(\d+)\.(\d+)/.exec(n.trim());if(!a)throw new Error(`"${n}" is not a version this can read`);const[i,c,h]=s.slice(1).map(Number),[e,o,l]=a.slice(1).map(Number);return!(e!==i||i===0&&o!==c||o<c||o===c&&l<h)}const $=n=>{const r=Math.max(n.lastIndexOf("/"),n.lastIndexOf("\\"));return r<=0?n.slice(0,r+1)||n:n.slice(0,r)};function g(n,r){const s=n(u);let a=$(s);for(let i=0;i<12;i++){const c=r(`${a}/package.json`);if(c!==void 0)try{const e=JSON.parse(c);if(e.name===t){if(!e.version)throw new Error(`${t} resolved but its package.json carries no version`);return e.version}}catch(e){if(!(e instanceof SyntaxError))throw e}const h=$(a);if(h===a)break;a=h}throw new Error(`resolved ${u} to ${s} but found no ${t} package.json above it`)}function v(n){const r=new Set;for(const s of n)if(/\.(ts|tsx|mts|mjs|js|jsx)$/.test(s.path))for(const a of s.text.matchAll(/(?:\bfrom\s*|\bimport\s*\(?\s*|\brequire\s*\(\s*)["'](efficient-easel\/[A-Za-z0-9._/-]+)["']/g))r.add(a[1]);return[...r].sort()}const k=n=>n.code??"threw";function x(n,r){const{check:s,report:a}=b(`${t} version`);{const e=(f,w)=>{try{return m(f,w)}catch{return"threw"}};s("the caret reading is right about 0.x, where the minor is part of the lock",e("0.10.0","^0.10.1")===!1&&e("0.10.1","^0.10.1")===!0&&e("0.11.0","^0.11.0")===!0&&e("0.11.0","^0.10.1")===!1&&e("0.10.1","^0.11.0")===!1&&e("0.11.4","^0.11.0")===!0&&e("0.11.4","~0.11.0")==="threw","a stale patch is caught; 0.x carets cross no minor and go no backwards; patches above the pin pass; only ^x.y.z is read");const o={"/s/node_modules/.pnpm/efficient-easel@1.2.3/node_modules/efficient-easel/package.json":JSON.stringify({name:t,version:"1.2.3"}),"/s/node_modules/.pnpm/efficient-easel@1.2.3/node_modules/efficient-easel/dist/package.json":"{ not json"};let l="";try{l=g(()=>"/s/node_modules/.pnpm/efficient-easel@1.2.3/node_modules/efficient-easel/dist/css/panel.css",f=>o[f])}catch(f){l=f.message}s("the manifest walk climbs past an unreadable manifest to the package's own",l==="1.2.3",l);const d=v([{path:"fixture.ts",text:[`import { A } from ${p(t+"/menu")};`,`// see ${t}/edit-lock`,`const m = await import(${p(t+"/icons")});`,`const s = ${p(t+"/not-an-import")};`].join(`
2
- `)}]);s("only import syntax counts as an import, never prose or a string",d.join()===`${t}/icons,${t}/menu`,JSON.stringify(d))}let i;try{const e=JSON.parse(r.packageJson);i=e.dependencies?.[t]??e.devDependencies?.[t]}catch(e){s("package.json reads as JSON",!1,e.message)}if(s(`package.json declares a dependency on ${t}`,i!==void 0)){let e;try{e=g(r.resolve,r.readText)}catch(o){s(`${t} resolves from the install`,!1,o.message)}if(e!==void 0){let o,l;try{o=m(e,i)}catch(d){o=!1,l=d.message}s(`the resolved ${t} satisfies the declared pin (declared ${i}, resolved ${e})`,o,l??`this tree resolves ${t} ${e}, which does not satisfy the declared ${i}. The install is stale \u2014 what you are looking at is not what this repository says it is. Refresh it with \`pnpm install\`, and inside Docker remember that a node_modules VOLUME outlives the container: it keeps whatever was installed into it until something installs again.`)}}const c=v(n);if(s(`found the ${t} imports (${c.length} subpaths)`,c.length>0,"the search is broken, not the code")){const e=[];for(const o of c)try{r.resolve(o)}catch(l){e.push(`${o} (${k(l)})`)}s(`every ${t} subpath this tree imports resolves from the install \u2014 ${c.join(", ")}`,e.length===0,`the installed package does not export: ${e.join(", ")}. The pin resolves and the lockfile is valid \u2014 the SUBPATH is missing, which means the pin points at a release published before it existed. Move the pin to the release that carries it.`)}let h=!1;try{r.resolve(`${t}/a-subpath-that-was-never-exported`)}catch{h=!0}return s(`the resolver refuses a ${t} subpath that does not exist`,h,"it resolved one \u2014 this check cannot fail"),a()}export{x as efficientEaselVersion,v as importedSubpaths,m as satisfiesCaret};
2
+ `)}]);s("only import syntax counts as an import, never prose or a string",d.join()===`${t}/icons,${t}/menu`,JSON.stringify(d))}let i;try{const e=JSON.parse(r.packageJson);i=e.dependencies?.[t]??e.devDependencies?.[t]}catch(e){s("package.json reads as JSON",!1,e.message)}if(s(`package.json declares a dependency on ${t}`,i!==void 0)){let e;try{e=g(r.resolve,r.readText)}catch(o){s(`${t} resolves from the install`,!1,o.message)}if(e!==void 0){let o,l;try{o=m(e,i)}catch(d){o=!1,l=d.message}s(`the resolved ${t} satisfies the declared pin (declared ${i}, resolved ${e})`,o,l??`this tree resolves ${t} ${e}, which does not satisfy the declared ${i}. The install is stale — what you are looking at is not what this repository says it is. Refresh it with \`pnpm install\`, and inside Docker remember that a node_modules VOLUME outlives the container: it keeps whatever was installed into it until something installs again.`)}}const c=v(n);if(s(`found the ${t} imports (${c.length} subpaths)`,c.length>0,"the search is broken, not the code")){const e=[];for(const o of c)try{r.resolve(o)}catch(l){e.push(`${o} (${k(l)})`)}s(`every ${t} subpath this tree imports resolves from the install — ${c.join(", ")}`,e.length===0,`the installed package does not export: ${e.join(", ")}. The pin resolves and the lockfile is valid — the SUBPATH is missing, which means the pin points at a release published before it existed. Move the pin to the release that carries it.`)}let h=!1;try{r.resolve(`${t}/a-subpath-that-was-never-exported`)}catch{h=!0}return s(`the resolver refuses a ${t} subpath that does not exist`,h,"it resolved one — this check cannot fail"),a()}export{x as efficientEaselVersion,v as importedSubpaths,m as satisfiesCaret};
@@ -1,7 +1,7 @@
1
1
  import{ruleChecks as F}from"./rule.js";const h=/[-a-zA-Z0-9:/.[\]]*bg-accent[-a-zA-Z0-9:/.[\]]*/g,u=/[-a-zA-Z0-9:/.[\]]*bg-btn[-a-zA-Z0-9:/.[\]]*/g;function N(o){const t=o.trim();return t.startsWith("//")||t.startsWith("*")||t.startsWith("/*")}function f(o,t){const n=[];for(const r of o)r.text.split(`
2
- `).forEach((l,i)=>{t.lastIndex=0,!(!t.test(l)||N(l))&&n.push({file:r.path,line:i+1,text:l})});return n}function g(o,t){const n=new Map;for(const r of o){const l=r.text.match(t)?.length??0;l&&n.set(r.file,(n.get(r.file)??0)+l)}return n}const d=o=>[...o.values()].reduce((t,n)=>t+n,0);function $(o){const t=/(?<![-:\w])text-bg\b/.test(o),n=/\btext-black\b/.test(o),r=/\bhover:text-bg\b/.test(o)&&/\bhover:bg-btn\b/.test(o);return t||n||r}const w=o=>/\bhover:bg-fg\b/.test(o)&&/\bhover:text-bg\b/.test(o);function B(o,t){const{check:n,report:r}=F("every button follows the button colour");{const e={path:"fixture.tsx",text:['<button className="bg-btn text-btn-ink hover:bg-btn/90" />','// <button className="bg-accent" /> \u2014 a comment is not paint','<span className={on ? "bg-accent text-bg" : "bg-fg/10"} />','<button className="bg-btn text-bg" />','<button className="bg-btn hover:bg-fg hover:text-bg" />'].join(`
3
- `)},a=f([e],u);n("the readers count fills per occurrence and drop comment lines",d(g(a,u))===4&&d(g(f([e],h),h))===1),n("\xA74's reader flags page ink on a button and keeps the --fg hover's ink",a.filter(s=>$(s.text)).map(s=>s.line).join()==="4"&&a.filter(s=>w(s.text)).map(s=>s.line).join()==="5")}const l=o.filter(e=>/\.(tsx?|css)$/.test(e.path)),i=new Set(t.exclude??[]);for(const e of i)n(`the excluded file is still real: ${e}`,l.some(a=>a.path===e),"remove the stale entry");const b=l.filter(e=>!i.has(e.path));n(`read the .ts, .tsx and .css files (${b.length})`,b.length>0,"the population is empty \u2014 this rule is measuring nothing");const y=f(b,h),c=f(b,u),m=g(y,h),p=g(c,u);{const e=[];for(const[a,s,k]of t.buttonFiles){const v=p.get(a)??0,x=m.get(a)??0;v!==s&&e.push(`${a}: ${v} bg-btn, expected ${s}`),x!==k&&e.push(`${a}: ${x} bg-accent left, expected ${k}`)}n(`\xA71 every ruled button paints the button token, and its file's markers do not (${t.buttonFiles.length} files)`,e.length===0,`a ruled button still paints --accent, or a marker was swept along with it:
2
+ `).forEach((l,i)=>{t.lastIndex=0,!(!t.test(l)||N(l))&&n.push({file:r.path,line:i+1,text:l})});return n}function g(o,t){const n=new Map;for(const r of o){const l=r.text.match(t)?.length??0;l&&n.set(r.file,(n.get(r.file)??0)+l)}return n}const d=o=>[...o.values()].reduce((t,n)=>t+n,0);function $(o){const t=/(?<![-:\w])text-bg\b/.test(o),n=/\btext-black\b/.test(o),r=/\bhover:text-bg\b/.test(o)&&/\bhover:bg-btn\b/.test(o);return t||n||r}const w=o=>/\bhover:bg-fg\b/.test(o)&&/\bhover:text-bg\b/.test(o);function B(o,t){const{check:n,report:r}=F("every button follows the button colour");{const e={path:"fixture.tsx",text:['<button className="bg-btn text-btn-ink hover:bg-btn/90" />','// <button className="bg-accent" /> — a comment is not paint','<span className={on ? "bg-accent text-bg" : "bg-fg/10"} />','<button className="bg-btn text-bg" />','<button className="bg-btn hover:bg-fg hover:text-bg" />'].join(`
3
+ `)},a=f([e],u);n("the readers count fills per occurrence and drop comment lines",d(g(a,u))===4&&d(g(f([e],h),h))===1),n("§4's reader flags page ink on a button and keeps the --fg hover's ink",a.filter(s=>$(s.text)).map(s=>s.line).join()==="4"&&a.filter(s=>w(s.text)).map(s=>s.line).join()==="5")}const l=o.filter(e=>/\.(tsx?|css)$/.test(e.path)),i=new Set(t.exclude??[]);for(const e of i)n(`the excluded file is still real: ${e}`,l.some(a=>a.path===e),"remove the stale entry");const b=l.filter(e=>!i.has(e.path));n(`read the .ts, .tsx and .css files (${b.length})`,b.length>0,"the population is empty — this rule is measuring nothing");const y=f(b,h),c=f(b,u),m=g(y,h),p=g(c,u);{const e=[];for(const[a,s,k]of t.buttonFiles){const v=p.get(a)??0,x=m.get(a)??0;v!==s&&e.push(`${a}: ${v} bg-btn, expected ${s}`),x!==k&&e.push(`${a}: ${x} bg-accent left, expected ${k}`)}n(`§1 every ruled button paints the button token, and its file's markers do not (${t.buttonFiles.length} files)`,e.length===0,`a ruled button still paints --accent, or a marker was swept along with it:
4
4
  `+e.join(`
5
- `))}{const e=d(m);n(`\xA72 ${t.accentFillsThatStay} bg-accent fills stay (found ${e})`,e===t.accentFillsThatStay,"either a marker was swept (too few) or new accent paint arrived (too many) \u2014 this count is derived from the tree, so a new call site has to come past it.");const a=d(p);n(`\xA72 ${t.buttonFills} bg-btn fills (found ${a})`,a===t.buttonFills)}for(const e of t.chips??[])n(`\xA73 ${e} still paints bg-accent`,(m.get(e)??0)>0,"a filter chip is not a button, and a sweep that repaints it has swept too far");{n(`\xA74 ${t.buttonSites} sites paint bg-btn (found ${c.length})`,c.length===t.buttonSites,"\xA74 cannot say anything about ink until the fills have actually moved");const e=c.filter(s=>$(s.text)).map(s=>`${s.file}:${s.line}`);n("\xA74 no button inks a bg-btn state with the page background or a literal",e.length===0,`a merchant's button colour moves the fill and leaves the ink behind:
5
+ `))}{const e=d(m);n(`§2 ${t.accentFillsThatStay} bg-accent fills stay (found ${e})`,e===t.accentFillsThatStay,"either a marker was swept (too few) or new accent paint arrived (too many) — this count is derived from the tree, so a new call site has to come past it.");const a=d(p);n(`§2 ${t.buttonFills} bg-btn fills (found ${a})`,a===t.buttonFills)}for(const e of t.chips??[])n(`§3 ${e} still paints bg-accent`,(m.get(e)??0)>0,"a filter chip is not a button, and a sweep that repaints it has swept too far");{n(`§4 ${t.buttonSites} sites paint bg-btn (found ${c.length})`,c.length===t.buttonSites,"§4 cannot say anything about ink until the fills have actually moved");const e=c.filter(s=>$(s.text)).map(s=>`${s.file}:${s.line}`);n("§4 no button inks a bg-btn state with the page background or a literal",e.length===0,`a merchant's button colour moves the fill and leaves the ink behind:
6
6
  `+e.join(`
7
- `));const a=c.filter(s=>w(s.text)).length;n(`\xA74 ${t.hoverFgKeepers} buttons still pair hover:bg-fg with hover:text-bg (found ${a})`,a===t.hoverFgKeepers,"a sweep that moved those to --btn-ink has put the button's ink on a foreground-coloured fill")}if(t.runtimeFallbacks!==void 0){const e=o.find(a=>a.path===t.runtimeFallbacks);if(n(`\xA75 read ${t.runtimeFallbacks}`,!!e)){const a=[...e.text.matchAll(/const (btnBg|btnFg) = \w+ \|\| "([^"]+)";/g)].map(s=>[s[1],s[2]]);n(`\xA75 found the 2 button-colour fallbacks (found ${a.length})`,a.length===2,"if the shape changed, this rule stopped reading the thing it names"),n("\xA75 the UNSET button falls back to --btn and --btn-ink",JSON.stringify(Object.fromEntries(a))===JSON.stringify({btnBg:"var(--btn)",btnFg:"var(--btn-ink)"}),"it falls back to the accent or to a literal again. Unset is the majority case, and on hartes-metal that painted #C0392B under a hard-coded black \u2014 card 66309's defect on the most prominent button of the page. Only the fallback may move; a merchant's own colour short-circuits it.")}}return r()}export{B as everyButtonFollowsTheButtonColour};
7
+ `));const a=c.filter(s=>w(s.text)).length;n(`§4 ${t.hoverFgKeepers} buttons still pair hover:bg-fg with hover:text-bg (found ${a})`,a===t.hoverFgKeepers,"a sweep that moved those to --btn-ink has put the button's ink on a foreground-coloured fill")}if(t.runtimeFallbacks!==void 0){const e=o.find(a=>a.path===t.runtimeFallbacks);if(n(`§5 read ${t.runtimeFallbacks}`,!!e)){const a=[...e.text.matchAll(/const (btnBg|btnFg) = \w+ \|\| "([^"]+)";/g)].map(s=>[s[1],s[2]]);n(`§5 found the 2 button-colour fallbacks (found ${a.length})`,a.length===2,"if the shape changed, this rule stopped reading the thing it names"),n("§5 the UNSET button falls back to --btn and --btn-ink",JSON.stringify(Object.fromEntries(a))===JSON.stringify({btnBg:"var(--btn)",btnFg:"var(--btn-ink)"}),"it falls back to the accent or to a literal again. Unset is the majority case, and on hartes-metal that painted #C0392B under a hard-coded black — card 66309's defect on the most prominent button of the page. Only the fallback may move; a merchant's own colour short-circuits it.")}}return r()}export{B as everyButtonFollowsTheButtonColour};
@@ -3,6 +3,6 @@ import{isRuleSource as y,ruleChecks as S}from"./rule.js";import{registryFiles as
3
3
  const b = contextLighting;
4
4
  `},{path:"fixture.css",text:`.x { color: var(--theme-accent-light-2); }
5
5
  .y { color: var(--theme-accent-light); }
6
- `}],r=u(e,A("textLight"));t("the name reader flags `textLight`, and not `contextLighting`",r.length===1&&r[0].startsWith("fixture.ts:1"),JSON.stringify(r));const n=u(e,$("--theme-accent-light"));t("the token reader flags `--theme-accent-light`, and not `--theme-accent-light-2`",n.length===1&&n[0].startsWith("fixture.css:2"),JSON.stringify(n))}const l=c.filter(e=>k.test(e.path)&&!y(e.path));t(`read the shipped source (${l.length} files, at least ${a.minFiles})`,l.length>=a.minFiles,"every check below asserts an absence, and a read of nothing satisfies an absence");const f=(e,r,n)=>{const s=u(l,r);t(`${e} is gone`,s.length===0,`\`${e}\` still lives in ${s.length} place(s) \u2014 ${n} (66238):
6
+ `}],r=u(e,A("textLight"));t("the name reader flags `textLight`, and not `contextLighting`",r.length===1&&r[0].startsWith("fixture.ts:1"),JSON.stringify(r));const n=u(e,$("--theme-accent-light"));t("the token reader flags `--theme-accent-light`, and not `--theme-accent-light-2`",n.length===1&&n[0].startsWith("fixture.css:2"),JSON.stringify(n))}const l=c.filter(e=>k.test(e.path)&&!y(e.path));t(`read the shipped source (${l.length} files, at least ${a.minFiles})`,l.length>=a.minFiles,"every check below asserts an absence, and a read of nothing satisfies an absence");const f=(e,r,n)=>{const s=u(l,r);t(`${e} is gone`,s.length===0,`\`${e}\` still lives in ${s.length} place(s) — ${n} (66238):
7
7
  ${s.join(`
8
- `)}`)};for(const e of x)f(e,A(e),"a theme is ONE flat palette");for(const e of[..._,...R,...b])f(e,A(e),"a theme is ONE flat palette");for(const e of N)f(e,$(e),"the keys and the tokens are ONE mechanism");if(a.theme){const e=a.theme;e.DEFAULT_THEMES.forEach((n,s)=>{const g=n,o=I.filter(h=>typeof g[h]!="string");t(`preset ${s} still states all six base colours`,o.length===0,`lost ${o.join(", ")} \u2014 66238 removes a duplicated HALF, never a colour`);const i=v.filter(h=>h in g);t(`preset ${s} carries no key 66238 deleted`,i.length===0,`still carries ${i.join(", ")}`)}),t(`read the presets (${e.DEFAULT_THEMES.length})`,e.DEFAULT_THEMES.length>0);const r={selected:0,themes:[e.DEFAULT_THEMES[0]]};for(const[n,s]of[["themeAccentStyle",e.themeAccentStyle(r)],["themeColorStyle",e.themeColorStyle(r)]])t(`${n} does not set --accent or --accent-2 directly`,!("--accent"in s)&&!("--accent-2"in s));t("the grading bands sit exactly on the WCAG thresholds",e.gradeContrast(e.READABILITY_AA)==="good"&&e.gradeContrast(e.READABILITY_AA+.5)==="good"&&e.gradeContrast(e.READABILITY_AA-.01)==="large-only"&&e.gradeContrast(e.READABILITY_AA_LARGE)==="large-only"&&e.gradeContrast(e.READABILITY_AA_LARGE-.01)==="hard"&&e.gradeContrast(1)==="hard"),t("the contrast ratio is the real WCAG figure, and symmetric",e.contrastRatio("#000000","#ffffff")===21&&e.contrastRatio("#ffffff","#000000")===21&&e.contrastRatio("not a colour","#ffffff")===null);{const n="#c9a461",s=e.normalizeThemeColors({text:"#0a0a0a",accent:n,background:"#f5f1e8"}).background,g=e.darkerAccents(n),o=[];g.length!==e.ACCENT_DARKEN_STEPS.length&&o.push(`${g.length} suggestions for ${e.ACCENT_DARKEN_STEPS.length} steps`);let i=e.contrastRatio(n,s)??0;for(const h of g){/^#[0-9a-f]{6}$/.test(h)||o.push(`${h} is not a plain hex`);const m=e.contrastRatio(h,s)??0;m>i||o.push(`${h} (${m}) does not beat the previous ${i}`),i=m}e.gradeContrast(i)!=="good"&&o.push(`the deepest reads ${e.gradeContrast(i)}, not good`),t("the darker suggestions are the same hue, and actually read better",o.length===0,o.join("; "))}t("a non-hex suggestion list is empty rather than three swatches of black",e.darkerAccents("var(--brand)").length===0)}if(a.liveAccentCarrier){const e=c.find(r=>r.path===a.liveAccentCarrier);t(`read the live site's accent carrier, ${a.liveAccentCarrier}`,!!e),t("the live site's accent carrier wears sf-theme-accent",!!e&&/className="contents sf-theme-accent"/.test(e.text))}const E=T(c),p=C(c);t(`read this repository's share of the block registry (${E.length} files)`,E.length>0);const L=[...p.matchAll(/className="[^"]*sf-theme-accent/g)].length;return t(`the registry's editor accent carriers wear sf-theme-accent (found ${L}, expected exactly ${a.editorAccentCarriers})`,L===a.editorAccentCarriers),t("the editor preview emits --theme-accent via themeAccentStyle, not a hand-rolled --accent",!/\["--accent"\]:/.test(p)),d()}export{F as flatThemePalette};
8
+ `)}`)};for(const e of x)f(e,A(e),"a theme is ONE flat palette");for(const e of[..._,...R,...b])f(e,A(e),"a theme is ONE flat palette");for(const e of N)f(e,$(e),"the keys and the tokens are ONE mechanism");if(a.theme){const e=a.theme;e.DEFAULT_THEMES.forEach((n,s)=>{const g=n,o=I.filter(h=>typeof g[h]!="string");t(`preset ${s} still states all six base colours`,o.length===0,`lost ${o.join(", ")} — 66238 removes a duplicated HALF, never a colour`);const i=v.filter(h=>h in g);t(`preset ${s} carries no key 66238 deleted`,i.length===0,`still carries ${i.join(", ")}`)}),t(`read the presets (${e.DEFAULT_THEMES.length})`,e.DEFAULT_THEMES.length>0);const r={selected:0,themes:[e.DEFAULT_THEMES[0]]};for(const[n,s]of[["themeAccentStyle",e.themeAccentStyle(r)],["themeColorStyle",e.themeColorStyle(r)]])t(`${n} does not set --accent or --accent-2 directly`,!("--accent"in s)&&!("--accent-2"in s));t("the grading bands sit exactly on the WCAG thresholds",e.gradeContrast(e.READABILITY_AA)==="good"&&e.gradeContrast(e.READABILITY_AA+.5)==="good"&&e.gradeContrast(e.READABILITY_AA-.01)==="large-only"&&e.gradeContrast(e.READABILITY_AA_LARGE)==="large-only"&&e.gradeContrast(e.READABILITY_AA_LARGE-.01)==="hard"&&e.gradeContrast(1)==="hard"),t("the contrast ratio is the real WCAG figure, and symmetric",e.contrastRatio("#000000","#ffffff")===21&&e.contrastRatio("#ffffff","#000000")===21&&e.contrastRatio("not a colour","#ffffff")===null);{const n="#c9a461",s=e.normalizeThemeColors({text:"#0a0a0a",accent:n,background:"#f5f1e8"}).background,g=e.darkerAccents(n),o=[];g.length!==e.ACCENT_DARKEN_STEPS.length&&o.push(`${g.length} suggestions for ${e.ACCENT_DARKEN_STEPS.length} steps`);let i=e.contrastRatio(n,s)??0;for(const h of g){/^#[0-9a-f]{6}$/.test(h)||o.push(`${h} is not a plain hex`);const m=e.contrastRatio(h,s)??0;m>i||o.push(`${h} (${m}) does not beat the previous ${i}`),i=m}e.gradeContrast(i)!=="good"&&o.push(`the deepest reads ${e.gradeContrast(i)}, not good`),t("the darker suggestions are the same hue, and actually read better",o.length===0,o.join("; "))}t("a non-hex suggestion list is empty rather than three swatches of black",e.darkerAccents("var(--brand)").length===0)}if(a.liveAccentCarrier){const e=c.find(r=>r.path===a.liveAccentCarrier);t(`read the live site's accent carrier, ${a.liveAccentCarrier}`,!!e),t("the live site's accent carrier wears sf-theme-accent",!!e&&/className="contents sf-theme-accent"/.test(e.text))}const E=T(c),p=C(c);t(`read this repository's share of the block registry (${E.length} files)`,E.length>0);const L=[...p.matchAll(/className="[^"]*sf-theme-accent/g)].length;return t(`the registry's editor accent carriers wear sf-theme-accent (found ${L}, expected exactly ${a.editorAccentCarriers})`,L===a.editorAccentCarriers),t("the editor preview emits --theme-accent via themeAccentStyle, not a hand-rolled --accent",!/\["--accent"\]:/.test(p)),d()}export{F as flatThemePalette};
@@ -1,2 +1,2 @@
1
1
  import{ruleChecks as f}from"./rule.js";const o="max-w-[var(--sf-site-width,1600px)]",p=t=>(t.split("/").pop()??t).replace(/\.tsx$/,"");function a(t){return{asks:t.includes("sectionRunsFullWidth(widthProps)"),column:t.includes(`: "mx-auto ${o}`)||t.includes(`: "relative z-[2] mx-auto ${o}`)||t.includes(`: "mx-auto grid ${o}`)}}function w(t,l){const{check:s,report:c}=f("full width means full");{const e=a(`const full = sectionRunsFullWidth(widthProps);
2
- <div className={full ? "w-full" : "mx-auto ${o} px-6"} />`),n=a(`<div className="mx-auto ${o} px-6" />`);s("the reader passes a conditional cap",e.asks&&e.column),s("\u2026and fails an unconditional one",!n.asks&&!n.column)}const r=t.filter(e=>e.path.endsWith(".tsx")),i=r.filter(e=>e.text.includes(o));s(`\u2026and it finds the files that carry one (${i.length} of ${r.length} .tsx files, at least ${l.carrierFloor})`,i.length>=l.carrierFloor&&i.length>0,`only ${i.length} files carry ${o}`);for(const e of l.converted){const n=t.find(u=>u.path===e);if(!s(`read ${e}`,!!n,"the converted section is gone \u2014 remove it from the list, or find where it went"))continue;const{asks:d,column:h}=a(n.text);s(`${p(e)} asks whether it runs full width`,d,"its content column is unconditional again \u2014 a merchant choosing Full width would get a capped section, which is the defect this card removed"),s("\u2026and still draws the column when it does not",h,"the other arm lost the cap \u2014 every published page of this type would go edge to edge, which is NOT what this card does")}if(l.widthHelper!==void 0){const e=t.find(n=>n.path===l.widthHelper);s(`read the width helper ${l.widthHelper}`,!!e)&&s("sectionRunsFullWidth resolves absence to the content column, never to full",/resolveWidth\(props as WidthProps, "page"\)/.test(e.text),"absence resolving to `full` sends every published section of these types edge to edge on the day this ships")}return c()}export{o as SITE_CAP,w as fullWidthMeansFull};
2
+ <div className={full ? "w-full" : "mx-auto ${o} px-6"} />`),n=a(`<div className="mx-auto ${o} px-6" />`);s("the reader passes a conditional cap",e.asks&&e.column),s("…and fails an unconditional one",!n.asks&&!n.column)}const r=t.filter(e=>e.path.endsWith(".tsx")),i=r.filter(e=>e.text.includes(o));s(`…and it finds the files that carry one (${i.length} of ${r.length} .tsx files, at least ${l.carrierFloor})`,i.length>=l.carrierFloor&&i.length>0,`only ${i.length} files carry ${o}`);for(const e of l.converted){const n=t.find(u=>u.path===e);if(!s(`read ${e}`,!!n,"the converted section is gone — remove it from the list, or find where it went"))continue;const{asks:d,column:h}=a(n.text);s(`${p(e)} asks whether it runs full width`,d,"its content column is unconditional again — a merchant choosing Full width would get a capped section, which is the defect this card removed"),s("…and still draws the column when it does not",h,"the other arm lost the cap — every published page of this type would go edge to edge, which is NOT what this card does")}if(l.widthHelper!==void 0){const e=t.find(n=>n.path===l.widthHelper);s(`read the width helper ${l.widthHelper}`,!!e)&&s("sectionRunsFullWidth resolves absence to the content column, never to full",/resolveWidth\(props as WidthProps, "page"\)/.test(e.text),"absence resolving to `full` sends every published section of these types edge to edge on the day this ships")}return c()}export{o as SITE_CAP,w as fullWidthMeansFull};
@@ -1,8 +1,8 @@
1
1
  import{HEADING_FACE_UTILITIES as _}from"../fonts.js";import{isRuleSource as k,ruleChecks as T}from"./rule.js";import{evalLength as S}from"./support/css-length.js";const j=["app/","components/","lib/"],v="40px",E=["1","1.4"],b=/(?:^|\s)((?:[a-z0-9@_.-]*\[[^\]]*\]:|[a-z0-9@_.-]+:)*)text-\[([^\]]*)\]/g;function L(t){const e=/max-\[(\d+)px\]:/.exec(t);return e?Number(e[1]):1600}function O(t,e){let s=-1,o="";for(let i=e;i>0;i--){const l=t[i-1];if((l==="`"||l==="'"||l==='"')&&t[i-2]!=="\\"){o=l,s=i;break}}if(s===-1)return null;for(let i=e;i<t.length;i++)if(t[i]===o&&t[i-1]!=="\\")return t.slice(s,i);return null}const A=/^(#|rgb|hsl|oklch|color|currentColor|inherit|transparent)/;function y(t){const e=[];for(const s of t){const o=s.text,i=new RegExp(_.join("|"),"g");let l;for(;l=i.exec(o);){const a=O(o,l.index);if(!a)continue;const h=[];b.lastIndex=0;let c;for(;c=b.exec(a);){const p=c[2].replace(/^length:/,"");A.test(p)||h.push({token:c[0].trim(),expr:p,vw:L(c[1])})}h.length!==0&&e.push({file:s.path,line:o.slice(0,l.index).split(`
2
- `).length,className:a,steps:h})}}return e}function u(t,e){return S(t.expr,t.vw,e)}const C=t=>u(t,{"--scale-heading":"1"})>40;function m(t,e){return t[`${e.file}:${e.line}`]??Object.entries(t).find(([s])=>{const o=s.indexOf("#");return o>0&&s.slice(0,o)===e.file&&e.className.includes(s.slice(o+1))})?.[1]??t[e.file]}const I=t=>t.filter(e=>/\.tsx?$/.test(e.path)&&!k(e.path)&&j.some(s=>e.path.startsWith(s)));function D(t,e){const s=e.notSwept??{},o=y(I(t)).filter(l=>!m(s,l)).flatMap(l=>l.steps.filter(a=>!C(a)&&u(a,{"--sf-heading-max":v,"--scale-heading":"1.4"})>40).map(a=>`${l.file}:${l.line} \xB7 ${a.token}`)),i=[];if(o.length){i.push(` note \u2014 ${o.length} step(s) clear 40px at the default heading scale but not at 1.4; outside this card's census, listed so they are not a silent gap:`);for(const l of o)i.push(` ${l}`)}for(const[l,a]of Object.entries(s))i.push(` note \u2014 not swept: ${l} \u2014 ${a}`);return i}function H(t,e){const{check:s,report:o}=T("heading ceiling sweep"),i=e.notSwept??{},l=I(t),a=y(l);s(`read the source (${l.length} .ts/.tsx files)`,l.length>0,"found no source to sweep \u2014 this rule is not measuring anything"),s(`the scanner finds display headings at all (${a.length}, at least ${e.minSites})`,a.length>=e.minSites,`found ${a.length} heading-face class lists with a text-[\u2026] size`);const h=a.filter(n=>!m(i,n)&&n.steps.some(C));for(const n of Object.keys(i)){const r=n.split(/[:#]/)[0];s(`notSwept["${n}"] still matches a real site`,a.some(f=>f.file===r&&m(i,f)===i[n]))}s(`the census finds the headings a 40px ceiling has to bring down (${h.length}, at least ${e.minSwept})`,h.length>=e.minSwept,`only ${h.length} class lists declare a size above 40px`);const c=[];for(const n of h)for(const r of n.steps)for(const f of E){const d=u(r,{"--sf-heading-max":v,"--scale-heading":f});d>40&&c.push(`${n.file}:${n.line} @scale ${f} \u2192 ${d}px \xB7 ${r.token}`)}s("no swept display heading renders above the site's 40px ceiling",c.length===0,`${c.length} step(s) ignore it:
2
+ `).length,className:a,steps:h})}}return e}function u(t,e){return S(t.expr,t.vw,e)}const C=t=>u(t,{"--scale-heading":"1"})>40;function m(t,e){return t[`${e.file}:${e.line}`]??Object.entries(t).find(([s])=>{const o=s.indexOf("#");return o>0&&s.slice(0,o)===e.file&&e.className.includes(s.slice(o+1))})?.[1]??t[e.file]}const I=t=>t.filter(e=>/\.tsx?$/.test(e.path)&&!k(e.path)&&j.some(s=>e.path.startsWith(s)));function D(t,e){const s=e.notSwept??{},o=y(I(t)).filter(l=>!m(s,l)).flatMap(l=>l.steps.filter(a=>!C(a)&&u(a,{"--sf-heading-max":v,"--scale-heading":"1.4"})>40).map(a=>`${l.file}:${l.line} · ${a.token}`)),i=[];if(o.length){i.push(` note — ${o.length} step(s) clear 40px at the default heading scale but not at 1.4; outside this card's census, listed so they are not a silent gap:`);for(const l of o)i.push(` ${l}`)}for(const[l,a]of Object.entries(s))i.push(` note — not swept: ${l} — ${a}`);return i}function H(t,e){const{check:s,report:o}=T("heading ceiling sweep"),i=e.notSwept??{},l=I(t),a=y(l);s(`read the source (${l.length} .ts/.tsx files)`,l.length>0,"found no source to sweep — this rule is not measuring anything"),s(`the scanner finds display headings at all (${a.length}, at least ${e.minSites})`,a.length>=e.minSites,`found ${a.length} heading-face class lists with a text-[…] size`);const h=a.filter(n=>!m(i,n)&&n.steps.some(C));for(const n of Object.keys(i)){const r=n.split(/[:#]/)[0];s(`notSwept["${n}"] still matches a real site`,a.some(f=>f.file===r&&m(i,f)===i[n]))}s(`the census finds the headings a 40px ceiling has to bring down (${h.length}, at least ${e.minSwept})`,h.length>=e.minSwept,`only ${h.length} class lists declare a size above 40px`);const c=[];for(const n of h)for(const r of n.steps)for(const f of E){const d=u(r,{"--sf-heading-max":v,"--scale-heading":f});d>40&&c.push(`${n.file}:${n.line} @scale ${f} → ${d}px · ${r.token}`)}s("no swept display heading renders above the site's 40px ceiling",c.length===0,`${c.length} step(s) ignore it:
3
3
  ${c.join(`
4
- `)}`);const p=a.flatMap(n=>n.steps.filter(r=>r.expr.includes("--sf-heading-max")).map(r=>({site:n,step:r})));s(`the sweep actually capped headings (${p.length} steps read --sf-heading-max, at least ${e.minCapped})`,p.length>=e.minCapped);const g=[];for(const{site:n,step:r}of p){const f=/^min\((.*),\s*var\(--sf-heading-max,[^)]*\)\)$/.exec(r.expr)?.[1];if(f!==void 0)for(const d of E){const $=S(f,r.vw,{"--scale-heading":d}),w=u(r,{"--scale-heading":d});$!==w&&g.push(`${n.file}:${n.line} @scale ${d}: ${$}px \u2192 ${w}px`)}}if(s("a tenant that sets no ceiling renders exactly what it renders today",g.length===0,`${g.length} step(s) moved:
4
+ `)}`);const p=a.flatMap(n=>n.steps.filter(r=>r.expr.includes("--sf-heading-max")).map(r=>({site:n,step:r})));s(`the sweep actually capped headings (${p.length} steps read --sf-heading-max, at least ${e.minCapped})`,p.length>=e.minCapped);const g=[];for(const{site:n,step:r}of p){const f=/^min\((.*),\s*var\(--sf-heading-max,[^)]*\)\)$/.exec(r.expr)?.[1];if(f!==void 0)for(const d of E){const $=S(f,r.vw,{"--scale-heading":d}),w=u(r,{"--scale-heading":d});$!==w&&g.push(`${n.file}:${n.line} @scale ${d}: ${$}px → ${w}px`)}}if(s("a tenant that sets no ceiling renders exactly what it renders today",g.length===0,`${g.length} step(s) moved:
5
5
  ${g.join(`
6
- `)}`),e.reference!==void 0){const n=t.find(r=>r.path===e.reference);s(`the reference component still spells the ceiling the one way (${e.reference})`,!!n&&/var\(--sf-heading-max,\s*9999px\)/.test(n.text),`${e.reference} no longer reads var(--sf-heading-max,9999px)`)}const N=/^var\(--sf-heading-max,/,x=p.filter(({step:n})=>!N.test(n.expr)).filter(({step:n})=>!/^min\(.*,var\(--sf-heading-max,9999px\)\)$/.test(n.expr)).map(({site:n,step:r})=>`${n.file}:${n.line} \xB7 ${r.token}`);return s("every capped step is spelt the reference's way",x.length===0,`${x.length} second spelling(s):
6
+ `)}`),e.reference!==void 0){const n=t.find(r=>r.path===e.reference);s(`the reference component still spells the ceiling the one way (${e.reference})`,!!n&&/var\(--sf-heading-max,\s*9999px\)/.test(n.text),`${e.reference} no longer reads var(--sf-heading-max,9999px)`)}const N=/^var\(--sf-heading-max,/,x=p.filter(({step:n})=>!N.test(n.expr)).filter(({step:n})=>!/^min\(.*,var\(--sf-heading-max,9999px\)\)$/.test(n.expr)).map(({site:n,step:r})=>`${n.file}:${n.line} · ${r.token}`);return s("every capped step is spelt the reference's way",x.length===0,`${x.length} second spelling(s):
7
7
  ${x.join(`
8
8
  `)}`),o()}export{O as enclosingLiteral,D as headingCeilingNotes,H as headingCeilingSweep};
@@ -1,5 +1,5 @@
1
- import{ruleChecks as m}from"./rule.js";import{readJsxLabels as $,readLabels as k}from"./support/label-extraction.js";function W(t){const e=[];return t.includes("==")?e.push("shows raw `==` to the merchant"):t.includes("=")&&e.push("carries an `x = y` legend"),/[()]/.test(t)&&e.push("carries a bracket hint"),t.includes(" \u2014 ")&&e.push("carries a ` \u2014 ` hint tail"),e}function j(t){return t.includes("=")?t.includes("==")?["shows raw `==` to the reader"]:["carries an `x = y` legend"]:[]}function x(t){const e=k(t.text),a=[];for(const r of e){const l=W(r.label);l.length&&a.push({where:`${t.path}:${r.line}`,label:r.label,why:l})}return{read:e.length,offenders:a}}function g(t){const e=$(t.text),a=[];for(const{line:r,label:l}of e){const i=j(l);i.length&&a.push({where:`${t.path}:${r}`,label:l,why:i})}return{read:e.length,offenders:a}}const w=t=>`${t.length} label(s) are hints, not names \u2014 the manual does the teaching:
2
- `+t.map(e=>` ${e.where} ${JSON.stringify(e.label)} \u2014 ${e.why.join(", ")}`).join(`
3
- `);function L(t,e){const{check:a,report:r}=m("label hints");{const n=x({path:"lib/puck/fixture.tsx",text:['heading: plainText("Heading (new line = break)"),','accent: plainText("Accent ==word=="),','legend: text("Size = px"),','tail: textarea("Body \u2014 one paragraph"),','body: plainText("Body", { placeholder: "A ==word== in the theme colour" }),','layout: { type: "select", label: "Layout",',' options: [{ label: "Split (editorial aside)", value: "split" }] },','defaultProps: { heading: "Our ==new== range" },'].join(`
4
- `)}),s=y=>n.offenders.filter(h=>h.where.endsWith(`:${y}`)).flatMap(h=>h.why);a("W1 flags a bracket hint (and the `x = y` inside it)",s(1).includes("carries a bracket hint")&&s(1).includes("carries an `x = y` legend")),a("W1 flags raw `==` in a label",s(2).includes("shows raw `==` to the merchant")),a("W1 flags an `x = y` legend",s(3).includes("carries an `x = y` legend")),a("W1 flags a ` \u2014 ` hint tail",s(4).includes("carries a ` \u2014 ` hint tail")),a("W1 passes a placeholder, a select option and a default prop, and reads the labels it should",n.offenders.length===4&&n.read===6,JSON.stringify(n));const o=g({path:"components/Fixture.tsx",text:['<Field label="Accent (==word==)" />','<Field label="Size = px" />','<Field label="Name (optional)" />','<button aria-label="a = b" />'].join(`
1
+ import{ruleChecks as m}from"./rule.js";import{readJsxLabels as $,readLabels as k}from"./support/label-extraction.js";function W(t){const e=[];return t.includes("==")?e.push("shows raw `==` to the merchant"):t.includes("=")&&e.push("carries an `x = y` legend"),/[()]/.test(t)&&e.push("carries a bracket hint"),t.includes(" — ")&&e.push("carries a ` — ` hint tail"),e}function j(t){return t.includes("=")?t.includes("==")?["shows raw `==` to the reader"]:["carries an `x = y` legend"]:[]}function x(t){const e=k(t.text),a=[];for(const r of e){const l=W(r.label);l.length&&a.push({where:`${t.path}:${r.line}`,label:r.label,why:l})}return{read:e.length,offenders:a}}function g(t){const e=$(t.text),a=[];for(const{line:r,label:l}of e){const i=j(l);i.length&&a.push({where:`${t.path}:${r}`,label:l,why:i})}return{read:e.length,offenders:a}}const w=t=>`${t.length} label(s) are hints, not names — the manual does the teaching:
2
+ `+t.map(e=>` ${e.where} ${JSON.stringify(e.label)} — ${e.why.join(", ")}`).join(`
3
+ `);function L(t,e){const{check:a,report:r}=m("label hints");{const n=x({path:"lib/puck/fixture.tsx",text:['heading: plainText("Heading (new line = break)"),','accent: plainText("Accent ==word=="),','legend: text("Size = px"),','tail: textarea("Body — one paragraph"),','body: plainText("Body", { placeholder: "A ==word== in the theme colour" }),','layout: { type: "select", label: "Layout",',' options: [{ label: "Split (editorial aside)", value: "split" }] },','defaultProps: { heading: "Our ==new== range" },'].join(`
4
+ `)}),s=y=>n.offenders.filter(h=>h.where.endsWith(`:${y}`)).flatMap(h=>h.why);a("W1 flags a bracket hint (and the `x = y` inside it)",s(1).includes("carries a bracket hint")&&s(1).includes("carries an `x = y` legend")),a("W1 flags raw `==` in a label",s(2).includes("shows raw `==` to the merchant")),a("W1 flags an `x = y` legend",s(3).includes("carries an `x = y` legend")),a("W1 flags a ` — ` hint tail",s(4).includes("carries a ` — ` hint tail")),a("W1 passes a placeholder, a select option and a default prop, and reads the labels it should",n.offenders.length===4&&n.read===6,JSON.stringify(n));const o=g({path:"components/Fixture.tsx",text:['<Field label="Accent (==word==)" />','<Field label="Size = px" />','<Field label="Name (optional)" />','<button aria-label="a = b" />'].join(`
5
5
  `)});a("W2 flags `==` and `x = y` in a JSX label, leaves a bracket alone, and reads no aria-label",o.offenders.length===2&&o.read===3&&o.offenders[0].why[0]==="shows raw `==` to the reader",JSON.stringify(o))}const l=t.filter(n=>n.path.startsWith("lib/puck/")&&/\.(ts|tsx)$/.test(n.path)),i=t.filter(n=>n.path.startsWith("components/")&&n.path.endsWith(".tsx")),f=l.map(x),u=i.map(g),d=f.reduce((n,s)=>n+s.read,0),c=u.reduce((n,s)=>n+s.read,0),p=f.flatMap(n=>n.offenders),b=u.flatMap(n=>n.offenders);return a(`read >= ${e.minLabels} field labels across ${l.length} files under lib/puck (found ${d})`,d>=e.minLabels,"the reader is broken, not the panel"),a(`read >= ${e.minJsxLabels} JSX label attributes across ${i.length} files under components (found ${c})`,c>=e.minJsxLabels,"the reader is broken, not the panel"),a(`W1 every field label under lib/puck is a plain name (${d} read)`,p.length===0,w(p)),a(`W2 no JSX label under components shows \`=\` (${c} read)`,b.length===0,w(b)),r()}export{W as hintsIn,L as labelHints};