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.
- package/CHANGELOG.md +47 -0
- package/README.md +7 -0
- package/dist/components/BlogPostSource.js +1 -1
- package/dist/components/Breadcrumb.js +1 -1
- package/dist/components/Btn.js +1 -1
- package/dist/components/CardShadowField.js +1 -1
- package/dist/components/CategoryBar.js +1 -1
- package/dist/components/ChartSection.js +1 -1
- package/dist/components/ChatSettingsPanel.js +1 -1
- package/dist/components/ClearColoursDialog.js +1 -1
- package/dist/components/ContactLocations.js +5 -5
- package/dist/components/CornersField.js +1 -1
- package/dist/components/DocBody.js +1 -1
- package/dist/components/DuplicateToPageMenu.js +1 -1
- package/dist/components/FeaturedCover.js +1 -1
- package/dist/components/FieldGlyph.js +1 -1
- package/dist/components/Footprint.js +1 -1
- package/dist/components/HeadingAudit.js +1 -1
- package/dist/components/HeroSection.js +1 -1
- package/dist/components/HoverEffectField.js +1 -1
- package/dist/components/ImageBlock.js +1 -1
- package/dist/components/ImageFocusRow.js +1 -1
- package/dist/components/LocationsGroupedField.js +1 -1
- package/dist/components/LongRead.js +1 -1
- package/dist/components/MediaUploadField.js +1 -1
- package/dist/components/MockupSection.js +1 -1
- package/dist/components/PagesPanel.js +1 -1
- package/dist/components/PartInkControl.js +1 -1
- package/dist/components/PartResetDot.js +1 -1
- package/dist/components/PartSettingsPanel.js +1 -1
- package/dist/components/PostCard.js +1 -1
- package/dist/components/ProcessSteps.js +1 -1
- package/dist/components/RMark.js +1 -1
- package/dist/components/Reel.js +1 -1
- package/dist/components/SectionEditor.js +1 -1
- package/dist/components/SectionHeader.js +1 -1
- package/dist/components/SectionThumbnail.js +1 -1
- package/dist/components/SlotChildRows.js +1 -1
- package/dist/components/SourceStatus.js +1 -1
- package/dist/components/Split.js +1 -1
- package/dist/components/StandaloneSectionEditor.js +1 -1
- package/dist/components/StylesPanel.js +1 -1
- package/dist/components/SwitchToBoxFooter.js +1 -1
- package/dist/components/Testimonials.js +1 -1
- package/dist/components/TextStyleRow.js +1 -1
- package/dist/components/TextStylesField.js +1 -1
- package/dist/components/ThemeCard.js +1 -1
- package/dist/components/ThemeColourField.js +1 -1
- package/dist/components/ThemeField.js +1 -1
- package/dist/components/TickerSettingsPanel.js +1 -1
- package/dist/components/Timeline.js +1 -1
- package/dist/components/TimelineTrack.js +1 -1
- package/dist/components/TitleWithButtons.js +1 -1
- package/dist/components/WebsiteScreenParts.js +1 -1
- package/dist/components/WebsiteSettingsPanel.d.ts +5 -9
- package/dist/components/WebsiteSettingsPanel.js +1 -1
- package/dist/components/goldPhrases.js +1 -1
- package/dist/components/storefront-editor.css +262 -542
- package/dist/lib/content/contact.js +3 -3
- package/dist/lib/content/subscribe.js +1 -1
- package/dist/lib/puck/ColourControl.d.ts +5 -0
- package/dist/lib/puck/ColourControl.js +1 -1
- package/dist/lib/puck/MarkupToolbar.js +1 -1
- package/dist/lib/puck/blocks/general/CategoryBar/fields.js +1 -1
- package/dist/lib/puck/blocks/general/Chart/fields.js +1 -1
- package/dist/lib/puck/blocks/general/FAQ/render.js +1 -1
- package/dist/lib/puck/blocks/general/FeaturedCover/render.js +1 -1
- package/dist/lib/puck/blocks/general/Footprint/fields.js +1 -1
- package/dist/lib/puck/blocks/general/IntroBleed/render.js +1 -1
- package/dist/lib/puck/blocks/general/LogoBlock/fields.js +1 -1
- package/dist/lib/puck/blocks/general/LongRead/render.js +1 -1
- package/dist/lib/puck/blocks/general/Masthead/render.js +1 -1
- package/dist/lib/puck/blocks/general/Mockup/fields.js +1 -1
- package/dist/lib/puck/blocks/general/OrderSummary/render.js +1 -1
- package/dist/lib/puck/blocks/general/ParagraphsA/render.js +1 -1
- package/dist/lib/puck/blocks/general/PostCard/render.js +1 -1
- package/dist/lib/puck/blocks/general/ProcessSteps/render.js +1 -1
- package/dist/lib/puck/blocks/general/Series/render.js +1 -1
- package/dist/lib/puck/blocks/general/Split/render.js +1 -1
- package/dist/lib/puck/blocks/general/Testimonials/fields.js +1 -1
- package/dist/lib/puck/blocks/general/Testimonials/render.js +1 -1
- package/dist/lib/puck/blocks/general/TimelineTrack/render.js +1 -1
- package/dist/lib/puck/blocks/placeholders.js +1 -1
- package/dist/lib/puck/controls.d.ts +0 -10
- package/dist/lib/puck/controls.js +1 -1
- package/dist/lib/puck/field-groups.js +1 -1
- package/dist/lib/puck/footer-block-row-name.js +1 -1
- package/dist/lib/puck/geocode-resolve.js +1 -1
- package/dist/lib/puck/locations-tree.js +2 -2
- package/dist/lib/puck/neutralization-contracts.js +1 -1
- package/dist/lib/puck/part-rows.js +1 -1
- package/dist/lib/puck/section-catalog.js +1 -1
- package/dist/lib/puck/section-descriptions.js +1 -1
- package/dist/lib/sections/divider-style.js +1 -1
- package/dist/lib/sections/product-card-style.js +1 -1
- package/dist/lib/sections/ticker-style.js +1 -1
- package/dist/lib/source-rules/accent-token-name.js +2 -2
- package/dist/lib/source-rules/button-corners.js +1 -1
- package/dist/lib/source-rules/editor-chrome-note.js +2 -2
- package/dist/lib/source-rules/editor-context-menu.js +2 -2
- package/dist/lib/source-rules/editor-puck-drag-off.js +2 -2
- package/dist/lib/source-rules/efficient-easel-version.js +1 -1
- package/dist/lib/source-rules/every-button-follows-the-button-colour.js +4 -4
- package/dist/lib/source-rules/flat-theme-palette.js +2 -2
- package/dist/lib/source-rules/full-width-means-full.js +1 -1
- package/dist/lib/source-rules/heading-ceiling-sweep.js +3 -3
- package/dist/lib/source-rules/label-hints.js +4 -4
- package/dist/lib/source-rules/label-vocabulary.js +5 -5
- package/dist/lib/source-rules/mirrored-rows-agree.js +4 -4
- package/dist/lib/source-rules/no-comment-in-jsx.js +2 -2
- package/dist/lib/source-rules/no-glyph-rows.js +2 -2
- package/dist/lib/source-rules/one-colour-control.js +2 -2
- package/dist/lib/source-rules/one-dropdown.js +2 -2
- package/dist/lib/source-rules/one-editable-list.js +3 -3
- package/dist/lib/source-rules/one-editor-list.js +1 -1
- package/dist/lib/source-rules/one-right-click.js +2 -2
- package/dist/lib/source-rules/one-switch.js +1 -1
- package/dist/lib/source-rules/one-up-level-link.js +7 -7
- package/dist/lib/source-rules/panel-colour-tokens.js +7 -7
- package/dist/lib/source-rules/panel-no-divider-lines.js +5 -5
- package/dist/lib/source-rules/panel-one-arrow.js +1 -1
- package/dist/lib/source-rules/panel-one-hover.js +2 -2
- package/dist/lib/source-rules/panel-rows-use-the-shared-row.js +5 -5
- package/dist/lib/source-rules/part-slider-fills-row.js +8 -8
- package/dist/lib/source-rules/part-style-naming.js +1 -1
- package/dist/lib/source-rules/puck-document-surfaces.js +6 -6
- package/dist/lib/source-rules/reduced-motion.js +2 -2
- package/dist/lib/source-rules/route-content-width.js +2 -2
- package/dist/lib/source-rules/rule.js +1 -1
- package/dist/lib/source-rules/section-theme-preview.js +2 -2
- package/dist/lib/source-rules/site-square-by-default.js +1 -1
- package/dist/lib/source-rules/site-width.js +2 -2
- package/dist/lib/source-rules/size-token-paints.js +2 -2
- package/dist/lib/source-rules/social-icons-import.js +1 -1
- package/dist/lib/source-rules/source-status-is-shared.js +1 -1
- package/dist/lib/source-rules/style-face-matches-size.js +3 -3
- package/dist/lib/source-rules/support/label-extraction.js +5 -5
- package/dist/lib/source-rules/support/part-slider-call-sites.js +4 -4
- package/dist/lib/source-rules/support/registry-source.js +1 -1
- package/dist/lib/source-rules/theme-mode-controls-gone.js +3 -3
- package/dist/lib/source-rules/upload-intake-guard.js +2 -2
- package/dist/lib/source-rules/word-styles.js +2 -2
- package/package.json +7 -7
- package/dist/lib/puck/slider-typing.d.ts +0 -9
- package/dist/lib/puck/slider-typing.js +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{failedChecks as $,ruleChecks as x}from"./rule.js";import{isPanelFile as v,readLabels as w,readPanelLabels as C}from"./support/label-extraction.js";import{registryFiles as T}from"./support/registry-source.js";const p=["kicker","tagline","headline","blurb","description","copy","paragraph","photo","picture","cta","kind","class","grayscale","greyscale","colour","align","align items","text alignment","centre","catalogue","behaviour","labelled","stylised","stack","container","gutter","margin","site width","max width","contained","boxed","header offset","clearance"],j=o=>o.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),R=p.map(o=>({word:o,pattern:new RegExp(`\\b${j(o)}s?\\b`)}));function H(o){return o.split(/\(| — /)[0].trim()}function k(o,c){const t=H(o).toLowerCase(),a=[];for(const{word:d,pattern:l}of R)l.test(t)&&a.push(`N10.1 "${d}"`);/^title\b/.test(t)&&!c&&a.push("N10.2 Title at section level"),/^heading\b/.test(t)&&c&&a.push("N10.2 Heading at item level");const e=/\b(button|link)\s+(text|label|href|url)$/.exec(t);return e&&a.push(`N10.3 "${e[1]} ${e[2]}"`),t==="text"&&a.push("N10.4 bare Text"),a}function N(o,c,t){const a=T(o),e=o.filter(v),d=new Map,l=[],h=(n,
|
|
2
|
-
`+i.map(n=>` ${n.file}:${n.line} ${n.item?"[item] ":"[section]"} ${JSON.stringify(n.label)}
|
|
3
|
-
`));for(const n of c.parked)t(`PARKED ${n.file} ${JSON.stringify(n.label)} still breaks N10
|
|
4
|
-
`)},{path:"components/FixturePanel.tsx",text:['import { SettingsField } from "efficient-easel/panel";',"const items = [",' { label: "Copy", onClick: () => copy() },',"];","const rows = [",' { key: "photo", label: "Photo", group: "media" },',"];",'<SettingsField label="Colour" />'].join(`
|
|
5
|
-
`)},{path:"components/HandRolled.tsx",text:'<div className="sf-field"><Row label="Gutter" /></div>'},{path:"components/ShopperForm.tsx",text:'<Field label="Description" />'}],d=x("fixture"),{read:l,live:h}=N(e,{floors:{"components/FixturePanel.tsx":5,"components/HandRolled.tsx":1,"components/Moved.tsx":1},minRegistryLabels:8,parked:[{file:"components/FixturePanel.tsx",label:"Colour",why:"fixture: still breaks"},{file:"lib/puck/config.tsx",label:"Headline",why:"fixture: renamed long ago"}]},d.check),
|
|
1
|
+
import{failedChecks as $,ruleChecks as x}from"./rule.js";import{isPanelFile as v,readLabels as w,readPanelLabels as C}from"./support/label-extraction.js";import{registryFiles as T}from"./support/registry-source.js";const p=["kicker","tagline","headline","blurb","description","copy","paragraph","photo","picture","cta","kind","class","grayscale","greyscale","colour","align","align items","text alignment","centre","catalogue","behaviour","labelled","stylised","stack","container","gutter","margin","site width","max width","contained","boxed","header offset","clearance"],j=o=>o.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),R=p.map(o=>({word:o,pattern:new RegExp(`\\b${j(o)}s?\\b`)}));function H(o){return o.split(/\(| — /)[0].trim()}function k(o,c){const t=H(o).toLowerCase(),a=[];for(const{word:d,pattern:l}of R)l.test(t)&&a.push(`N10.1 "${d}"`);/^title\b/.test(t)&&!c&&a.push("N10.2 Title at section level"),/^heading\b/.test(t)&&c&&a.push("N10.2 Heading at item level");const e=/\b(button|link)\s+(text|label|href|url)$/.exec(t);return e&&a.push(`N10.3 "${e[1]} ${e[2]}"`),t==="text"&&a.push("N10.4 bare Text"),a}function N(o,c,t){const a=T(o),e=o.filter(v),d=new Map,l=[],h=(n,r)=>{for(const u of n){const y=r(u.text);d.set(u.path,y.length);for(const m of y)l.push({...m,file:u.path,why:k(m.label,m.item)})}};h(a,w),h(e,C);for(const[n,r]of Object.entries(c.floors)){const u=d.get(n);t(`${n} yields >= ${r} labels (found ${u??"none: not read"})`,u!==void 0&&u>=r,u===void 0?"it has a floor but was not read — it dropped out of the panels and the registry":"the reader is broken, not the panel")}const s=a.reduce((n,r)=>n+(d.get(r.path)??0),0);t(`the block registry yields >= ${c.minRegistryLabels} labels across ${a.length} files (found ${s})`,s>=c.minRegistryLabels,"the reader is broken, not the panel");const g=l.filter(n=>n.why.length>0),f=n=>c.parked.some(r=>r.file===n.file&&r.label===n.label),i=g.filter(n=>!f(n)),b=l.filter(n=>n.item).length;t(`every field label obeys N10.1–N10.4 (${l.length} read across ${a.length+e.length} files: ${l.length-b} section-level, ${b} item-level)`,i.length===0,`${i.length} break doc/naming.md N10:
|
|
2
|
+
`+i.map(n=>` ${n.file}:${n.line} ${n.item?"[item] ":"[section]"} ${JSON.stringify(n.label)} — ${n.why.join(", ")}`).join(`
|
|
3
|
+
`));for(const n of c.parked)t(`PARKED ${n.file} ${JSON.stringify(n.label)} still breaks N10 — ${n.why}`,g.some(r=>r.file===n.file&&r.label===n.label),"it no longer breaks N10 (renamed, or its row deleted) — take it out of the list");return{read:l,live:i}}function L(o,c){const{check:t,report:a}=x("label vocabulary");t(`N10.1 forbids ${p.length} words, each written once, lower-case and trimmed: ${[...p].sort().join(", ")}`,p.length>0&&new Set(p).size===p.length&&p.every(e=>e===e.trim().toLowerCase()&&e.length>0),JSON.stringify(p));{const e=(l,h=!1)=>k(l,h),d=p.filter(l=>{const h=`N10.1 "${l}"`,s=l[0].toUpperCase()+l.slice(1);return!e(s).includes(h)||!e(`Card ${l}s`).includes(h)});t("N10.1 catches every word on the list, alone and as a qualified plural",d.length===0,d.join(", ")),t("N10.1 …but not inside a longer word",e("Copyright").length===0&&e("Classic layout").length===0&&e("Stacking order").length===0,JSON.stringify([e("Copyright"),e("Classic layout"),e("Stacking order")])),t("N10.2 a section's label may not start Title, an item's may not start Heading",e("Title").includes("N10.2 Title at section level")&&e("Heading",!0).includes("N10.2 Heading at item level")),t("N10.2 …and each is right at its own level",e("Heading").length===0&&e("Title",!0).length===0&&e("Card title").length===0&&e("Section heading",!0).length===0),t("N10.3 nothing follows `button` or `link`",e("Button text").includes('N10.3 "button text"')&&e("Submit button label").includes('N10.3 "button label"')&&e("Link href").includes('N10.3 "link href"')&&e("Archive link URL").includes('N10.3 "link url"')),t("N10.3 …and the pair itself passes",e("Button").length===0&&e("Button link").length===0&&e("Archive link").length===0),t("N10.4 a bare Text fails; Alt text is a name",e("Text").includes("N10.4 bare Text")&&e("Alt text").length===0),t("the name is cut at a hint tail, so a hint cannot corrupt the verdict",e("Body (blank line = new paragraph)").length===0&&e("Copy — the words under it").includes('N10.1 "copy"'))}{const e=[{path:"lib/puck/config.tsx",text:['heading: plainText("Heading"),','eyebrow: plainText("Kicker"),','title: plainText("Title"),','cards: { type: "array", label: "Cards",'," arrayFields: {",' heading: plainText("Heading"),',' title: plainText("Card title"),'," },","},",'layout: { type: "select", label: "Layout",',' options: [{ label: "Centre", value: "center" }] },'].join(`
|
|
4
|
+
`)},{path:"components/FixturePanel.tsx",text:['import { SettingsField } from "efficient-easel/panel";',"const items = [",' { label: "Copy", onClick: () => copy() },',"];","const rows = [",' { key: "photo", label: "Photo", group: "media" },',"];","const tabs: SettingsTabItem[] = [",' { id: "text", label: "Text" },',"];",'<SettingsField label="Colour" />'].join(`
|
|
5
|
+
`)},{path:"components/HandRolled.tsx",text:'<div className="sf-field"><Row label="Gutter" /></div>'},{path:"components/ShopperForm.tsx",text:'<Field label="Description" />'}],d=x("fixture"),{read:l,live:h}=N(e,{floors:{"components/FixturePanel.tsx":5,"components/HandRolled.tsx":1,"components/Moved.tsx":1},minRegistryLabels:8,parked:[{file:"components/FixturePanel.tsx",label:"Colour",why:"fixture: still breaks"},{file:"lib/puck/config.tsx",label:"Headline",why:"fixture: renamed long ago"}]},d.check),s=$(d.report()).map(i=>i.name),g=i=>s.some(b=>b.startsWith(i)),f=l.map(i=>i.label);t("the fixture reads the registry's fields and the panels' rows, and no option, action, tab or shopper's form",f.join(" · ")==="Heading · Kicker · Title · Cards · Heading · Card title · Layout · Photo · Colour · Gutter",f.join(" · ")),t("…and the breaches are the ones it wrote, the parked one waived",h.map(i=>`${i.label}=${i.why.join("+")}`).join(" · ")==='Kicker=N10.1 "kicker" · Title=N10.2 Title at section level · Heading=N10.2 Heading at item level · Photo=N10.1 "photo" · Gutter=N10.1 "gutter"',JSON.stringify(h.map(i=>[i.label,i.why]))),t("a file under its floor goes red",g("components/FixturePanel.tsx yields")&&!g("components/HandRolled.tsx yields"),s.join(" | ")),t("a floored file that is not read goes red",g("components/Moved.tsx yields"),s.join(" | ")),t("a registry under its floor goes red",g("the block registry yields"),s.join(" | ")),t("a PARKED label that no longer breaks N10 goes red, and one that still does is waived",g('PARKED lib/puck/config.tsx "Headline"')&&!g('PARKED components/FixturePanel.tsx "Colour"'),s.join(" | ")),t("the verdict goes red on a live breach",g("every field label obeys"),s.join(" | ")),t("…and nothing else did",s.length===5,s.join(" | "))}return N(o,c,t),a()}export{p as NEVER_WORDS,L as labelVocabulary,k as vocabularyBreaches};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import{ruleChecks as k}from"./rule.js";import{blockSource as E,registryFiles as S}from"./support/registry-source.js";const x="lib/puck/controls.tsx",j=["slider","select","segmented","textList"];function y(c){return/icon=\{<([A-Z][A-Za-z0-9]*Icon)\s*\/>\}/.exec(c)?.[1]??/<([A-Z][A-Za-z0-9]*Icon)\s*\/>/.exec(c)?.[1]??/<FieldGlyph noun="([a-z]+)"/.exec(c)?.[1]??"none"}function I(c){const s=new Map;for(const l of c)l.text.split(`
|
|
2
2
|
`).forEach((t,p)=>{const a=t.trim();if(!(a.startsWith("//")||a.startsWith("*")||a.startsWith("/*")))for(const h of j){if(new RegExp(`^(export )?function ${h}\\(`).test(a))continue;const d=t.match(new RegExp(`(^|[^.\\w])${h}\\(\\s*"([^"]+)"`));d&&s.set(d[2],[...s.get(d[2])??[],{file:l,line:p+1,builder:h}])}});return s}function R(c){const s=[];for(const l of c)l.text.split(`
|
|
3
3
|
`).forEach((t,p)=>{const a=t.match(/<SettingsField\s+label="([^"]+)"/);a&&s.push({file:l.path,line:p+1,label:a[1].trim(),glyph:y(t)})});return s}function W(c,s,l){const{check:t,report:p}=k("mirrored rows agree"),a=l.mirrored??[],h=l.sameWord??[];{const e={path:"fixture.tsx",text:["export function select(label: string) {}",'const a = select("Product", <BoxIcon />, options);','// select("In a comment", <BoxIcon />)','const b = usePreviewParts().select("Not a row");','const c = <SettingsField label="Product" icon={<BoxIcon />}>{x}</SettingsField>;'].join(`
|
|
4
|
-
`)},o=I([e]),n=R([e]);t("the builder-label reader finds a call, and not its declaration, a comment or another object's method",[...o.keys()].join()==="Product"&&o.get("Product")?.[0].line===2,JSON.stringify([...o.keys()])),t("the row reader finds a hand-drawn row and the glyph it wears",n.length===1&&n[0].label==="Product"&&n[0].glyph==="BoxIcon",JSON.stringify(n))}const d=s.find(e=>e.path===x);for(const e of a){const o=d?new RegExp(`export const ${e.constant} = \\{[\\s\\S]*?\\} as const;`).exec(d.text):null;t(`${e.constant} is declared in ${x}`,o!==null),o&&(t("
|
|
4
|
+
`)},o=I([e]),n=R([e]);t("the builder-label reader finds a call, and not its declaration, a comment or another object's method",[...o.keys()].join()==="Product"&&o.get("Product")?.[0].line===2,JSON.stringify([...o.keys()])),t("the row reader finds a hand-drawn row and the glyph it wears",n.length===1&&n[0].label==="Product"&&n[0].glyph==="BoxIcon",JSON.stringify(n))}const d=s.find(e=>e.path===x);for(const e of a){const o=d?new RegExp(`export const ${e.constant} = \\{[\\s\\S]*?\\} as const;`).exec(d.text):null;t(`${e.constant} is declared in ${x}`,o!==null),o&&(t("…and carries the label",/label:\s*"/.test(o[0]),o[0].slice(0,120)),t("…and carries the glyph",/icon:\s*</.test(o[0]),o[0].slice(0,120)));let n=null;try{n=E(c,s,e.declaredIn)}catch{n=null}t(`read the ${e.declaredIn} block`,n!==null,`no block "${e.declaredIn}" among the files handed in`);const r=s.find(i=>i.path===e.drawnIn)?.text??null;t(`read ${e.drawnIn}`,r!==null);for(const[i,$]of[[`the ${e.declaredIn} block`,n],[e.drawnIn,r]])$!==null&&(t(`${i} spreads ${e.constant}`,new RegExp(`${e.constant}\\.(label|icon)`).test($)),t(`…and no longer hard-codes "${e.row}"`,!new RegExp(`"${e.row}"`).test($),`${i} still contains the literal`))}t(`the mirrored pairs were checked (${a.length}, at least ${l.floors.pairs}; an empty sweep proves nothing)`,a.length>=l.floors.pairs);const f=I([...S(s),...d?[d]:[]]),w=s.filter(e=>/^components\/[^/]+\.tsx$/.test(e.path)),u=R(w),g=[],m=[];for(const e of u){if(!f.has(e.label))continue;if(h.includes(e.label)){m.push({at:`${e.file}:${e.line}`,label:e.label,glyph:e.glyph});continue}const o=(f.get(e.label)??[]).map(n=>`${n.builder} @ ${n.file.path}:${n.line}`).join(" · ");g.push(`${e.file}:${e.line} "${e.label}" — also ${o}`)}t(`the builder-label scan read something (${f.size} labels, at least ${l.floors.labels})`,f.size>=l.floors.labels),t(`the component scan read something (${u.length} hand-drawn rows in ${w.length} component files, at least ${l.floors.panelRows})`,u.length>=l.floors.panelRows),t("no NEW hard-coded mirror",g.length===0,`
|
|
5
5
|
${g.join(`
|
|
6
|
-
`)}`);const b=h.filter(e=>!f.has(e));t("no same-word entry has outlived its subject",b.length===0,b.join("
|
|
6
|
+
`)}`);const b=h.filter(e=>!f.has(e));t("no same-word entry has outlived its subject",b.length===0,b.join(" · "));for(const e of h){const o=new Set,n=[];for(const r of m.filter(i=>i.label===e))o.add(r.glyph),n.push(`${r.at} → ${r.glyph}`);for(const r of f.get(e)??[]){const i=y(r.file.text.split(`
|
|
7
7
|
`).slice(r.line-1,r.line+6).join(`
|
|
8
|
-
`));o.add(i),n.push(`${r.file.path}:${r.line}
|
|
8
|
+
`));o.add(i),n.push(`${r.file.path}:${r.line} → ${i}`)}t(`"${e}" draws ONE picture wherever it is declared`,o.size===1,`${o.size} different:
|
|
9
9
|
${n.join(`
|
|
10
|
-
`)}`),t(
|
|
10
|
+
`)}`),t(`…and "${e}" was actually found in both places`,n.length>=2,`${n.length}`)}return p()}export{y as glyphOf,W as mirroredRowsAgree};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import{ruleChecks as g}from"./rule.js";function h(n,a){const s=n.createSourceFile(a.path,a.text,n.ScriptTarget.Latest,!0,n.ScriptKind.TSX),l=[],o=t=>{if(n.isJsxText(t)){const r=t.getFullText(s),e=t.getFullStart();r.split(`
|
|
2
2
|
`).forEach((m,c)=>{const i=m.trim();if(!(i.startsWith("//")||i.startsWith("/*")||i.startsWith("*/"))||i.startsWith("//")&&/^\/\/[A-Za-z0-9.-]+\.[A-Za-z]{2,}\//.test(i))return;const f=e+r.split(`
|
|
3
3
|
`).slice(0,c).join(`
|
|
4
|
-
`).length+(c>0?1:0),{line:x}=s.getLineAndCharacterOfPosition(f);l.push({file:a.path,line:x+1,text:i.length>90?`${i.slice(0,90)}
|
|
5
|
-
`)});s("the reader flags a `//` line between tags, and neither a braced comment nor a protocol-relative URL",o.length===1&&o[0].line===3,JSON.stringify(o));const t=a.filter(e=>e.path.endsWith(".tsx"));s(`read the .tsx files (${t.length})`,t.length>0,"found no .tsx files to scan
|
|
4
|
+
`).length+(c>0?1:0),{line:x}=s.getLineAndCharacterOfPosition(f);l.push({file:a.path,line:x+1,text:i.length>90?`${i.slice(0,90)}…`:i})})}n.forEachChild(t,o)};return o(s),l}function d(n,a){const{check:s,report:l}=g("no comment in JSX"),o=h(n,{path:"fixture.tsx",text:["const a = ("," <div>"," // this line is printed on the page"," {/* this one is a comment */}",' <a href="x">//cdn.example.com/x.js</a>'," </div>",");"].join(`
|
|
5
|
+
`)});s("the reader flags a `//` line between tags, and neither a braced comment nor a protocol-relative URL",o.length===1&&o[0].line===3,JSON.stringify(o));const t=a.filter(e=>e.path.endsWith(".tsx"));s(`read the .tsx files (${t.length})`,t.length>0,"found no .tsx files to scan — this rule is not measuring anything");const r=t.flatMap(e=>h(n,e));return s(`no \`//\` or \`/* */\` comment line sits between JSX tags (${t.length} .tsx files)`,r.length===0,`${r.length} line(s) React renders as TEXT on the page — wrap them in {/* ... */}:
|
|
6
6
|
`+r.map(e=>` ${e.file}:${e.line} ${e.text}`).join(`
|
|
7
7
|
`)),l()}export{h as jsxCommentLines,d as noCommentInJsx};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import{isRegistryPath as m,registryFiles as x}from"./support/registry-source.js";import{ruleChecks as y}from"./rule.js";const d="lib/puck/controls.tsx",u=/doc\/naming\.md|N10\.\d|owner|GG|SS seat|editor-shell|\d{4}-\d{2}-\d{2}/;function w(r){const e=x(r),o=r.filter(n=>n.path===d&&!m(n.path)),h=r.filter(n=>/^components\/[^/]+\.tsx$/.test(n.path));return[...e,...o,...h]}function f(r){const e=r.text,o=[],h=/noGlyph\(/g;let n;for(;n=h.exec(e);){const l=e.lastIndexOf(`
|
|
2
2
|
`,n.index)+1,s=e.slice(l,n.index);if(/^\s*(\/\/|\*|\/\*)/.test(s)||(s.match(/"/g)??[]).length%2===1||/\bfunction\s+$/.test(s))continue;let c=1,a=n.index+n[0].length;for(;a<e.length&&c>0;a++)e[a]==="("?c+=1:e[a]===")"&&(c-=1);const i=[...e.slice(n.index+n[0].length,a-1).matchAll(/"((?:[^"\\]|\\.)*)"/g)].map(g=>g[1]).join(""),p=e.slice(0,n.index).split(`
|
|
3
|
-
`).length;o.push({at:`${r.path}:${p}`,reason:i})}return o}function $(r,e){const{check:o,report:h}=y("no-glyph rows");{const t=f({path:"fixture.tsx",text:["/**"," * `noGlyph(reason)`
|
|
4
|
-
`)});o("the reader finds the two calls, and neither the docblock, the declaration, the string nor the comment",t.length===2&&t[0].at==="fixture.tsx:7"&&t[1].at==="fixture.tsx:12",JSON.stringify(t)),o("
|
|
3
|
+
`).length;o.push({at:`${r.path}:${p}`,reason:i})}return o}function $(r,e){const{check:o,report:h}=y("no-glyph rows");{const t=f({path:"fixture.tsx",text:["/**"," * `noGlyph(reason)` — the docblock names it; that is not a use."," */","export function noGlyph(reason: string) {",' if (!reason) throw new Error("noGlyph() needs a reason");',"}",'const a = select("Process", noGlyph(',` "A facet's options grow out of the CATALOGUE — owner, " +`,' "2026-08-29.",',"));",'// noGlyph("in a comment") is not a use','const b = noGlyph("");'].join(`
|
|
4
|
+
`)});o("the reader finds the two calls, and neither the docblock, the declaration, the string nor the comment",t.length===2&&t[0].at==="fixture.tsx:7"&&t[1].at==="fixture.tsx:12",JSON.stringify(t)),o("…reads a concatenated reason whole, so it can be checked for a source",t[0]?.reason==="A facet's options grow out of the CATALOGUE — owner, 2026-08-29."&&u.test(t[0].reason),JSON.stringify(t[0])),o("…and reads a bare call as no reason",t[1]?.reason==="",JSON.stringify(t[1]))}const n=w(r),l=n.reduce((t,i)=>t+i.text.length,0);o(`the scan read the files it meant to (${n.length} files, ${l} characters, at least ${e.minChars})`,l>=e.minChars,"the registry, lib/puck/controls.tsx or components/ moved — a scan that reads nothing finds no exceptions");const s=n.flatMap(f),c=s.filter(t=>!t.reason.trim());o("every noGlyph() states a reason",c.length===0,c.map(t=>t.at).join(" · "));const a=s.filter(t=>t.reason.trim()&&!u.test(t.reason));if(o("…and every reason names a source that can be checked",a.length===0,a.map(t=>`${t.at} — "${t.reason.slice(0,60)}"`).join(" · ")),o(`the no-glyph rows still number ${e.expected} (found ${s.length}${s.length?`: ${s.map(t=>t.at).join(", ")}`:""})`,s.length===e.expected,"a ruling was added or lost — move the number, with the ruling, in the guard that counts it"),e.controls){const t=r.find(p=>p.path===d)?.text??"";o(`read ${d}`,t.length>0,"the file noGlyph and iconOf are written in is not among the files handed in"),o(`noGlyph is still exported from ${d}`,/export function noGlyph\(/.test(t)),o("…and a marker is unwrapped rather than drawn as an empty gutter",/export function iconOf\(/.test(t)&&/isNoGlyph\(icon\) \? undefined : icon/.test(t),"SettingsField draws its icon span only when given an icon");const i=(t.match(/icon=\{iconOf\(icon\)\}/g)??[]).length;o(`…and the shared builders pass their icon through it (${i}, at least ${e.controls.buildersFloor})`,i>=e.controls.buildersFloor,`${i} builders`)}return h()}export{d as CONTROLS_FILE,$ as noGlyphRows,f as noGlyphRowsIn};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{ruleChecks as $}from"./rule.js";const n=
|
|
2
|
-
// ${e}`))),t("the reader sees
|
|
1
|
+
import{ruleChecks as $}from"./rule.js";const n=l=>l.replace(/\/\*[\s\S]*?\*\//g,"").replace(/(^|[^:])\/\/.*$/gm,"$1"),c=new RegExp('type=\\{?"color"'),i=new RegExp("<ColorPicker\\b"),h=new RegExp(`<(?:${["ColourPicker","ColourSwatchRow","ColourWindow"].join("|")})\\b`),d=new RegExp('className="[^"]*sf-(ink|colorinput)');function k(l,s){const{check:t,report:C}=$("one colour control");{const e='<input type="color" value={v} />',m="<ColorPicker value={v} onChange={f} />",w="<ColourSwatchRow value={v} onChange={f} />";t("the reader sees a native colour input",c.test(n(e))),t("…and not one a comment talks about",!c.test(n(`/* ${e} */
|
|
2
|
+
// ${e}`))),t("the reader sees the shell's ColorPicker",i.test(n(m))),t("…and tells it from efficient-easel's colour UI",!i.test(w)&&h.test(w)&&!h.test(m)),t("…and the retired classes in markup",d.test('<span className="sf-ink-row" />')&&d.test('<div className="x sf-colorinput" />'))}const r=l.filter(e=>/\.tsx?$/.test(e.path));t(`read the source (${r.length} TS/TSX files, at least ${s.minFiles})`,r.length>=s.minFiles,"fewer means the walk broke, and every check below is vacuous");const a=r.map(e=>[e.path,n(e.text)]),u=a.filter(([,e])=>c.test(e)).map(([e])=>e),o=a.filter(([,e])=>i.test(e)).map(([e])=>e),p=a.filter(([,e])=>h.test(e)).map(([e])=>e);t("§1 nothing draws efficient-easel's own colour UI — the shell's ColorPicker replaced it (harness UI-24)",p.length===0,p.join(", ")),t("§1 nothing draws a native colour input — its window is the operating system's",u.length===0,u.join(", ")),s.sharedControl?(t(`read the shared control ${s.sharedControl}`,r.some(e=>e.path===s.sharedControl)),t("§1 exactly one file draws efficient-shell's ColorPicker",o.length===1,o.length===0?"NONE — the reader is broken, and every check below is vacuous":`${o.length}: ${o.join(", ")}`),t(`§1 …and it is the shared control, ${s.sharedControl}`,o[0]===s.sharedControl,o.join(", "))):t("§1 no file here draws efficient-shell's ColorPicker — the builder's shared control is the one that does",o.length===0,`${o.length}: ${o.join(", ")}`);const f=r.filter(e=>d.test(e.text)).map(e=>e.path);return t("§9 nothing draws a `sf-ink-` or `sf-colorinput` class any more",f.length===0,f.join(", ")),C()}export{k as oneColourControl,n as stripComments};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{ruleChecks as x}from"./rule.js";function S(c,d){const i=c.createSourceFile(d.path,d.text,c.ScriptTarget.Latest,!0,c.ScriptKind.TSX),n=[];let g=0;const h=s=>i.getLineAndCharacterOfPosition(s.getStart(i)).line+1,u=s=>{if(c.isJsxOpeningElement(s)||c.isJsxSelfClosingElement(s)){const a=s.tagName.getText(i);a==="select"&&n.push({file:d.path,line:h(s),what:"R1 raw <select>
|
|
1
|
+
import{ruleChecks as x}from"./rule.js";function S(c,d){const i=c.createSourceFile(d.path,d.text,c.ScriptTarget.Latest,!0,c.ScriptKind.TSX),n=[];let g=0;const h=s=>i.getLineAndCharacterOfPosition(s.getStart(i)).line+1,u=s=>{if(c.isJsxOpeningElement(s)||c.isJsxSelfClosingElement(s)){const a=s.tagName.getText(i);a==="select"&&n.push({file:d.path,line:h(s),what:"R1 raw <select> — use efficient-easel's FieldSelect"}),(a==="FieldSelect"||/^FieldSelect</.test(a))&&g++}if(c.isCallExpression(s)){const a=s.expression.getText(i),r=s.arguments[0];(a==="h"||/(^|\.)createElement$/.test(a))&&r&&c.isStringLiteralLike(r)&&r.text==="select"&&n.push({file:d.path,line:h(s),what:`R2 ${a}("select") — use efficient-easel's FieldSelect`})}c.forEachChild(s,u)};return u(i),{selects:n,fieldSelect:g}}function v(c,d,i){const{check:n,report:g}=x("one dropdown"),h=i.notEditor??{},u=i.ownedNow??{},s=i.until??{};{const e=["const a = <select value={v} onChange={f}><option>x</option></select>;",'const b = h("select", { value: v });','const c = React.createElement("select", null);',"// a comment about <select> is not a use","const d = <FieldSelect value={v} options={o} onChange={f} />;","const e = <FieldSelect<string> value={v} options={o} onChange={f} />;",'const s = "<select>"; const t = <Select />;'].join(`
|
|
2
2
|
`),{selects:t,fieldSelect:f}=S(c,{path:"fixture.tsx",text:e}),l=t.map(o=>o.line);n("R1 flags a JSX <select>",t.some(o=>o.line===1&&o.what.startsWith("R1"))),n('R2 flags h("select")',t.some(o=>o.line===2&&o.what.startsWith("R2"))),n('R2 flags React.createElement("select")',t.some(o=>o.line===3&&o.what.startsWith("R2"))),n("a comment, a string and a <Select> component are not uses",!l.includes(4)&&!l.includes(7)),n("FieldSelect passes and is counted, generic form too",!l.includes(5)&&!l.includes(6)&&f===2)}const a=d.filter(e=>/\.tsx?$/.test(e.path)&&!e.path.endsWith(".d.ts")),r=new Map;let w=0;for(const e of a){const t=S(c,e);t.selects.length&&r.set(e.path,t.selects),w+=t.fieldSelect}const p=[];for(const[e,t]of r)e in h||e in u||e in s||p.push(...t);n("no raw <select> in editor code",p.length===0,`${p.length} hit(s):
|
|
3
3
|
`+p.map(e=>`${e.file}:${e.line} ${e.what}`).join(`
|
|
4
|
-
`));for(const[e,[t,f]]of Object.entries(h)){const l=r.get(e)?.length??0;n(`shopper select still real and still ${t}: ${e} (${f})`,l===t,`found ${l}
|
|
4
|
+
`));for(const[e,[t,f]]of Object.entries(h)){const l=r.get(e)?.length??0;n(`shopper select still real and still ${t}: ${e} (${f})`,l===t,`found ${l} — fix or remove the entry`)}for(const[e,[t,f]]of Object.entries(u)){const l=r.get(e)?.length??0;n(`left for another card, exactly ${t}: ${e} (${f})`,l===t,l>t?`found ${l} — a NEW raw <select> in a file that is being replaced; use FieldSelect`:`found ${l} — that card has landed; remove this entry`)}for(const[e,[t,f,l]]of Object.entries(s)){const o=r.get(e)?.length??0;n(`waiting, exactly ${t}: ${e} (until ${f})`,o===t,`found ${o} — swapped? remove this entry`);let m=!1;try{m=l()}catch($){n(`could read whether ${e}'s wait is over`,!1,$.message);continue}n(`${e} is still waiting (installed efficient-easel lacks it)`,!m,`the installed efficient-easel now has ${f} — swap ${e} to FieldSelect and remove its UNTIL entry`)}return n(`walked the editor source (${a.length} TS/TSX files, at least ${i.minFiles})`,a.length>=i.minFiles),n(`found >= ${i.fieldSelectFloor} <FieldSelect> uses (found ${w})`,w>=i.fieldSelectFloor,"the builder + the swapped rows; fewer means a row went back to a raw <select> or the reader broke"),g()}export{v as oneDropdown};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import{ruleChecks as $}from"./rule.js";const y=s=>s.replace(/\/\*[\s\S]*?\*\//g,"").replace(/(^|[^:"'`])\/\/[^\n]*/g,"$1").replace(/\{\/\*[\s\S]*?\*\/\}/g,""),v=[["Move up / Move down buttons",/aria-label=["'{`]*Move (up|down)/],['"+ Add
|
|
2
|
-
<button type="button" style={iconBtn} aria-label="Move up" title="Move up" onClick={() => swap(1, 0)}
|
|
1
|
+
import{ruleChecks as $}from"./rule.js";const y=s=>s.replace(/\/\*[\s\S]*?\*\//g,"").replace(/(^|[^:"'`])\/\/[^\n]*/g,"$1").replace(/\{\/\*[\s\S]*?\*\/\}/g,""),v=[["Move up / Move down buttons",/aria-label=["'{`]*Move (up|down)/],['"+ Add …" buttons',/>\s*\+ Add [A-Za-z]|["'`]\+ Add [A-Za-z]/],["sf-textlist markup (the ticker's old list)",/["'` ]sf-textlist/],["TextListEditor",/\bTextListEditor\b/]];function M(s,h){const{check:U,report:w}=$("one editable list"),i=(t,d,o)=>{let p=!1,A=o;try{p=typeof d=="function"?d():d}catch(e){A=String(e.message??e)}return U(t,p,A)},x=`
|
|
2
|
+
<button type="button" style={iconBtn} aria-label="Move up" title="Move up" onClick={() => swap(1, 0)}>↑</button>
|
|
3
3
|
<button type="button" style={addBtn} onClick={addGroup}>+ Add group</button>
|
|
4
4
|
<div className="sf-textlist"><button className="sf-textlist-add">+ Add item</button></div>
|
|
5
5
|
export function TextListEditor() {}
|
|
6
|
-
`;for(const[t,d]of v)i(`control: the rule for ${t} catches it in old code`,d.test(y(x)));const a=s.filter(t=>t.path.endsWith(".tsx"));i(`read the editor's .tsx files (${a.length}, at least ${h.minFiles})`,a.length>=h.minFiles&&a.length>0,`only ${a.length}`);for(const[t,d]of v){const o=a.filter(p=>d.test(y(p.text))).map(p=>p.path);i(`no ${t} in the .tsx files`,o.length===0,o.join(", "))}const u=s.filter(t=>t.path.endsWith(".css"));i(`read the editor's stylesheets (${u.length}, at least ${h.minSheets})`,u.length>=h.minSheets);const r=u.filter(t=>/\.sf-textlist\b/.test(t.text.replace(/\/\*[\s\S]*?\*\//g,"")));i("no .sf-textlist rule left behind",r.length===0,r.map(t=>t.path).join(", "));const m=t=>{const d=s.find(o=>o.path===t);if(!d)throw new Error(`${t} was not among the files read`);return y(d.text)},c=h.presence??{};if(c.controls!==void 0){const t=c.controls;i(`${t} draws lines of copy with easel's ListField`,()=>/import \{[^}]*\bListField\b[^}]*\} from "efficient-easel\/fields"/.test(m(t)))}if(c.locationsPanel!==void 0){const t=c.locationsPanel;i(`the Locations panel (${t}) draws its groups and cities with ListField`,()=>(m(t).match(/<ListField\b/g)??[]).length===2)}if(c.tickerPanel!==void 0){const t=c.tickerPanel;i(`the ticker panel (${t}) draws Ticker items with the same TextListField the textList builder draws`,()=>/<TextListField\b/.test(m(t)))}return h.listModules&&E(i,h.listModules),w()}function E(s,h){const U=n=>()=>{throw new Error(`${n} does not exist`)},w=(n,l)=>n[l]??U(l),i=w(h.locationsTree,"writeGroupCities"),x=w(h.locationsTree,"writeGroupOrder"),a=w(h.controls,"aimAfterListChange"),u=(n,l)=>({name:n,group:l,groupName:""}),r=n=>n.map(l=>`${l.group}:${l.name}`).join(" "),m=(n,l,f)=>{const g=n.slice();return[g[l],g[f]]=[g[f],g[l]],g},c=(n,l,f,g)=>{const b=l.slice();return[b[f],b[f+g]]=[b[f+g],b[f]],b.flatMap(L=>n.filter(S=>(S.group??"")===L))},t=[u("Sydney","AU"),u("Austin","US"),u("Perth","AU"),u("Miami","US"),u("Hobart","AU")],d=[0,2,4],o=d.map(n=>t[n]);s("a city one step down saves what the old
|
|
6
|
+
`;for(const[t,d]of v)i(`control: the rule for ${t} catches it in old code`,d.test(y(x)));const a=s.filter(t=>t.path.endsWith(".tsx"));i(`read the editor's .tsx files (${a.length}, at least ${h.minFiles})`,a.length>=h.minFiles&&a.length>0,`only ${a.length}`);for(const[t,d]of v){const o=a.filter(p=>d.test(y(p.text))).map(p=>p.path);i(`no ${t} in the .tsx files`,o.length===0,o.join(", "))}const u=s.filter(t=>t.path.endsWith(".css"));i(`read the editor's stylesheets (${u.length}, at least ${h.minSheets})`,u.length>=h.minSheets);const r=u.filter(t=>/\.sf-textlist\b/.test(t.text.replace(/\/\*[\s\S]*?\*\//g,"")));i("no .sf-textlist rule left behind",r.length===0,r.map(t=>t.path).join(", "));const m=t=>{const d=s.find(o=>o.path===t);if(!d)throw new Error(`${t} was not among the files read`);return y(d.text)},c=h.presence??{};if(c.controls!==void 0){const t=c.controls;i(`${t} draws lines of copy with easel's ListField`,()=>/import \{[^}]*\bListField\b[^}]*\} from "efficient-easel\/fields"/.test(m(t)))}if(c.locationsPanel!==void 0){const t=c.locationsPanel;i(`the Locations panel (${t}) draws its groups and cities with ListField`,()=>(m(t).match(/<ListField\b/g)??[]).length===2)}if(c.tickerPanel!==void 0){const t=c.tickerPanel;i(`the ticker panel (${t}) draws Ticker items with the same TextListField the textList builder draws`,()=>/<TextListField\b/.test(m(t)))}return h.listModules&&E(i,h.listModules),w()}function E(s,h){const U=n=>()=>{throw new Error(`${n} does not exist`)},w=(n,l)=>n[l]??U(l),i=w(h.locationsTree,"writeGroupCities"),x=w(h.locationsTree,"writeGroupOrder"),a=w(h.controls,"aimAfterListChange"),u=(n,l)=>({name:n,group:l,groupName:""}),r=n=>n.map(l=>`${l.group}:${l.name}`).join(" "),m=(n,l,f)=>{const g=n.slice();return[g[l],g[f]]=[g[f],g[l]],g},c=(n,l,f,g)=>{const b=l.slice();return[b[f],b[f+g]]=[b[f+g],b[f]],b.flatMap(L=>n.filter(S=>(S.group??"")===L))},t=[u("Sydney","AU"),u("Austin","US"),u("Perth","AU"),u("Miami","US"),u("Hobart","AU")],d=[0,2,4],o=d.map(n=>t[n]);s("a city one step down saves what the old ↓ saved",()=>r(i(t,"AU",[o[1],o[0],o[2]]))===r(m(t,0,2))),s("a city one step up saves what the old ↑ saved",()=>r(i(t,"AU",[o[0],o[2],o[1]]))===r(m(t,4,2))),s("a city dragged from first to last leaves every other group's cards in their places",()=>{const n=i(t,"AU",[o[1],o[2],o[0]]);return n[1]===t[1]&&n[3]===t[3]&&n.length===t.length}),s("…and puts the group's own cards in the new order",()=>{const n=i(t,"AU",[o[1],o[2],o[0]]);return r(d.map(l=>n[l]))==="AU:Perth AU:Hobart AU:Sydney"});const p={...u("","AU")};s('"+ Add city" at the end saves what the old button saved (the new card last)',()=>r(i(t,"AU",[...o,p]))===r([...t,p])),s("a group one step down saves what the old ↓ saved",()=>r(x(t,["US","AU"],()=>[]))===r(c(t,["AU","US"],0,1)));const A=u("","Group 3");s('a new group gets its first city ("+ Add group" put it last)',()=>r(x(t,["AU","US","Group 3"],n=>n==="Group 3"?[A]:[]))===r([...c(t,["US","AU"],0,1),A]));const e=[{text:"a"},{text:"b"},{text:"c"}];s("typing in the aimed line keeps the aim",()=>a(e,[e[0],{text:"bb"},e[2]],1)===1),s("taking out a line BELOW the aimed one keeps the aim",()=>a(e,[e[0],e[1]],1)===1),s("taking out the aimed line lets go",()=>a(e,[e[0],e[2]],1)===null),s("taking out a line ABOVE the aimed one lets go (its box now holds other words)",()=>a(e,[e[1],e[2]],1)===null),s("a line added at the end keeps the aim",()=>a(e,[...e,{text:""}],1)===1),s("a line added above the aimed one lets go",()=>a(e,[e[0],{text:""},e[1],e[2]],1)===null),s("a move lets go (the caret going with the line aims again)",()=>a(e,[e[1],e[0],e[2]],1)===null),s("nothing aimed stays nothing aimed",()=>a(e,[e[1],e[0]],null)===null)}export{M as oneEditableList};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import{ruleChecks as d}from"./rule.js";function s(i){return i.includes('role="listbox"')||i.includes('type="checkbox"')&&/role="group"/.test(i)&&/set\w*Open\(/.test(i)}function u(i,o){const{check:t,report:c}=d("one editor list"),n=o.kept??{};t("a listbox is a list",s('<ul role="listbox">')),t("tick boxes in a group the file opens itself are a list",s(`const [o, setOpen] = useState(false);
|
|
2
2
|
<div role="group"><input type="checkbox" /></div>
|
|
3
|
-
setOpen(true);`)),t("a lone tick box is not a list",!s('<input type="checkbox" />')),t("FieldSelect is not a list of the file's own",!s("<FieldSelect options={o} multiple inline />"));const l=i.filter(e=>e.path.endsWith(".tsx"));t(`read the source (${l.length} .tsx files, at least ${o.minFiles})`,l.length>=o.minFiles,"fewer means the walk broke, and the census is vacuous");const r=l.filter(e=>s(e.text)).map(e=>e.path);for(const[e,p]of Object.entries(n))t(`the kept list is still real: ${e} (${p})`,r.includes(e),"it no longer draws a list
|
|
3
|
+
setOpen(true);`)),t("a lone tick box is not a list",!s('<input type="checkbox" />')),t("FieldSelect is not a list of the file's own",!s("<FieldSelect options={o} multiple inline />"));const l=i.filter(e=>e.path.endsWith(".tsx"));t(`read the source (${l.length} .tsx files, at least ${o.minFiles})`,l.length>=o.minFiles,"fewer means the walk broke, and the census is vacuous");const r=l.filter(e=>s(e.text)).map(e=>e.path);for(const[e,p]of Object.entries(n))t(`the kept list is still real: ${e} (${p})`,r.includes(e),"it no longer draws a list — remove the stale entry");const a=r.filter(e=>!(e in n));return t("no second editor list: every file that draws a list of its own is a kept one",a.length===0,`a second editor list: ${a.join(", ")} — draw it with efficient-easel's FieldSelect`),c()}export{s as drawsList,u as oneEditorList};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{ruleChecks as y}from"./rule.js";import{isPanelFile as b,readJsxLabels as m,readLabels as f,readPanelLabels as u,readRowLabels as w}from"./support/label-extraction.js";import{registryFiles as L,registryText as $}from"./support/registry-source.js";const F=/label:\s*"(?:[^"\\]|\\.)*"\s*,\s*onClick:/g;function p(e){const s=new Set;return[...f(e),...w(e),...m(e)].filter(t=>{const l=`${t.line}:${t.label}`;return s.has(l)?!1:(s.add(l),!0)})}function g(e){return{lost:p(e).length-u(e).length,actions:(e.match(F)??[]).length}}function k(e,s,t){const l=o=>s.find(a=>a.path===o)?.text,i=[...t.stylesheets,t.locationsRows],r=i.filter(o=>l(o)===void 0);e(`read the files drawn round the screen (${i.length-r.length} of ${i.length})`,r.length===0&&t.stylesheets.length>0,`not among the files handed in: ${r.join(", ")||"no stylesheet named"}
|
|
2
|
-
`),d=u(a).map(h=>h.label);t("
|
|
1
|
+
import{ruleChecks as y}from"./rule.js";import{isPanelFile as b,readJsxLabels as m,readLabels as f,readPanelLabels as u,readRowLabels as w}from"./support/label-extraction.js";import{registryFiles as L,registryText as $}from"./support/registry-source.js";const F=/label:\s*"(?:[^"\\]|\\.)*"\s*,\s*onClick:/g;function p(e){const s=new Set;return[...f(e),...w(e),...m(e)].filter(t=>{const l=`${t.line}:${t.label}`;return s.has(l)?!1:(s.add(l),!0)})}function g(e){return{lost:p(e).length-u(e).length,actions:(e.match(F)??[]).length}}function k(e,s,t){const l=o=>s.find(a=>a.path===o)?.text,i=[...t.stylesheets,t.locationsRows],r=i.filter(o=>l(o)===void 0);e(`read the files drawn round the screen (${i.length-r.length} of ${i.length})`,r.length===0&&t.stylesheets.length>0,`not among the files handed in: ${r.join(", ")||"no stylesheet named"} — every clause below is vacuous without them`);const n=l(t.locationsRows)??"",c=t.stylesheets.filter(o=>(l(o)??"").includes("sf-paste-hint"));e("§1d no stylesheet of the editor's draws a second toast",c.length===0,`sf-paste-hint in ${c.join(", ")}`),e("§3 the Locations rows are where this reads them",/selectLocationsFocus\(si, \{ level: "group"/.test(n)&&/selectLocationsFocus\(si, \{ level: "city"/.test(n)),e("§3 a Locations group row is not addressed as a node",!/selectLocationsFocus\(si, \{ level: "group"[\s\S]{0,200}data-node-id/.test(n)&&!/data-node-id/.test(n)),e("§3 a Locations city row is not addressed as a node",!/selectLocationsFocus\(si, \{ level: "city"[\s\S]{0,200}data-node-id/.test(n))}function P(e,s){const{check:t,report:l}=y("one right click");s.editor&&k(t,e,s.editor);{const a=["const items = [",' { label: "Copy", onClick: () => copy() },',"];","const rows = [",' { key: "heading", label: "Heading", group: "text" },',"];"].join(`
|
|
2
|
+
`),d=u(a).map(h=>h.label);t("§4 the action rule drops an action's label and keeps a field's",d.length===1&&d[0]==="Heading"&&g(a).lost===1&&g(a).actions===1,JSON.stringify({kept:d,...g(a)}))}const i=e.filter(b);t(`§4 read the panel files (${i.length}, at least ${s.minPanelFiles})`,i.length>=s.minPanelFiles,"fewer means a panel stopped drawing a row or the predicate broke");const r=[];for(const a of i){const{lost:d,actions:h}=g(a.text);d>h&&r.push(`${a.path} (${d}, ${h} of them actions)`)}t("§4 no panel file loses a label to the action rule beyond its actions",r.length===0,r.join(", ")+" — a real panel dropping out of the vocabulary guard is the failure this rule must not cause");const n=L(e),c=$(e),o=f(c).length;return t(`§4 read the block registry (${n.length} files, ${o} labels)`,n.length>0,"no registry file among the files handed in"),t("§4 the inspector's own labels are read by a path the rule cannot reach",n.length>0&&(p(c).length===u(c).length||o>=s.registryLabelFloor),`the registry goes through readLabels, not readPanelLabels (${o} read, floor ${s.registryLabelFloor})`),l()}export{P as oneRightClick};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{ruleChecks as x}from"./rule.js";function S(i,o){const n=i.createSourceFile(o.path,o.text,i.ScriptTarget.Latest,!0,i.ScriptKind.TSX),s=[],f=[],a=t=>n.getLineAndCharacterOfPosition(t.getStart(n)).line+1,r=t=>{if(i.isJsxOpeningElement(t)||i.isJsxSelfClosingElement(t)){let h=!1,g="";for(const e of t.attributes.properties){if(!i.isJsxAttribute(e))continue;const c=e.name.getText(n),m=e.initializer,w=m?m.getText(n):"";c==="role"&&/["'`]switch["'`]/.test(w)&&(h=!0),c==="className"&&(g=w)}s.push({name:t.tagName.getText(n),line:a(t),roleSwitch:h,classText:g})}if(i.isPropertyAssignment(t)){const h=t.name.getText(n).replace(/["']/g,""),g=t.initializer.getText(n);h==="role"&&/^["'`]switch["'`]$/.test(g)&&s.push({name:"(createElement)",line:a(t),roleSwitch:!0,classText:""}),h==="className"&&f.push({line:a(t),text:g})}i.forEachChild(t,r)};return r(n),{tags:s,classStrings:f}}function p(i,o,n){const{tags:s,classStrings:f}=S(i,o),a=[];let r=0;for(const t of s)t.name==="SettingsSwitch"&&r++,t.name==="ToggleSwitch"&&a.push({file:o.path,line:t.line,what:"R1 <ToggleSwitch>
|
|
1
|
+
import{ruleChecks as x}from"./rule.js";function S(i,o){const n=i.createSourceFile(o.path,o.text,i.ScriptTarget.Latest,!0,i.ScriptKind.TSX),s=[],f=[],a=t=>n.getLineAndCharacterOfPosition(t.getStart(n)).line+1,r=t=>{if(i.isJsxOpeningElement(t)||i.isJsxSelfClosingElement(t)){let h=!1,g="";for(const e of t.attributes.properties){if(!i.isJsxAttribute(e))continue;const c=e.name.getText(n),m=e.initializer,w=m?m.getText(n):"";c==="role"&&/["'`]switch["'`]/.test(w)&&(h=!0),c==="className"&&(g=w)}s.push({name:t.tagName.getText(n),line:a(t),roleSwitch:h,classText:g})}if(i.isPropertyAssignment(t)){const h=t.name.getText(n).replace(/["']/g,""),g=t.initializer.getText(n);h==="role"&&/^["'`]switch["'`]$/.test(g)&&s.push({name:"(createElement)",line:a(t),roleSwitch:!0,classText:""}),h==="className"&&f.push({line:a(t),text:g})}i.forEachChild(t,r)};return r(n),{tags:s,classStrings:f}}function p(i,o,n){const{tags:s,classStrings:f}=S(i,o),a=[];let r=0;for(const t of s)t.name==="SettingsSwitch"&&r++,t.name==="ToggleSwitch"&&a.push({file:o.path,line:t.line,what:"R1 <ToggleSwitch> — use easel's SettingsSwitch"}),t.roleSwitch&&!(o.path in n)&&a.push({file:o.path,line:t.line,what:`R2 hand-rolled role="switch" on <${t.name}> — use easel's SettingsSwitch`}),/\bsf-toggle\b/.test(t.classText)&&a.push({file:o.path,line:t.line,what:"R3 sf-toggle class in className"});for(const t of f)/\bsf-toggle\b/.test(t.text)&&a.push({file:o.path,line:t.line,what:"R3 sf-toggle class in className"});return{bad:a,settingsSwitch:r}}function d(i){const o=[];return i.text.replace(/\/\*[\s\S]*?\*\//g,s=>s.replace(/[^\n]/g," ")).split(`
|
|
2
2
|
`).forEach((s,f)=>{/\.sf-toggle\b/.test(s)&&o.push({file:i.path,line:f+1,what:"R3 .sf-toggle selector"})}),o}function b(i,o,n){const{check:s,report:f}=x("one switch"),a=n.notEditor??{};{const e=["const a = <ToggleSwitch value onChange={f} />;",'const b = <button role="switch" aria-checked={x} />;','const c = <span className={`sf-toggle ${on ? "is-on" : ""}`} />;','const d = h("button", { role: "switch", className: "sf-toggle" });',"// a comment about <ToggleSwitch> is not a use",'const ok = <SettingsSwitch checked onChange={f} ariaLabel="X" />;'].join(`
|
|
3
3
|
`),{bad:c,settingsSwitch:m}=p(i,{path:"fixture.tsx",text:e},{}),w=c.map(l=>l.line);s("R1 flags <ToggleSwitch>",c.some(l=>l.line===1&&l.what.startsWith("R1"))),s('R2 flags <button role="switch">',c.some(l=>l.line===2&&l.what.startsWith("R2"))),s("R3 flags an sf-toggle className",c.some(l=>l.line===3&&l.what.startsWith("R3"))),s("R2 + R3 flag the createElement form",w.filter(l=>l===4).length===2),s("a comment is not a use",!w.includes(5)),s("SettingsSwitch passes and is counted",!w.includes(6)&&m===1);const u=d({path:"fixture.css",text:`/* .sf-toggle in a comment */
|
|
4
4
|
.sf-toggle.is-on { }
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import{ruleChecks as w}from"./rule.js";const k=/className=["'][^"']*?(?<=["'\s-])back(?=["'\s-])/,v=/^\s*(?:<span[^>]*>\s*)?(?:\{\s*)?['"`]?\s*(?:[←‹↰⟵]|Back\b|All of\b)/,C=/<[A-Z]\w*(?:Back|Chevron|ArrowLeft)\w*\b/;function b(l){const a=l.replace(/=>/g,"=:"),e=[];for(const i of a.matchAll(/<button\b[^>]*(?<!\/)>([\s\S]*?)<\/button>/g)){const c=i[1],h=i[0].slice(0,i[0].length-c.length-9);(v.test(c)||k.test(h)||k.test(c)||C.test(c))&&e.push(a.slice(0,i.index).split(`
|
|
2
2
|
`).length)}return e}const y=l=>/from\s+["']efficient-easel\/(?!render["']|markup["']|data["'])[\w-]+["']/.test(l);function $(l,a){const{check:e,report:i}=w("one up-level link"),c=a.waiting??{},h=[["Locations link",`<button type="button" style={linkBtn} onClick={() => setLocationsFocus({ level: "groups" })}>
|
|
3
|
-
|
|
4
|
-
</button>`],["Locations link, a name",'<button type="button" style={linkBtn} onClick={onBack}
|
|
3
|
+
← Locations
|
|
4
|
+
</button>`],["Locations link, a name",'<button type="button" style={linkBtn} onClick={onBack}>← {String(card.name ?? "") || "City"}</button>'],["part panel back",`<button type="button" className="sf-part-back" onClick={onBack}>
|
|
5
5
|
<BackChevron />
|
|
6
6
|
<span>Back to the page</span>
|
|
7
7
|
</button>`],["easel's old list back",`<button type="button" className="es-list-back" onClick={() => onOpen(null)}>
|
|
8
8
|
<span className="es-list-back-chev" aria-hidden="true" />
|
|
9
9
|
{label}
|
|
10
10
|
</button>`],["a chevron as text",`<button type="button" onClick={() => setOpen(null)}>
|
|
11
|
-
<span aria-hidden
|
|
11
|
+
<span aria-hidden>‹</span>
|
|
12
12
|
<span>All panels</span>
|
|
13
|
-
</button>`]];for(const[t,n]of h)e(
|
|
13
|
+
</button>`]];for(const[t,n]of h)e(`§1 the scanner sees a retired shape: ${t}`,b(n).length===1);const g=[["a select's chevron",`<button className="es-fin es-fsel-button" onClick={() => toggle()}>
|
|
14
14
|
<span className="es-fsel-chev" aria-hidden="true" />
|
|
15
|
-
</button>`],["a reorder arrow",'<button type="button" onClick={() => move(-1)}
|
|
15
|
+
</button>`],["a reorder arrow",'<button type="button" onClick={() => move(-1)}>↑</button>'],["a background class",'<button className="sf-background-chip">Fill</button>'],["a self-closing button before a real one",`<button className="sf-swatch" onClick={() => pick(t)} />
|
|
16
16
|
<span className="sf-go-back">x</span>
|
|
17
|
-
<button>Done</button>`]];for(const[t,n]of g)e(
|
|
18
|
-
`));for(const[t,{count:n,why:r}]of Object.entries(c)){const o=f[t];e(
|
|
17
|
+
<button>Done</button>`]];for(const[t,n]of g)e(`§1 the scanner leaves alone: ${t}`,b(n).length===0);const p=l.filter(t=>/\.(tsx|jsx)$/.test(t.path)&&y(t.text));e(`§2 the scan reached the editor code (${p.length} files, at least ${a.minEditorFiles})`,p.length>=a.minEditorFiles,"fewer means the walk or the editor-code test broke, and the check below is vacuous");const u=[],f={};for(const t of p){const n=b(t.text);if(t.path in c)f[t.path]=n.length;else for(const r of n)u.push(`${t.path}:${r}`)}e("§2 no hand-built back control in editor code — use InspectorUpLevel from efficient-easel/chrome",u.length===0,u.join(`
|
|
18
|
+
`));for(const[t,{count:n,why:r}]of Object.entries(c)){const o=f[t];e(`§3 ${t} still has exactly ${n} waiting (${o??"file not scanned"})`,o===n,`${r}. If they moved, delete this waiting entry.`)}for(const t of a.upLevelUses??[]){const n=l.find(s=>s.path===t.file);if(!e(`§4 read ${t.file}`,!!n)||!n)continue;const r=n.text.replace(/=>/g,"=:"),o=[...r.matchAll(/<InspectorUpLevel\b([^>]*?)\/>/g)].map(s=>s[1]);e(`§4 ${t.file} draws InspectorUpLevel ${t.words.length} times (${o.length})`,o.length===t.words.length),e("§4 …each inline: it sits in the panel body, under the inspector head",o.length>0&&o.every(s=>/\binline\b/.test(s)));const d=o.map(s=>s.match(/name=(\{.*?\}|"[^"]*")\s+onSelect=/)?.[1]??s);e(`§4 …with the words it had: ${t.what}`,t.words.every((s,m)=>s.test(d[m]??"")),d.join(" | ")),e("§4 …and no linkBtn back is left",!/style=\{linkBtn\}\s+onClick=\{(?:onBack|\(\) =: setLocationsFocus)/.test(r))}return i()}export{b as backControls,y as isEditorCode,$ as oneUpLevelLink};
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import{ruleChecks as F}from"./rule.js";const I=new Set("slate gray zinc neutral stone red orange amber yellow lime green emerald teal cyan sky blue indigo violet purple fuchsia pink rose".split(" ")),S=new Set("text bg border ring fill stroke divide outline shadow from via to placeholder accent caret decoration".split(" ")),E=new Set("text w h p px py pt pb pl pr m mx my mt mb ml mr gap top bottom left right inset basis size leading tracking rounded border min-w max-w min-h max-h gap-x gap-y space-x space-y translate-x translate-y".split(" "));function R(t){let a=t;for(let s=0;s<20;s++){const n=a.replace(/var\(\s*--[A-Za-z0-9-]+\s*,((?:[^()]|\([^()]*\))*)\)/g,(d,u)=>`var(--x,${"
|
|
2
|
-
`),l=A(t,"sample.tsx",e).map(b=>`${b.kind}:${b.text}`).sort(),h=["bw:bg-black/30","bw:bg-white/45","bw:border-white/10","bw:hover:bg-white/70","bw:md:text-white","colour-value:#d85a30","colour-value:rgba(","colour-value:text-[#eee]","palette:hover:bg-amber-100","palette:text-zinc-400","palette:text-zinc-400/50","size-value:max-w-[170px]","size-value:py-[7px]","size-value:text-[13px]"];n("the scanner is CALIBRATED
|
|
1
|
+
import{ruleChecks as F}from"./rule.js";const I=new Set("slate gray zinc neutral stone red orange amber yellow lime green emerald teal cyan sky blue indigo violet purple fuchsia pink rose".split(" ")),S=new Set("text bg border ring fill stroke divide outline shadow from via to placeholder accent caret decoration".split(" ")),E=new Set("text w h p px py pt pb pl pr m mx my mt mb ml mr gap top bottom left right inset basis size leading tracking rounded border min-w max-w min-h max-h gap-x gap-y space-x space-y translate-x translate-y".split(" "));function R(t){let a=t;for(let s=0;s<20;s++){const n=a.replace(/var\(\s*--[A-Za-z0-9-]+\s*,((?:[^()]|\([^()]*\))*)\)/g,(d,u)=>`var(--x,${"·".repeat(u.length)})`);if(n===a)break;a=n}return a}function N(t){const a=t.lastIndexOf("]"),s=t.indexOf("/",a+1);if(s===-1)return t;const n=t.slice(s+1);return/^(?:\d{1,3}|\[[^\]]*\])$/.test(n)?t.slice(0,s):t}function P(t){let a=t;for(let s=0;s<12;s++){const n=/^(?:[a-z0-9-]+|[a-z-]+-\[[^\]]*\]|\[[^\]]*\]):(.*)$/.exec(a);if(!n)break;a=n[1]}return N(a.replace(/^!/,"").replace(/^-/,""))}function O(t){const a=P(t),s=/^([a-z-]+)-\[([^\]]+)\]$/.exec(a);if(s){const[,u,o]=s;if(o.includes("·")||/^var\(/.test(o)||/^url\(|^none$|^inherit$|^auto$/.test(o))return null;const p=/^#/.test(o)||/^(?:rgba?|hsla?|oklch|color-mix)\(/.test(o)||/^(?:white|black|transparent|currentColor)$/.test(o),f=/^-?[\d.]+(?:px|rem|em|%|vw|vh|ch|ex|pt|fr)?$|calc\(|^clamp\(/.test(o);return p&&(S.has(u)||E.has(u))?"colour-value":f&&E.has(u)?"size-value":S.has(u)&&!f?"colour-value":E.has(u)?"size-value":null}const n=/^([a-z-]+)-([a-z]+)-(\d{2,3})$/.exec(a);if(n&&S.has(n[1])&&I.has(n[2]))return"palette";const d=/^([a-z-]+)-(white|black)$/.exec(a);return d&&S.has(d[1])?"bw":null}const C=/#(?:[0-9a-fA-F]{8}|[0-9a-fA-F]{6}|[0-9a-fA-F]{4}|[0-9a-fA-F]{3})\b/g,B=/\b(?:rgba?|hsla?|oklch|color-mix)\(/g;function A(t,a,s){const n=t.createSourceFile(a,s,t.ScriptTarget.Latest,!0,t.ScriptKind.TSX),d=[],u=o=>{let p=null;if((t.isStringLiteral(o)||t.isNoSubstitutionTemplateLiteral(o)||t.isTemplateHead(o)||t.isTemplateMiddle(o)||t.isTemplateTail(o))&&(p=o.text),p&&p.length){const f=n.getLineAndCharacterOfPosition(o.getStart(n)).line+1;let r=R(p);for(const c of r.split(/[\s;{}]+/)){if(!c)continue;const w=O(c);w&&(d.push({file:a,line:f,kind:w,text:c}),c.includes("[")&&(r=r.split(c).join("·".repeat(c.length))))}for(const c of[C,B]){c.lastIndex=0;let w;for(;w=c.exec(r);)d.push({file:a,line:f,kind:"colour-value",text:w[0]})}}t.forEachChild(o,u)};return t.forEachChild(n,u),d}function U(t,a,s){let n;if(a.startsWith("@/"))n=a.slice(2);else if(a.startsWith("./")||a.startsWith("../")){const d=s.split("/").slice(0,-1);for(const u of a.split("/"))u===".."?d.pop():u!=="."&&d.push(u);n=d.join("/")}else return null;for(const d of[`${n}.ts`,`${n}.tsx`,`${n}/index.ts`,`${n}/index.tsx`,n])if(t.has(d)&&/\.tsx?$/.test(d)&&!d.endsWith(".d.ts"))return d;return null}function j(t,a){const s=new Set(a.map(o=>o.path)),n=new Map,d=new Set;for(const o of a){const p=t.createSourceFile(o.path,o.text,t.ScriptTarget.Latest,!0,t.ScriptKind.TSX),f=new Map,r=[],c=(i,m)=>{const x=U(s,i,o.path);if(x&&(r.push(x),!!m&&(m.name&&f.set(m.name.text,x),m.namedBindings&&t.isNamedImports(m.namedBindings))))for(const g of m.namedBindings.elements)f.set(g.name.text,x)},w=i=>{if(t.isImportDeclaration(i)&&t.isStringLiteral(i.moduleSpecifier)&&c(i.moduleSpecifier.text,i.importClause),t.isExportDeclaration(i)&&i.moduleSpecifier&&t.isStringLiteral(i.moduleSpecifier)&&c(i.moduleSpecifier.text),t.isCallExpression(i)&&i.expression.kind===t.SyntaxKind.ImportKeyword){const m=i.arguments[0];m&&t.isStringLiteral(m)&&c(m.text)}if(t.isObjectLiteralExpression(i)){const m=i.properties.some(g=>t.isPropertyAssignment(g)&&t.isIdentifier(g.name)&&g.name.text==="type"&&t.isStringLiteral(g.initializer)&&g.initializer.text==="custom"),x=i.properties.find(g=>t.isPropertyAssignment(g)&&t.isIdentifier(g.name)&&g.name.text==="render");if(m&&x){const g=e=>{(t.isJsxOpeningElement(e)||t.isJsxSelfClosingElement(e))&&t.isIdentifier(e.tagName)&&f.has(e.tagName.text)&&d.add(f.get(e.tagName.text)),t.forEachChild(e,g)};g(x.initializer)}}t.forEachChild(i,w)};t.forEachChild(p,w),n.set(o.path,r)}return{customFieldTargets:d,reachableFrom:o=>{const p=new Set,f=[...o];for(;f.length;){const r=f.pop();if(!p.has(r)){p.add(r);for(const c of n.get(r)??[])f.push(c)}}return p}}}const L=["bg-[","url(a","/b.png)]"].join("");function K(t,a,s){const n=a.filter(i=>/\.tsx?$/.test(i.path)&&!i.path.endsWith(".d.ts")),d=j(t,n),u=n.filter(i=>s.editorRoot(i.path)).map(i=>i.path),o=n.filter(i=>s.publishedRoot(i.path)).map(i=>i.path),p=d.reachableFrom(u),f=d.reachableFrom(o),r=[...p].filter(i=>s.panelFile(i)&&!f.has(i)),c=[...d.customFieldTargets].filter(s.panelFile),w=[...new Set([...r,...c])].sort();return{source:n,editorRoots:u,publishedRoots:o,customFields:c,population:w}}function M(t,a,s){const{check:n,report:d}=F("panel colour tokens");{const e=["// a stale slot — #319 and 13.6:1","/** Theme 1 accent #2e2e2e + black ink */",'const a = "text-[13px] py-[7px]";','const b = "text-[var(--sf-warning,#b45309)]";','const c = "border-[var(--sf-line, rgba(128,128,128,0.3))]";','const d = "text-zinc-400 hover:bg-amber-100 md:text-white";','const e = "z-[200] grid-cols-[1fr_auto]";','const f = { color: "#d85a30", boxShadow: "0 1px 2px rgba(20,20,22,0.10)" };','const g = "text-[#eee] max-w-[170px]";','const h = "bg-black/30 text-zinc-400/50 border-white/10";','const j = "w-2 bg-white/45 hover:bg-white/70";',`const i = "${L} w-1/2";`].join(`
|
|
2
|
+
`),l=A(t,"sample.tsx",e).map(b=>`${b.kind}:${b.text}`).sort(),h=["bw:bg-black/30","bw:bg-white/45","bw:border-white/10","bw:hover:bg-white/70","bw:md:text-white","colour-value:#d85a30","colour-value:rgba(","colour-value:text-[#eee]","palette:hover:bg-amber-100","palette:text-zinc-400","palette:text-zinc-400/50","size-value:max-w-[170px]","size-value:py-[7px]","size-value:text-[13px]"];n("the scanner is CALIBRATED — it reads code, not comments, and not tokens",JSON.stringify(l)===JSON.stringify(h),`the scanner has drifted. It must (1) never read a comment — the card number #319 and the contrast table are not colours; (2) read var(--tok, #fallback) as the TOKEN it is, at any nesting; (3) decide colour-vs-size from the VALUE, so text-[13px] is a size and text-[#eee] is a colour; (4) ignore z-[…] and grid-cols-[…], which are neither; (5) see THROUGH an opacity modifier — bg-black/30 is still a hard-coded black — without mistaking the slash inside ${L} or the one in w-1/2 for one. Got: ${JSON.stringify(l)}`)}const{source:u,editorRoots:o,publishedRoots:p,customFields:f,population:r}=K(t,a,s),{floors:c}=s;n(`the editor entries were found (${o.length}, at least ${c.editorRoots})`,o.length>=c.editorRoots&&o.length>0,"the walk read no editor entry, so every rule below would be an assertion over an empty list and would pass for the wrong reason"),n(`the published entries were found (${p.length}, at least ${c.publishedRoots})`,p.length>=c.publishedRoots&&p.length>0,"without them every component would look editor-only and the population would balloon"),n(`the panel population is ${r.length} files (at least ${c.population})`,r.length>=c.population&&r.length>0,"a population that small is not a clean repository, it is a scan that read nowhere — something in module resolution broke"),n(`Puck custom fields draw ${f.length} panel files (at least ${c.customFields})`,f.length>=c.customFields,"rule B is what covers every settings field a registry reaches; if it stops matching, the field components leave the population in silence");for(const e of s.anchors??[])n(`${e} is panel chrome, and in the population`,r.includes(e),"it is missing — the walk has stopped reaching the editor");for(const e of s.drawnByAField??[])n(`${e} is drawn by a Puck custom field, and in the population`,r.includes(e),'it belongs to the panel, but its NAME does not say "panel" — rule B has stopped working');for(const e of s.opacitySites??[])n(`${e} carries a colour behind an opacity modifier, and stays in the population`,r.includes(e),"it is one of the sites this rule was once blind to; if it has dropped out, the rule is green while ignoring a defect it was written to find");for(const e of s.shopperFacing??[])n(`${e} paints a shopper's page, and is not in the population`,!r.includes(e),"it is not this rule's business, and taking it would make this a storefront gate wearing a panel gate's name");const w=new Map(u.map(e=>[e.path,e.text])),i=new Map;for(const e of r){const l=A(t,e,w.get(e)??"");l.length&&i.set(e,l)}const m=[...i.values()].reduce((e,l)=>e+l.length,0),x=new Map(s.allow.map(e=>[e.file,e])),g=r.reduce((e,l)=>e+(w.get(l)??"").length,0);n(`the scan read ${g} bytes of panel source (at least ${c.bytes})`,g>=c.bytes&&g>0,'that is not the editor. Every "no literals found" below would be meaningless.'),n("the scanner found literals while the allow list still lists files",m>0||s.allow.length===0,"the scanner found nothing anywhere while the allow list still lists files. A scan that reads nothing looks exactly like a clean codebase.");{const e=[];for(const[l,h]of[...i].sort()){if(x.has(l))continue;const b=h.slice(0,6).map($=>` ${l}:${$.line} ${$.text}`);e.push(` ${l} — ${h.length} literal${h.length===1?"":"s"}
|
|
3
3
|
`+b.join(`
|
|
4
4
|
`)+(h.length>b.length?`
|
|
5
|
-
|
|
5
|
+
…and ${h.length-b.length} more`:""))}n("NO panel file outside the list carries a colour or size literal",e.length===0,`Hard-coded colour/size literals in editor PANEL components.
|
|
6
6
|
|
|
7
|
-
The panel's colours and sizes come from tokens
|
|
7
|
+
The panel's colours and sizes come from tokens — the --sf-* custom properties the editor's sheets define (efficient-easel/screen.css, and the builder's components/storefront-editor.css) and the .es-* classes efficient-easel draws. A literal here is a private copy of one step of that scale: it does not move when the scale moves, and nothing says it is wrong until somebody looks at the panel beside the campaign designer.
|
|
8
8
|
|
|
9
9
|
Spell it as a token. If it genuinely cannot be one, add a line to the allow list with KEEP (a design reason) or UNTIL (what has to land), and its exact count.
|
|
10
10
|
|
|
11
11
|
`+e.join(`
|
|
12
12
|
|
|
13
|
-
`))}{const e=[];for(const l of s.allow){const h=i.get(l.file);if(!r.includes(l.file)){e.push(` ${l.file} is on the list but is no longer a panel component (the editor does not reach it, and no Puck custom field draws it). Delete the line.`);continue}if(!h){e.push(` ${l.file} is CLEAN now
|
|
13
|
+
`))}{const e=[];for(const l of s.allow){const h=i.get(l.file);if(!r.includes(l.file)){e.push(` ${l.file} is on the list but is no longer a panel component (the editor does not reach it, and no Puck custom field draws it). Delete the line.`);continue}if(!h){e.push(` ${l.file} is CLEAN now — 0 literals, the line still allows ${l.count}. Delete the line: that is what "done" looks like.`);continue}if(h.length>l.count){const b=h.slice(l.count).map($=>`${$.text} @${$.line}`);e.push(` ${l.file} has ${h.length} literals, the line allows ${l.count}. A NEW literal went into a file we already tolerate — this is exactly how the count creeps back. Spell it as a token; do not raise the number. (e.g. ${b.slice(0,3).join(", ")})`)}h.length<l.count&&e.push(` ${l.file} is down to ${h.length} literals from ${l.count}. Good — now finish it: either clear the rest and delete the line, or lower the count in this same PR. A line that allows more than the file spends goes on excusing a debt that has been paid.`)}n(`every allow line still describes its file — counts pinned BOTH ways (${s.allow.length} lines)`,e.length===0,`the allow list is out of date:
|
|
14
14
|
|
|
15
15
|
${e.join(`
|
|
16
16
|
|
|
17
|
-
`)}`)}{const e=[],l=new Set;for(const h of s.allow)l.has(h.file)&&e.push(` ${h.file} is listed twice`),l.add(h.file),h.count<1&&e.push(` ${h.file} pins count ${h.count}
|
|
17
|
+
`)}`)}{const e=[],l=new Set;for(const h of s.allow)l.has(h.file)&&e.push(` ${h.file} is listed twice`),l.add(h.file),h.count<1&&e.push(` ${h.file} pins count ${h.count} — a line allowing nothing is a deleted line`),h.kind==="KEEP"&&h.why.trim().length<20&&e.push(` ${h.file} is a KEEP with no design behind it. A KEEP says why the literal is RIGHT; if you cannot say that, it is an UNTIL.`),h.kind==="UNTIL"&&h.until.trim().length<20&&e.push(` ${h.file} is an UNTIL with no expiry. A reason explains why a line is here today; an expiry is what GETS IT OUT. Say what has to land.`);n("every allow line carries an EXPIRY, not just a reason",e.length===0,`allow lines are malformed:
|
|
18
18
|
|
|
19
19
|
${e.join(`
|
|
20
|
-
`)}`)}{const e=i.size,l=r.length-e,h=r.length?Math.round(l/r.length*100):0,b=s.allow.filter(y=>y.kind==="KEEP").length,$=s.allow.filter(y=>y.kind==="UNTIL").length,z=[...i.values()].flat().filter(y=>y.kind!=="size-value").length;n(`PROGRESS
|
|
20
|
+
`)}`)}{const e=i.size,l=r.length-e,h=r.length?Math.round(l/r.length*100):0,b=s.allow.filter(y=>y.kind==="KEEP").length,$=s.allow.filter(y=>y.kind==="UNTIL").length,z=[...i.values()].flat().filter(y=>y.kind!=="size-value").length;n(`PROGRESS — panel token coverage: ${l}/${r.length} files clean (${h}%) · ${m} literals left in ${e} file${e===1?"":"s"} (${z} colour + ${m-z} size) · allow: ${b} KEEP + ${$} UNTIL`,r.length>0);for(const[y,T]of[...i].sort((k,v)=>v[1].length-k[1].length)){const k=T.filter(v=>v.kind!=="size-value").length;n(`PROGRESS ${String(k).padStart(3)} colour ${String(T.length-k).padStart(3)} size ${y}`,!0)}}return d()}export{R as blankVarFallbacks,O as classifyClassToken,M as panelColourTokens,K as panelPopulation,A as scanSource};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import{ruleChecks as E}from"./rule.js";function v(r){const s=r.split(`
|
|
2
2
|
`),n=[];return s.forEach((g,a)=>{if(!/border-(top|bottom|left|right)[a-z-]*\s*:[^;]*\bsolid\b/.test(g))return;let $=0,c="?";for(let i=a-1;i>=0;i-=1)if($+=(s[i].match(/\}/g)??[]).length-(s[i].match(/\{/g)??[]).length,$<0){c=s[i].split("{")[0].trim();let h=i-1;for(;h>=0&&s[h].trim().endsWith(",");)c=`${s[h].trim()} ${c}`,h-=1;break}n.push({line:a+1,selector:c,decl:g.trim()})}),n}const y=r=>r.split(/[\s,+>~]/)[0].trim(),j=(r,s)=>s in r||y(s)in r,O=/\bzinc-|--sf-line\b|--sf-divider\b|--sf-input-border\b|--sf-chip-bg\b|--es-line\b|--es-divider\b|--sf-text-secondary\b/,k=[["utility",/(^|["'`\s])border-(t|b|l|r)($|["'`\s])/],["style object",/border(Top|Bottom|Left|Right)\s*:/],["css in a string",/border-(top|bottom|left|right)\s*:/]];function S(r){return/radius/i.test(r)||!O.test(r)?null:k.find(([,s])=>s.test(r))?.[0]??null}function N(r,s){const{check:n,report:g}=E("panel divider lines"),a=s.allowed??{},$=s.pendingElsewhere??{},c=s.allowedTsx??[];{const e=u=>["border",u].join("-"),t=[".a,",".b + .c {",` ${e("top")}: 1px solid red;`,"}",`.d { ${e("radius")}: 4px; }`].join(`
|
|
3
|
-
`),l=v(t);n("the census finds a directional solid border, with its whole multi-line selector",l.length===1&&l[0].selector===".a, .b + .c"&&y(l[0].selector)===".a",JSON.stringify(l));const d=["zinc","200"].join("-"),f=[["a utility",`<div className="${e("t")} border-${d}" />`,"utility"],["a style object",`style={{ ${["border","Top"].join("")}: "1px solid var(--sf-line)" }}`,"style object"],["CSS in a string",`const s = "${e("left")}: 1px solid var(--es-divider)";`,"css in a string"],["a border in the storefront's own ink",`<div className="${e("t")} border-line" />`,null],["a radius",`<div className="${e("t")} border-${d} rounded" style={{ borderRadius: 4 }} />`,null]];for(const[u,o,p]of f)n(`the sweep reads ${u} as ${p??"no hairline"}`,S(o)===p,o)}const i=[];for(const e of s.panelSheets??[]){const t=r.find(o=>o.path===e)?.text??"",l=v(t);n(`${e} was read and parsed`,t.length>1e4&&l.length>0,`${t.length} bytes, ${l.length} directional border(s)`),i.push(...l.map(o=>({...o,sheet:e})));const f=(t.replace(/\/\*[\s\S]*?\*\//g,"").match(/\[class\*="_PuckFields[^"]*"\][^{]*\{/g)??[]).length;n(`${e} reaches for no Puck field wrapper`,f===0,`${f} rule(s) still select \`[class*="_PuckFields
|
|
4
|
-
${o} {`);if(!n(`the allowed full-bleed rule is still in ${e}`,p>=0,o))continue;const x=u.slice(p,u.indexOf("}",p));for(const w of["left","right"])n(`the full-bleed rule cancels the panel's ${w} padding, so it reaches the edge`,new RegExp(`margin-${w}:\\s*calc\\(var\\(--sf-insp-pad-x\\)\\s*\\*\\s*-1\\)`).test(x),"inset, this is the line the owner rejected
|
|
5
|
-
`+e.map(t=>`${t.sheet}:${t.line} ${t.selector}
|
|
6
|
-
`))}const h=e=>i.some(t=>t.selector===e||y(t.selector)===e);for(const[e,t]of Object.entries(a))n(`ALLOWED entry ${e} still names a real rule (${t.slice(0,48)}
|
|
3
|
+
`),l=v(t);n("the census finds a directional solid border, with its whole multi-line selector",l.length===1&&l[0].selector===".a, .b + .c"&&y(l[0].selector)===".a",JSON.stringify(l));const d=["zinc","200"].join("-"),f=[["a utility",`<div className="${e("t")} border-${d}" />`,"utility"],["a style object",`style={{ ${["border","Top"].join("")}: "1px solid var(--sf-line)" }}`,"style object"],["CSS in a string",`const s = "${e("left")}: 1px solid var(--es-divider)";`,"css in a string"],["a border in the storefront's own ink",`<div className="${e("t")} border-line" />`,null],["a radius",`<div className="${e("t")} border-${d} rounded" style={{ borderRadius: 4 }} />`,null]];for(const[u,o,p]of f)n(`the sweep reads ${u} as ${p??"no hairline"}`,S(o)===p,o)}const i=[];for(const e of s.panelSheets??[]){const t=r.find(o=>o.path===e)?.text??"",l=v(t);n(`${e} was read and parsed`,t.length>1e4&&l.length>0,`${t.length} bytes, ${l.length} directional border(s)`),i.push(...l.map(o=>({...o,sheet:e})));const f=(t.replace(/\/\*[\s\S]*?\*\//g,"").match(/\[class\*="_PuckFields[^"]*"\][^{]*\{/g)??[]).length;n(`${e} reaches for no Puck field wrapper`,f===0,`${f} rule(s) still select \`[class*="_PuckFields…"]\` — the rows are \`.es-insp-row\` now`);const u=t.replace(/,\s*\n\s*/g,", ");for(const o of s.fullBleed??[]){if(!(o in a))continue;const p=u.indexOf(`
|
|
4
|
+
${o} {`);if(!n(`the allowed full-bleed rule is still in ${e}`,p>=0,o))continue;const x=u.slice(p,u.indexOf("}",p));for(const w of["left","right"])n(`the full-bleed rule cancels the panel's ${w} padding, so it reaches the edge`,new RegExp(`margin-${w}:\\s*calc\\(var\\(--sf-insp-pad-x\\)\\s*\\*\\s*-1\\)`).test(x),"inset, this is the line the owner rejected — full bleed is the exemption"),n(`…and puts that ${w} padding back inside, so the words stay on the rows' edge`,new RegExp(`padding-${w}:\\s*var\\(--sf-insp-pad-x\\)`).test(x))}}for(const e of s.fullBleed??[])n(`the full-bleed selector is an ALLOWED entry: ${e}`,e in a);if(s.panelSheets?.length){const e=i.filter(t=>!j(a,t.selector)&&!j($,t.selector));n(`no unlisted directional border survives in a panel stylesheet (${i.length} censused)`,e.length===0,`${e.length} neither removed, allowed, nor owed to another card — delete it and let the rhythm tokens separate, or add it to the allow-list with a reason if it is a control edge:
|
|
5
|
+
`+e.map(t=>`${t.sheet}:${t.line} ${t.selector} — ${t.decl}`).join(`
|
|
6
|
+
`))}const h=e=>i.some(t=>t.selector===e||y(t.selector)===e);for(const[e,t]of Object.entries(a))n(`ALLOWED entry ${e} still names a real rule (${t.slice(0,48)}…)`,h(e),"the rule is gone: delete this entry with it");for(const[e,t]of Object.entries($))n(`PENDING entry ${e} is still owed — ${t}`,h(e),"that card has landed and the rule is gone: delete this entry");const b=r.filter(e=>/\.tsx?$/.test(e.path));n(`the repository's own TS/TSX files were swept (${b.length}, at least ${s.minFiles})`,b.length>=s.minFiles);const m=[];for(const e of b)e.text.split(`
|
|
7
7
|
`).forEach((t,l)=>{const d=S(t);d&&(c.some(f=>f.file===e.path&&t.includes(f.util))||m.push(`${e.path}:${l+1} (${d}) ${t.trim().slice(0,80)}`))});n("no editor panel file draws an unlisted directional border",m.length===0,`${m.length} found:
|
|
8
8
|
${m.join(`
|
|
9
|
-
`)}`);for(const e of c){const t=b.find(l=>l.path===e.file)?.text??"";n(`ALLOWED_TSX entry ${e.file} ${e.util} still names a real line (${e.why.slice(0,40)}
|
|
9
|
+
`)}`);for(const e of c){const t=b.find(l=>l.path===e.file)?.text??"";n(`ALLOWED_TSX entry ${e.file} ${e.util} still names a real line (${e.why.slice(0,40)}…)`,t.split(`
|
|
10
10
|
`).some(l=>k.some(([,d])=>d.test(l))&&l.includes(e.util)),"the line is gone: delete this entry")}return g()}export{N as panelNoDividerLines};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isRuleSource as U,ruleChecks as G}from"./rule.js";import{readRules as K}from"./support/css-rules.js";import{stripComments as V}from"./support/css-source.js";import{applicable as q,census as J,deriveShapes as L,derefVars as Q,importsOf as B,readVars as X,resolve as O,splitDecls as W}from"./support/panel-select-cascade.js";const S="--sf-select-arrow",R=["background-image","background-size","background-repeat","background-position"],I=l=>/(^|[\s,>+~(])select(?![\w-])/.test(l);function Y(l,a){return typeof a!="string"?{sheet:a,ours:!1}:{sheet:l.find(o=>o.path===a),ours:!0}}const Z=l=>l.filter(a=>/\.(tsx?|mts)$/.test(a.path)&&!U(a.path)),P=(l,a)=>l===`@/${a}`||l.endsWith(`/${a}`)||l===`./${a.split("/").pop()}`;function oe(l,a,o,n){const x=new Set;for(const j of o){const k=a.find(r=>r.path===j);if(k)for(const r of B(l,k))r.endsWith(".css")&&!P(r,n)&&!r.startsWith("@/")&&!r.startsWith(".")&&x.add(r)}return[...x].sort()}function ae(l,a,o){const{check:n,report:x}=G("panel one arrow"),{sheet:j,ours:k}=Y(a,o.panelSheet),r=j?.path??(typeof o.panelSheet=="string"?o.panelSheet:o.panelSheet.path),{population:g,helperDeclared:N}=J(l,Z(a),o.selectHelper),T=g.filter(e=>e.engineDrawn),p=g.filter(e=>!e.engineDrawn);a.some(e=>e.path===o.selectHelper.file)&&n(`${o.selectHelper.file} declares the house \`select\` helper`,N,"the census has no helper to resolve (B) against"),n(`the panel draws house-drawn selects (${p.length}, at least ${o.houseDrawnFloor})`,p.length>=o.houseDrawnFloor&&(p.length>0||o.houseDrawnFloor===0),`${p.length} found: ${p.map(e=>`${e.file}:${e.line}`).join(", ")||"(none)"}
|
|
1
|
+
import{isRuleSource as U,ruleChecks as G}from"./rule.js";import{readRules as K}from"./support/css-rules.js";import{stripComments as V}from"./support/css-source.js";import{applicable as q,census as J,deriveShapes as L,derefVars as Q,importsOf as B,readVars as X,resolve as O,splitDecls as W}from"./support/panel-select-cascade.js";const S="--sf-select-arrow",R=["background-image","background-size","background-repeat","background-position"],I=l=>/(^|[\s,>+~(])select(?![\w-])/.test(l);function Y(l,a){return typeof a!="string"?{sheet:a,ours:!1}:{sheet:l.find(o=>o.path===a),ours:!0}}const Z=l=>l.filter(a=>/\.(tsx?|mts)$/.test(a.path)&&!U(a.path)),P=(l,a)=>l===`@/${a}`||l.endsWith(`/${a}`)||l===`./${a.split("/").pop()}`;function oe(l,a,o,n){const x=new Set;for(const j of o){const k=a.find(r=>r.path===j);if(k)for(const r of B(l,k))r.endsWith(".css")&&!P(r,n)&&!r.startsWith("@/")&&!r.startsWith(".")&&x.add(r)}return[...x].sort()}function ae(l,a,o){const{check:n,report:x}=G("panel one arrow"),{sheet:j,ours:k}=Y(a,o.panelSheet),r=j?.path??(typeof o.panelSheet=="string"?o.panelSheet:o.panelSheet.path),{population:g,helperDeclared:N}=J(l,Z(a),o.selectHelper),T=g.filter(e=>e.engineDrawn),p=g.filter(e=>!e.engineDrawn);a.some(e=>e.path===o.selectHelper.file)&&n(`${o.selectHelper.file} declares the house \`select\` helper`,N,"the census has no helper to resolve (B) against"),n(`the panel draws house-drawn selects (${p.length}, at least ${o.houseDrawnFloor})`,p.length>=o.houseDrawnFloor&&(p.length>0||o.houseDrawnFloor===0),`${p.length} found: ${p.map(e=>`${e.file}:${e.line}`).join(", ")||"(none)"} — with none, this card's rows do not exist and §3 passes empty`);const v=V(j?.text??""),$=X(v);n(`${r} was read`,v.length>1e4,`${v.length} bytes`);const D=K(v),i=L(g,v),F=i.reduce((e,t)=>e+t.rows,0);n(`every house-drawn select is in exactly one shape, and no engine-drawn field is in any (${i.map(e=>e.name).join(", ")})`,F===p.length,`shapes hold ${F} row(s), the census found ${p.length} house-drawn`);const u=new Map,E=new Map,y=[],M=[];for(const e of i){const t=q(D,e,r,0,"rest"),s=q(D,e,r,0,"hover");y.push(...t.unreadable,...s.unreadable),M.push(...s.undecided);const m={appearance:O(t.applied,"appearance",$).value};for(const h of R)m[h]=O(t.applied,h,$).value;m["@from"]=O(t.applied,"background-image",$).from,u.set(e.name,m);const c={};for(const h of R)c[h]=O(s.applied,h,$).value;E.set(e.name,c)}const f=(e,t)=>e.get(t.name);n("every rule that can reach a panel select was understood",y.length===0,`could not parse: ${[...new Set(y)].slice(0,4).join(" · ")}${y.length>4?` (+${y.length-4} more)`:""} — a rule this model skips is a rule it lies about`);for(const e of i){const t=f(u,e);n(`${e.name}: the browser draws no arrow of its own (appearance: none)`,t.appearance==="none",`appearance resolves to \`${t.appearance}\` on ${e.rows} row(s) — the platform arrow is still painted, so an added image would sit beside it`)}for(const e of i){const t=f(u,e);n(`${e.name}: an arrow IS drawn`,/url\(/.test(t["background-image"]),`background-image resolves to \`${t["background-image"]}\` on ${e.rows} row(s) — a row with no arrow reads as a stylesheet that failed to load, and taking the arrow away undoes the owner's 2026-09-01 ruling`)}const A=f(u,i[0]);for(const e of["appearance",...R]){const t=i.filter(s=>f(u,s)[e]!==A[e]);n(`every shape resolves the same \`${e}\``,t.length===0,`${i[0].name} (${i[0].rows} row(s)) says \`${A[e].slice(0,44)}\`, but `+t.map(s=>`${s.name} (${s.rows} row(s)) says \`${f(u,s)[e].slice(0,44)}\``).join("; "))}const C=e=>e["background-repeat"]!=="no-repeat"||e["background-size"]==="auto";for(const e of i){const t=f(u,e);n(`${e.name}: the arrow is never tiled (66397)`,!C(t),`repeat=${t["background-repeat"]} size=${t["background-size"]} — a full-size tile repeats ~2.5x across a 248px row and draws a bar of triangles`)}for(const e of i){const t=f(E,e);n(`${e.name}: the arrow is never tiled under the pointer either (66397)`,/url\(/.test(t["background-image"])&&!C(t),`hovered image=${t["background-image"].slice(0,40)} repeat=${t["background-repeat"]} size=${t["background-size"]} — this is the dark bar with three triangles, in the state it was filed in`)}const H=M.filter(e=>W(e.decls).some(t=>t.prop==="background"||R.includes(t.prop)));if(n("no rule this model cannot judge decides a hovered arrow property",H.length===0,`${[...new Set(H.map(e=>`@media ${e.media} { ${e.selector} }`))].join(" · ")} — an unjudged media query holding one of these properties makes (e) a guess`),k){const e=D.flatMap(t=>t.selector.split(",").map(s=>s.trim()).filter(s=>I(s)).filter(()=>W(t.decls).some(s=>s.prop==="background")).map(s=>t.media?`${t.media} { ${s} }`:s));n("no rule that names the `select` element uses the `background` shorthand",e.length===0,`${e.join(" · ")} — a shorthand resets background-size/-repeat/-position/-image; set \`background-color\` instead (card 66397)`)}if(o.editorEntries!==void 0){const e=/^@measured\/puck(\/|$)/,t=new Set;for(const c of o.editorEntries){const h=a.find(d=>d.path===c),w=h?B(l,h):[];n(`${c} loads ${r}`,w.some(d=>P(d,r)),h?"§3 resolves the panel stylesheet's cascade, and this editor does not load it":"the entry is not among the files");const b=w.filter(d=>e.test(d));n(`${c} imports nothing from Puck's package`,b.length===0,`imports ${b.join(", ")} — Puck was removed in blueprint 6.3, and §3 no longer models its stylesheet`);for(const d of w)d.endsWith(".css")&&!P(d,r)&&t.add(d)}const s=[],m=[];for(const c of t){const w=(c.startsWith("@/")?a.find(b=>b.path===c.slice(2))?.text:void 0)??o.loadedSheets?.[c]??null;if(w===null){m.push(c);continue}for(const b of K(V(w)))for(const d of b.selector.split(","))I(d)&&s.push(`${c}: ${d.trim()}`)}n(`every other stylesheet the editors load was read (${t.size})`,m.length===0,`could not resolve ${m.join(", ")} — a sheet this rule cannot open is a cascade it cannot rule out`),n("no other stylesheet the editors load has a rule naming the `select` element",s.length===0,`${s.slice(0,4).join(" · ")}${s.length>4?` (+${s.length-4} more)`:""} — §3 resolves ${r} alone, so this rule is invisible to every answer above`)}const z=Q($.get(S)??"",$);if(k){const e=D.flatMap(t=>W(t.decls)).filter(t=>t.prop===S);n(`${r} declares \`${S}\` exactly once, as an image`,e.length===1&&/^url\(/.test(z),`${e.length} declaration(s), value \`${z.slice(0,60)||"(none)"}\` — the one arrow needs one definition`)}for(const e of i){const t=f(u,e)["background-image"];n(`${e.name}: the arrow it draws is \`var(${S})\`, glyph for glyph`,z!==""&&t===z,`resolves \`${t.slice(0,60)}\` — a redrawn arrow is the SS seat's call, made once in \`${S}\`, not in a rule of its own`)}const _=p.flatMap(e=>e.classes.filter(t=>/^(appearance-|bg-(none|\[|gradient|linear|radial|conic))/.test(t)).map(t=>`${e.file}:${e.line} \`${t}\``));return n("no panel select decides its arrow with a utility class",_.length===0,`${_.join(", ")} — §3 reads only ${r}`),n(`the population came from the program, not from a number typed here (${g.length}, at least ${o.populationFloor})`,g.length>=o.populationFloor&&g.length>0,`${g.length} dropdowns found (${p.length} house-drawn, ${T.length} engine-drawn) — a census that collapses to a handful means the walk stopped reading`),x()}export{Z as censusFiles,oe as editorPackageSheets,I as namesSelectElement,ae as panelOneArrow,Y as panelSheetOf};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{censusFiles as Q,panelSheetOf as X}from"./panel-one-arrow.js";import{ruleChecks as Y}from"./rule.js";import{readRules as I}from"./support/css-rules.js";import{stripComments as K}from"./support/css-source.js";import{applicable as P,census as ee,chainInMarkup as te,compound as q,deriveShapes as oe,readVars as ne,resolve as A,splitCombinators as B,splitDecls as F}from"./support/panel-select-cascade.js";const y=["background-color","background-image","border-color"],se=["background-image","border-color"],j="--sf-hover",C="--sf-select-arrow",m=p=>{const u=/fill='([^']*)'/.exec(p)?.[1];return u?`url(<svg
|
|
2
|
-
${w}`:w,v=ne(R);s(`${k} was read`,w.length>1e4,`${w.length} bytes`),a&&s(`${a.path} was read`,S.length>1e4,`${S.length} bytes`);const D=I(S),L=I(w),c=oe(f,R),b=(e,t)=>{const n=P(D,e,a?.path??"",0,t),o=P(L,e,k,D.length,t);return{applied:[...n.applied,...o.applied],unreadable:[...n.unreadable,...o.unreadable],undecided:[...n.undecided,...o.undecided]}},T=(e,t)=>Object.fromEntries(t.map(n=>[n,A(e.applied,n,v).value])),x=new Map,g=new Map,H=[],V=[];for(const e of c){const t=b(e,"rest"),n=b(e,"hover");H.push(...t.unreadable,...n.unreadable),V.push(...n.undecided),x.set(e.name,T(t,y)),g.set(e.name,{...T(n,y),"@from":A(n.applied,"background-color",v).from})}const d=(e,t)=>e.get(t.name);s("every rule that can reach a panel select was understood",H.length===0,`could not parse: ${[...new Set(H)].slice(0,4).join("
|
|
1
|
+
import{censusFiles as Q,panelSheetOf as X}from"./panel-one-arrow.js";import{ruleChecks as Y}from"./rule.js";import{readRules as I}from"./support/css-rules.js";import{stripComments as K}from"./support/css-source.js";import{applicable as P,census as ee,chainInMarkup as te,compound as q,deriveShapes as oe,readVars as ne,resolve as A,splitCombinators as B,splitDecls as F}from"./support/panel-select-cascade.js";const y=["background-color","background-image","border-color"],se=["background-image","border-color"],j="--sf-hover",C="--sf-select-arrow",m=p=>{const u=/fill='([^']*)'/.exec(p)?.[1];return u?`url(<svg … fill='${u}'>)`:p.length>46?`${p.slice(0,46)}…`:p};function ie(p,u,r){const{check:s,report:Z}=Y("panel one hover"),{sheet:N,ours:E}=X(u,r.panelSheet),k=N?.path??(typeof r.panelSheet=="string"?r.panelSheet:r.panelSheet.path),{population:f,helperDeclared:G}=ee(p,Q(u),r.selectHelper),J=f.filter(e=>e.engineDrawn),$=f.filter(e=>!e.engineDrawn);u.some(e=>e.path===r.selectHelper.file)&&s(`${r.selectHelper.file} declares the house \`select\` helper`,G,"the census has no helper to resolve against"),s(`the panel draws house-drawn selects (${$.length}, at least ${r.houseDrawnFloor})`,$.length>=r.houseDrawnFloor&&($.length>0||r.houseDrawnFloor===0),`${$.length} found — with none, this card's rows do not exist`),s(`the population came from the program, not from a number typed here (${f.length}, at least ${r.populationFloor})`,f.length>=r.populationFloor&&f.length>0,`${f.length} dropdowns found (${$.length} house-drawn, ${J.length} engine-drawn) — a census that collapses to a handful means the walk stopped reading`);const a=r.screenSheet,S=K(a?.text??""),w=K(N?.text??""),R=a?`${S}
|
|
2
|
+
${w}`:w,v=ne(R);s(`${k} was read`,w.length>1e4,`${w.length} bytes`),a&&s(`${a.path} was read`,S.length>1e4,`${S.length} bytes`);const D=I(S),L=I(w),c=oe(f,R),b=(e,t)=>{const n=P(D,e,a?.path??"",0,t),o=P(L,e,k,D.length,t);return{applied:[...n.applied,...o.applied],unreadable:[...n.unreadable,...o.unreadable],undecided:[...n.undecided,...o.undecided]}},T=(e,t)=>Object.fromEntries(t.map(n=>[n,A(e.applied,n,v).value])),x=new Map,g=new Map,H=[],V=[];for(const e of c){const t=b(e,"rest"),n=b(e,"hover");H.push(...t.unreadable,...n.unreadable),V.push(...n.undecided),x.set(e.name,T(t,y)),g.set(e.name,{...T(n,y),"@from":A(n.applied,"background-color",v).from})}const d=(e,t)=>e.get(t.name);s("every rule that can reach a panel select was understood",H.length===0,`could not parse: ${[...new Set(H)].slice(0,4).join(" · ")} — a rule this model skips is a rule it lies about`);const U=V.filter(e=>F(e.decls).some(t=>y.includes(t.prop)||t.prop==="background"||t.prop==="border"));s("no rule this model cannot judge decides a hovered value",U.length===0,`${U.map(e=>`@media ${e.media} { ${e.selector} }`).join(" · ")} — an unjudged media query holding one of these properties makes every answer above a guess`);const M=c[0];for(const e of y){const t=d(g,M)[e],n=c.filter(o=>d(g,o)[e]!==t);s(`every shape resolves the same hovered \`${e}\``,n.length===0,`${M.name} (${M.rows} row(s)) says \`${m(t)}\`, but `+n.map(o=>`${o.name} (${o.rows} row(s)) says \`${m(d(g,o)[e])}\``).join("; "))}for(const e of c){const t=d(x,e),n=d(g,e);s(`${e.name}: the ground actually changes under the pointer`,t["background-color"]!==n["background-color"],`rest and hover both resolve \`${m(n["background-color"])}\` on ${e.rows} row(s) — a row that does not answer the pointer is the other half of this card's defect`)}const O=v.get(j);(E||a)&&s(`the ${a?"editor's sheets declare":"panel declares"} \`${j}\``,O!==void 0,`the token the ruling names is not in ${a?`${a.path} or ${k}`:"the panel stylesheet"}`);for(const e of c){const t=d(g,e);s(`${e.name}: the hovered ground is the panel's own \`var(${j})\``,O!==void 0&&t["background-color"]===O,`resolves to \`${m(t["background-color"])}\`, and \`${j}\` is \`${O??"(not declared)"}\` — option D is the panel's own hover ground, picked by Lewis on 2026-09-02, and a near miss is not it`)}for(const e of c){const t=d(x,e),n=d(g,e);for(const o of se)s(`${e.name}: \`${o}\` is unchanged under the pointer`,t[o]===n[o],`rest \`${m(t[o])}\` -> hover \`${m(n[o])}\` on ${e.rows} row(s) — Lewis chose D (ground only) over B (border jump) and C (darker glyph) from pictures; moving this is re-deciding it`)}if(E){const e=l=>l.replace(/\s+/g," ").replace(/^url\(([\s\S]*)\)$/,"$1").replace(/^["']|["']$/g,"").trim(),t=v.get(C),n=e(t??"");s(`the panel declares \`${C}\` as an image`,t!==void 0&&/^url\(/.test(t.trim())&&n!=="",`\`${C}\` is \`${m(t??"(not declared)")}\` — the one arrow needs one definition`);const o=n===""?-1:R.split(n).length-1;s(`the arrow glyph is written ONCE in ${k}${a?` and ${a.path}`:""}`,o===1,`found ${o} — pasting the arrow into a second rule is exactly what card 66399 removed. Read \`var(${C})\` instead`);const i=[...D,...L].filter(l=>/select/.test(l.selector)&&F(l.decls).some(_=>/url\(\s*["']?data:/.test(_.value)));s("no select rule holds the arrow inline; every one reads the token",i.length===0,`${i.map(l=>l.selector.trim()).join(" · ")} still writes a data URI`)}const W=b(c[0],"hover").applied.filter(e=>/:hover/.test(e.selector)),h=W[0];if(E){s("the panel declares a select hover rule at all",W.length>0,"no `…select:hover` rule in the panel stylesheet reaches a house row — they do nothing under the pointer, which is this card's defect");const e=h?F(h.decls):[],t=h?B(h.selector).slice(0,-1).flatMap(o=>q(o)?.classes??[]):[];s("the hover rule uses no `!important`",h!==void 0&&!/!\s*important/i.test(h.decls),"an `!important` here beats every other state the panel paints, including ones this card was never given");const n=e.map(o=>o.prop).filter(o=>!y.includes(o));s("the hover rule declares only the three properties the ruling names",h!==void 0&&n.length===0,`also declares ${n.join(", ")} — the ruling is a ground colour, plus the arrow and border restated at rest. Anything else is a state this card was not given`),s("the hover rule is confined to the inspector body",t.includes("sf-insp-body"),`its ancestor chain is \`${t.join(" ")}\` — without \`.sf-insp-body\` it reaches every select in the editor, not the panel's`)}for(const e of c){const t=A(b(e,"rest").applied,"background-color",v).from;s(`${e.name}: the resting ground is NOT decided by the hover rule`,h===void 0||!t.includes(h.selector),`rest resolves from \`${t}\` — the pointer state has leaked into the row at rest`)}if(r.chrome){const e=new Set;for(const o of c)for(const i of["rest","hover"])for(const l of b(o,i).applied)for(const _ of B(l.selector).slice(0,-1))for(const z of q(_)?.classes??[])z.startsWith("@")||e.add(z);s(`the rules that resolved lean on wrapper classes (${[...e].map(o=>`.${o}`).join(" ")||"none"})`,e.size>0);const t=te(p,u,[...e]),n=new Set(D.flatMap(o=>[...o.selector.matchAll(/\.(-?[_a-zA-Z][\w-]*)/g)].map(i=>i[1])));for(const o of e){const i=(t.get(o)??[]).length>0,l=!i&&n.has(o);s(`\`.${o}\` is still written above a panel select in the editor's markup${l?` — the screen's (${a?.path} styles it)`:""}`,i||l,`no component writes \`${o}\` as a className any more${a?`, and ${a.path} styles no \`.${o}\` either`:""} — the rules leaning on it still parse, still look right, and match none of the ${$.length} house-drawn rows`)}}return Z()}export{ie as panelOneHover};
|
|
@@ -6,16 +6,16 @@ import{ruleChecks as v}from"./rule.js";import{isRegistryPath as F,registryFiles
|
|
|
6
6
|
`);if(!P.test(d))return;const r=d.match(/sf-field-label[^>]*>([^<{][^<]*)</),p=d.match(/sf-field-label[^>]*>\{([^}]*)\}/),x=d.match(/<Label>([^<{][^<]*)</),b=d.match(/<Label>\{([^}]*)\}/),h=$=>$.trim()==="label"?"{label}":"{...}",m=r?r[1].trim():p?h(p[1]):x?x[1].trim():b?h(b[1]):"{...}";(o[e.path]??=[]).push(m)})}return o}function M(s){const o=[];let e=0;for(const g of s){const a=g.text.split(`
|
|
7
7
|
`);a.forEach((f,d)=>{if(!/<SettingsField\b/.test(f))return;const r=[];let p=!1;for(let b=d;b<Math.min(d+40,a.length)&&!p;b+=1){let h=a[b];if(h=h.replace(/\/\*[\s\S]*?\*\//g," "),/\/\*/.test(h)&&(h=h.slice(0,h.indexOf("/*"))),/^\s*\*/.test(a[b]))continue;const m=h.indexOf("//");m!==-1&&(h=h.slice(0,m)),r.push(h),h.includes(">")&&(p=!0)}const x=r.join(`
|
|
8
8
|
`);/\blabel=/.test(x)&&(e+=1,!/\bicon=/.test(x)&&o.push(`${g.path}:${d+1} ${f.trim().slice(0,80)}`))})}return{seen:e,bare:o}}function K(s,o){const{check:e,report:g}=v("panel rows use the shared row"),a=o.exceptions??[],{floors:f}=o;{const t={path:"fixture.tsx",text:['import { slider, textList } from "@/lib/puck/controls";','const a = { textList: () => controls.textList("Items", <ListIcon />, x) };','const b = slider("Thickness", 0, 10);','const c = slider("Gap", <GapIcon />, 0, 10);','// slider("In a comment", 0, 10) is not a row'].join(`
|
|
9
|
-
`)},n=G([t],"textList",[]),c=G([t],"slider",[]);e("the reader reads a namespaced call's own arguments, not the arrow's",n.total===1&&n.bare.length===0,JSON.stringify(n)),e("the reader flags a bare row whose neighbour has a glyph, and skips a comment",c.total===2&&c.bare.length===1&&c.bare[0].startsWith("fixture.tsx:3"),JSON.stringify(c))}const r=new Map(s.map(t=>[t.path,t])).get(L);e(o.writesBuilders?`read ${L}, where the builders are declared`:`${L} is not among this repository's files (it calls the builders, it does not declare them)`,!!r===o.writesBuilders);const p=D(s);e(`read this repository's share of the block registry (${p.length} files)`,p.length>0);const x=s.filter(t=>/^components\/[^/]+\.tsx$/.test(t.path)),b=s.filter(t=>/^scripts\/[^/]+\.tsx$/.test(t.path)),h=[...p,...r?[r]:[],...x],m=t=>t.path===L;{const t=h.filter(l=>m(l)||/import \{[^}]*\btoggle\b[^}]*\} from "@\/lib\/puck\/controls"/.test(l.text)),n=t.filter(l=>m(l)||F(l.path));e("only the known files use the toggle builder",t.length===n.length&&(!o.writesBuilders||n.some(m))&&n.some(l=>F(l.path)),t.map(l=>l.path).join("
|
|
9
|
+
`)},n=G([t],"textList",[]),c=G([t],"slider",[]);e("the reader reads a namespaced call's own arguments, not the arrow's",n.total===1&&n.bare.length===0,JSON.stringify(n)),e("the reader flags a bare row whose neighbour has a glyph, and skips a comment",c.total===2&&c.bare.length===1&&c.bare[0].startsWith("fixture.tsx:3"),JSON.stringify(c))}const r=new Map(s.map(t=>[t.path,t])).get(L);e(o.writesBuilders?`read ${L}, where the builders are declared`:`${L} is not among this repository's files (it calls the builders, it does not declare them)`,!!r===o.writesBuilders);const p=D(s);e(`read this repository's share of the block registry (${p.length} files)`,p.length>0);const x=s.filter(t=>/^components\/[^/]+\.tsx$/.test(t.path)),b=s.filter(t=>/^scripts\/[^/]+\.tsx$/.test(t.path)),h=[...p,...r?[r]:[],...x],m=t=>t.path===L;{const t=h.filter(l=>m(l)||/import \{[^}]*\btoggle\b[^}]*\} from "@\/lib\/puck\/controls"/.test(l.text)),n=t.filter(l=>m(l)||F(l.path));e("only the known files use the toggle builder",t.length===n.length&&(!o.writesBuilders||n.some(m))&&n.some(l=>F(l.path)),t.map(l=>l.path).join(" · "));const c=[];let u=0;for(const l of n)l.text.split(`
|
|
10
10
|
`).forEach((w,i)=>{const y=w.trim();B(y)||/classList\.toggle/.test(w)||/^export function toggle\(/.test(y)||/(^|[^.\w])toggle\(/.test(w)&&(u+=1,!(N.test(w)||/<FieldGlyph\b/.test(w))&&c.push(`${l.path}:${i+1} ${y.slice(0,80)}`))});e("every toggle() row carries a glyph",c.length===0,`
|
|
11
11
|
${c.join(`
|
|
12
|
-
`)}`),e(
|
|
12
|
+
`)}`),e(`…and there are rows to check (${u} toggle() call sites, at least ${f.toggle})`,u>=f.toggle,"the scan is reading nothing")}const $=[{builder:"textList",inControls:!1,accept:[/TICKER_ITEMS_ROW\.icon/],name:"every textList() row carries a glyph"},{builder:"segmented",inControls:!0,accept:[/\bnoGlyph\(/],name:"every segmented() row carries a glyph (or a ruled noGlyph)"},{builder:"select",inControls:!0,accept:[/\btextKindGlyph\(/,/\bnoGlyph\(/],name:"every select() row carries a glyph (or a ruled noGlyph)"},{builder:"slider",inControls:!0,accept:[/\bnoGlyph\(/],name:"every slider() row carries a glyph"}];for(const t of $){const n=[...p,...t.inControls&&r?[r]:[],...b].filter(l=>t.inControls&&m(l)||F(l.path)||T(l.text,t.builder)),{bare:c,total:u}=G(n,t.builder,t.accept);e(t.name,c.length===0,`
|
|
13
13
|
${c.join(`
|
|
14
|
-
`)}`),e(
|
|
14
|
+
`)}`),e(`…and the ${t.builder} scan actually read rows (${u} call sites in ${n.length} files, at least ${f[t.builder]})`,u>=f[t.builder])}{const t=U(h);for(const i of a)e(`the exception still names a real row — ${i.label}`,(t[i.file]??[]).includes(i.label),`${i.file} no longer draws it`);const n={};for(const[i,y]of Object.entries(t)){const k=y.filter(O=>!a.some(j=>j.file===i&&j.label===O));k.length&&(n[i]=k)}const c=i=>i.slice().sort(),u=[];for(const[i,y]of Object.entries(n)){const k=I[i];if(!k){u.push(`${i} — hand-rolls ${y.length} row(s) and is not in PENDING`);continue}const O=c(y),j=c(k);(O.length!==j.length||O.some((C,R)=>C!==j[R]))&&u.push(`${i} — found [${O.join(" · ")}] but PENDING says [${j.join(" · ")}]`)}e("no NEW hand-rolled settings row",u.length===0,`
|
|
15
15
|
${u.join(`
|
|
16
|
-
`)}`);const l=Object.keys(I).filter(i=>!n[i]);e("no PENDING entry has outlived its subject",l.length===0,l.join("
|
|
16
|
+
`)}`);const l=Object.keys(I).filter(i=>!n[i]);e("no PENDING entry has outlived its subject",l.length===0,l.join(" · "));const w=Object.values(n).reduce((i,y)=>i+y.length,0);e("the hand-rolled rows are GONE — the count is zero",w===0,`${w}`),e("…and no file is left hand-rolling one",Object.keys(I).length===0,Object.keys(I).join(" · ")),e(`…and the rulings are exactly ${o.rulings} (a new one is argued in review, not added)`,a.length===o.rulings&&a.every(i=>i.why.trim().length>40),`${a.length}`),e("…and BlogSource is not among them, because its row is gone",!a.some(i=>i.file.endsWith("BlogSource.tsx")))}{const t=Object.values(S).reduce((n,c)=>n+c,0);if(e("every builder is finished — nothing left to count",t===0,`${t}`),e("…and the emptiness is because FOUR builders are done, not because the list was cleared",E.length===8&&["textList","segmented","select","slider"].every(n=>E.includes(n)),E.join(" · ")),e("…and a finished builder has left the list",E.every(n=>!(n in S)),E.join(" · ")),r){const n=Object.keys(S).filter(c=>!new RegExp(`(export )?function ${c}\\(`).test(r.text));e(`…and every builder it names is really declared in ${L}`,n.length===0,n.join(" · "))}}if(r){const t=r.text,n=t.indexOf("export function backgroundField("),c=n===-1?"":t.slice(n),u=c.slice(0,c.indexOf(`
|
|
17
17
|
}
|
|
18
|
-
`));e("backgroundField is declared",n!==-1),e("backgroundField renders SettingsField",/<SettingsField/.test(u)),e("
|
|
18
|
+
`));e("backgroundField is declared",n!==-1),e("backgroundField renders SettingsField",/<SettingsField/.test(u)),e("…and hand-rolls no row of its own",u.length>0&&!/className="sf-field"/.test(u)),e("…and passes `BackgroundIcon`, not merely some glyph",/icon=\{<BackgroundIcon \/>\}/.test(u));const l=t.indexOf("export function cardImageBackgroundField("),w=l===-1?"":t.slice(l);e("…and cardImageBackgroundField still passes `GroundIcon`",/icon=\{<GroundIcon \/>\}/.test(w.slice(0,w.indexOf(`
|
|
19
19
|
}
|
|
20
20
|
`))))}{const{seen:t,bare:n}=M(x);e(`the hand-rolled row scan read something (${t} hand-drawn SettingsField rows across ${x.length} files, at least ${f.handDrawn})`,t>=f.handDrawn),e("no hand-rolled SettingsField row goes without a glyph",n.length===0,`
|
|
21
21
|
${n.join(`
|