linear-grab 0.27.1 → 0.27.2

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.
@@ -37,7 +37,7 @@ const flag = (name, fallback) => {
37
37
  const PORT = Number(flag('--port', '4577'));
38
38
  const DIR = flag('--dir', process.cwd());
39
39
  const CLAUDE_BIN = flag('--claude', 'claude');
40
- const VERSION = '0.27.1';
40
+ const VERSION = '0.27.2';
41
41
 
42
42
  // ---- audit subcommand dispatch ---------------------------------------------
43
43
  // `npx linear-grab-bridge audit` is a headless design gate — it sweeps a
@@ -3,4 +3,4 @@
3
3
  `)}var L=!1,R=!1,z=[],B=-1/0,V=200,Te=4e3,Ee=100;function H(){B=W()}var U=[`scroll`,`wheel`,`pointermove`];function W(){return typeof performance<`u`?performance.now():Date.now()}function De(e,t){let n=e&&typeof e==`object`?e:{},r=t&&typeof t==`object`?t:{},i=new Set([...Object.keys(n),...Object.keys(r)]),a=[];for(let e of i){if(e===`children`)continue;let t=n[e],i=r[e];Object.is(t,i)||(typeof t==`function`&&typeof i==`function`?a.push(`prop:${e}(fn)`):t&&i&&typeof t==`object`&&typeof i==`object`&&Oe(t,i)?a.push(`prop:${e}(ref)`):a.push(`prop:${e}`))}return a}function Oe(e,t){let n=Object.keys(e),r=Object.keys(t);if(n.length!==r.length)return!1;for(let r of n)if(!Object.is(e[r],t[r]))return!1;return!0}function ke(e){let t=[],n={};de(e,(e,r)=>{if(r===`unmount`||!ae(e))return;let i=ce(e.type)??`Anonymous`;r===`mount`&&(n[i]=(n[i]??0)+1);let a=0;try{a=se(e).selfTime||0}catch{}let o=[];if(r===`update`&&e.alternate)try{o=De(e.alternate.memoizedProps,e.memoizedProps)}catch{o=[`prop:(unknown)`]}let s=null;try{let t=oe(e)?.stateNode;t&&typeof WeakRef<`u`&&t instanceof Element&&(s=new WeakRef(t))}catch{}t.push({name:i,selfTime:a,phase:r===`mount`?`mount`:`update`,changes:o,el:s})}),t.sort((e,t)=>t.selfTime-e.selfTime);let r=t.slice(0,V),i=Math.max(0,t.length-V),a=0;for(let e of t)a+=e.selfTime;let o=W(),s=o-B<=Ee;z.push({at:o,duration:a,entries:r,droppedEntries:i,mounts:n,nearInput:s})}function Ae(){if(!L&&!(typeof window>`u`)){L=!0;try{fe({onCommitFiberRoot:(e,t)=>{if(R&&!(z.length>=Te))try{ke(t)}catch{}}})}catch{}}}function je(){if(!(typeof window>`u`)&&!R){z=[],B=-1/0,Ae();try{for(let e of U)window.addEventListener(e,H,{capture:!0,passive:!0})}catch{}R=!0}}function Me(){R=!1;try{for(let e of U)window.removeEventListener(e,H,{capture:!0})}catch{}let e=z;return z=[],B=-1/0,e}function Ne(){return R}var G={budgets:{selfMs:2,commitMs:8},rules:{}},K=/^###\s+(R\d+)\s*[·.\-–—:]?\s*`([^`]+)`.*?\((error|warn)\)\s*$/;function q(e){return K.test(e.trim())}function J(e,t){let n=e.split(`
4
4
  `),r=RegExp(`^\\s*\\*\\*${t.source}[^*]*:\\*\\*\\s*(.*)$`,`i`),i=/^\s*\*\*[^*]+:\*\*/,a=0;for(;a<n.length&&!r.test(n[a]);a++);if(a>=n.length)return``;let o=n[a].replace(r,`$1`),s=[];for(o.trim()&&s.push(o.trim()),a+=1;a<n.length;a++){let e=n[a];if(i.test(e)||/^###\s/.test(e)||/^##\s/.test(e)||/^---\s*$/.test(e))break;if(e.trim())s.push(e.trim());else if(s.length){let e=a+1;for(;e<n.length&&!n[e].trim();)e++;if(e>=n.length||i.test(n[e])||/^###\s|^##\s|^---\s*$/.test(n[e]))break}}return Pe(s.join(` `)).trim()}function Pe(e){return e.replace(/\*\*([^*]+)\*\*/g,`$1`).replace(/__([^_]+)__/g,`$1`).replace(/(^|[\s(])[*_]([^*_\s][^*_]*?)[*_]([\s.,;:)]|$)/g,`$1$2$3`).replace(/\s{2,}/g,` `)}function Y(e){let t=e.match(/≤(\d+(?:\.\d+)?)ms self/),n=e.match(/commit >(\d+(?:\.\d+)?)ms/)??e.match(/>(\d+(?:\.\d+)?)ms of React work/);return{selfMs:t?parseFloat(t[1]):G.budgets.selfMs,commitMs:n?parseFloat(n[1]):G.budgets.commitMs}}function X(e){let t=Y(e),n={},r=e.split(`
5
5
  `),i=0;for(;i<r.length;){let e=r[i];if(!q(e)){i++;continue}let t=e.trim(),a=[],o=i+1;for(;o<r.length;o++){let e=r[o];if(q(e)||/^##\s/.test(e.trim())||/^---\s*$/.test(e.trim()))break;a.push(e)}try{let e=t.match(K);if(e){let[,t,r,i]=e,o=a.join(`
6
- `);n[t]={id:t,slug:r,severity:i===`warn`?`warn`:`error`,ruleText:J(o,/Rule/),fixText:J(o,/Fix/),detectText:J(o,/Detect/)}}}catch{}i=o}return{budgets:t,rules:n}}async function Z(e){let t=window.__REACT_GRAB__;if(!t||!t.getDisplayName&&!t.getSource)return;let n=new Map;for(let t of e){let e=t.el?.deref();if(!e)continue;let r=n.get(e);r?r.push(t):n.set(e,[t])}let r=300;for(let[e,i]of n){if(r--<=0)break;let n=null,a=null;try{n=t.getDisplayName?.(e)??null}catch{}try{let n=await t.getSource?.(e);n?.filePath&&(a=`${n.filePath}${n.lineNumber==null?``:`:${n.lineNumber}`}`)}catch{}if(!(!n&&!a))for(let e of i)!e.component&&n&&(e.component=n),a&&(e.source=a)}}function Q(e){return e.map(({el:e,...t})=>t)}function Fe(e,t){return!t||t.selfMs==null&&t.commitMs==null?e:{...e,budgets:{selfMs:t.selfMs??e.budgets.selfMs,commitMs:t.commitMs??e.budgets.commitMs}}}function $(){je()}async function Ie(e,t,n){let r=Me(),i=e?X(e):G;i=Fe(i,t);let a=ge(r,i,{ignore:n});return await Z(a),Q(a)}async function Le(){let e=ve(document);return await Z(e),Q(e)}function Re(e){return I(e)}function ze(e,t){return we(e,t?X(t):G)}if(window.__RENDER_SCAN__={version:`0.27.1`,start:$,recording:Ne,finish:Ie,snapshot:Le,report:Re,prompt:ze},window.__RENDER_SCAN_AUTOSTART__){try{m()}catch{}$()}})();
6
+ `);n[t]={id:t,slug:r,severity:i===`warn`?`warn`:`error`,ruleText:J(o,/Rule/),fixText:J(o,/Fix/),detectText:J(o,/Detect/)}}}catch{}i=o}return{budgets:t,rules:n}}async function Z(e){let t=window.__REACT_GRAB__;if(!t||!t.getDisplayName&&!t.getSource)return;let n=new Map;for(let t of e){let e=t.el?.deref();if(!e)continue;let r=n.get(e);r?r.push(t):n.set(e,[t])}let r=300;for(let[e,i]of n){if(r--<=0)break;let n=null,a=null;try{n=t.getDisplayName?.(e)??null}catch{}try{let n=await t.getSource?.(e);n?.filePath&&(a=`${n.filePath}${n.lineNumber==null?``:`:${n.lineNumber}`}`)}catch{}if(!(!n&&!a))for(let e of i)!e.component&&n&&(e.component=n),a&&(e.source=a)}}function Q(e){return e.map(({el:e,...t})=>t)}function Fe(e,t){return!t||t.selfMs==null&&t.commitMs==null?e:{...e,budgets:{selfMs:t.selfMs??e.budgets.selfMs,commitMs:t.commitMs??e.budgets.commitMs}}}function $(){je()}async function Ie(e,t,n){let r=Me(),i=e?X(e):G;i=Fe(i,t);let a=ge(r,i,{ignore:n});return await Z(a),Q(a)}async function Le(){let e=ve(document);return await Z(e),Q(e)}function Re(e){return I(e)}function ze(e,t){return we(e,t?X(t):G)}if(window.__RENDER_SCAN__={version:`0.27.2`,start:$,recording:Ne,finish:Ie,snapshot:Le,report:Re,prompt:ze},window.__RENDER_SCAN_AUTOSTART__){try{m()}catch{}$()}})();
@@ -1,3 +1,3 @@
1
1
  (function(){function e(e){return e.split(`,`).map(e=>{let t=e.trim();return t.endsWith(`ms`)?parseFloat(t)||0:t.endsWith(`s`)?(parseFloat(t)||0)*1e3:0})}var t=new Set(`width.height.min-width.min-height.max-width.max-height.top.left.right.bottom.inset.margin.margin-top.margin-right.margin-bottom.margin-left.padding.padding-top.padding-right.padding-bottom.padding-left.font-size.line-height.letter-spacing.box-shadow.filter.background-position.grid-template-columns.grid-template-rows.flex-basis.gap`.split(`.`));function n(e){return e.includes(`all`)?[`all (includes layout/paint props)`]:e.filter(e=>t.has(e))}function r(e){let t=i(e);if(t===`ease-in`||t===`ease-in-out`)return t;let n=t.match(/cubic-bezier\(\s*([\d.]+)\s*,\s*(-?[\d.]+)/);if(n){let e=parseFloat(n[1]),r=parseFloat(n[2]);if(e>=.4&&r<e/2)return t}return null}function i(e){let t=0;for(let n=0;n<e.length;n++){let r=e[n];if(r===`(`)t++;else if(r===`)`)t--;else if(r===`,`&&t===0)return e.slice(0,n).trim()}return e.trim()}var a=`[data-slot*="content"], [role="menu"], [role="dialog"], [role="listbox"]`,o=new Set([`width`,`height`,`flex-basis`,`grid-template-columns`,`grid-template-rows`,`grid-template-areas`]);function s(e,t){if(e instanceof SVGElement)return!0;try{if(e.closest(`svg`))return!0}catch{}try{if(e.closest(a))return!0}catch{}try{if(e.closest(`[data-panel]`))return!t||t.length===0||t.every(e=>o.has(e))}catch{}return!1}function c(e){let t=e.id?`#${e.id}`:``,n=typeof e.className==`string`&&e.className?`.${e.className.split(/\s+/).filter(Boolean).slice(0,2).join(`.`)}`:``;return`${e.tagName.toLowerCase()}${t}${n}`}function l(e,t,n){return{ruleId:e.id,part:e.part,severity:e.severity,description:e.description,selector:c(t),evidence:n,el:typeof WeakRef<`u`?new WeakRef(t):void 0}}function u(e){return e.getBoundingClientRect()}function d(e){return e.split(`/`)[0].trim().split(/\s+/).filter(Boolean).map(e=>e.endsWith(`px`)?parseFloat(e):NaN)}function f(e){let t=e.match(/rgba?\(([^)]+)\)/);return t?t[1].split(`,`).map(e=>e.trim()).join(`,`):null}function p(e,t){let n=t,r=n.maskImage||n.webkitMaskImage||``;if(r&&r!==`none`)return r;let i=(e.getAttribute(`style`)??``).match(/(?:^|;|\s)(?:-webkit-)?mask-image\s*:\s*([^;]+)/i);return i&&i[1].trim()&&i[1].trim()!==`none`?i[1].trim():``}var m=new Set([0,8,12,16]),h={light:f(`rgb(240, 240, 241)`),dark:f(`rgb(35, 35, 37)`)},g=new Set([`#50e3c2`,`#ee0000`,`#f5a623`]),_=`button, a, [role="button"], [role="tab"], [role="menuitem"], [role="option"], input, textarea, select`;function v(e){let t=[],n=e=>{for(let r of Array.from(e)){let e=r;if(e.cssRules&&e.cssRules.length){n(e.cssRules);continue}let i=r.selectorText;i&&i.includes(`:hover`)&&t.push(i.replace(/:hover/g,``))}};for(let t of Array.from(e.styleSheets))try{n(t.cssRules)}catch{}return t}function y(e,t){for(let n of t)if(n.trim())try{if(e.matches(n))return!0}catch{}return!1}function b(e){return!e.matches(`button, [role="button"]`)||e.querySelectorAll(`svg`).length===0?!1:(e.textContent??``).trim().length===0}function x(e){let t=e.parentElement;for(;t;){try{let e=S(t).overflowY;if(e===`auto`||e===`scroll`)return t}catch{}t=t.parentElement}return null}var S=e=>getComputedStyle(e),C={id:`radius-vocabulary`,part:`§40`,severity:`error`,description:`§40: radius vocabulary is {8, 12, 16, full} — never rounded-md/[4px]/[6px]/[10px]. Off-vocabulary radius.`,check(e){let t=[];for(let n of e.els){let r=e.css(n).borderRadius;if(!r||r===`0px`)continue;let i=d(r);if(i.some(e=>Number.isNaN(e)))continue;let a=Math.max(...i);if(a===0)continue;let o=u(n),s=Math.min(o.width||0,o.height||0);s>0&&a>=s/2-.5||i.every(e=>m.has(e))||t.push(l(this,n,`border-radius: ${r}`))}return t}},w={id:`row-radius-token`,part:`§40`,severity:`error`,description:`§40: list rows consume --row-radius (8px) or 0 — no bespoke per-surface row radius.`,check(e){let t=[];for(let n of e.els){let r=u(n).height;if(r<26||r>50||!x(n))continue;let i=n.parentElement;if(!i||Array.from(i.children).filter(e=>{let t=u(e);return Math.abs(t.height-r)<2}).length<3)continue;let a=d(e.css(n).borderRadius);if(a.some(e=>Number.isNaN(e)))continue;let o=Math.max(0,...a);o===0||o===8||t.push(l(this,n,`row radius ${e.css(n).borderRadius} (want 0 or var(--row-radius)=8px)`))}return t}},T={id:`active-fill-token`,part:`§11`,severity:`error`,description:`§11: active/selected fill must be var(--selected) (theme-safe) — never bg-secondary (#F0F0F1 / #232325).`,check(e){let t=[],n=null;try{let t=e.root.documentElement??document.documentElement;t&&(n=f(e.css(t).getPropertyValue(`--selected`).trim()))}catch{}let r=[h.light,h.dark].filter(Boolean);if(n&&r.includes(n)){let t=e.root.documentElement??document.documentElement;return[l({id:this.id,part:this.part,severity:`warn`,description:`§11: active-fill-token unverifiable — --selected resolves EQUAL to bg-secondary this theme; cannot distinguish the token from the anti-pattern.`},t??e.els[0],`--selected == bg-secondary (${n})`)]}for(let n of e.els){if(!(n.matches(`[role="tab"], [role="button"], [aria-selected], [role="option"]`)||n.closest(`[role="tablist"], [role="menu"], [data-rail], nav`)))continue;let i=f(e.css(n).backgroundColor);i&&r.includes(i)&&t.push(l(this,n,`background bg-secondary (${e.css(n).backgroundColor}) — use var(--selected)`))}return t}},E={id:`inline-style-state`,part:`§40`,severity:`error`,description:`§40: the shared row-action class owns ALL visual states; inline styles carry LAYOUT ONLY — an inline background/color kills the class state.`,check(e){let t=[];for(let n of e.els){let e=typeof n.className==`string`?n.className:``;if(!/pb-row-action|row-action/.test(e))continue;let r=n.getAttribute(`style`)??``;/(^|;|\s)(background|color)\s*:/.test(r)&&t.push(l(this,n,`inline style="${r}" on a row-action element`))}return t}},D={id:`icon-button-no-hover`,part:`§40`,severity:`warn`,description:`§40/Part 10: every interactive control changes color on hover — icon-only button with no :hover rule and no .pb-row-action.`,check(e){let t=[],n=v(e.root.styleSheets?e.root:document);for(let r of e.els){if(!b(r))continue;let i=typeof r.className==`string`?r.className:``;/pb-row-action|row-action/.test(i)||r.hasAttribute(`data-framer-appear-id`)||Array.from(r.attributes).some(e=>e.name.startsWith(`data-framer`))||e.css(r).willChange.includes(`transform`)||y(r,n)||t.push(l(this,r,`icon-only ${r.tagName.toLowerCase()} with no hover rule / .pb-row-action`))}return t}},O={id:`icon-button-no-tooltip`,part:`§7`,severity:`warn`,description:`§7: every icon-only button gets the shared Tooltip — needs aria-describedby / data-slot="tooltip-trigger" (menu-row title exempt).`,check(e){let t=[];for(let n of e.els)b(n)&&(n.hasAttribute(`aria-describedby`)||n.getAttribute(`data-slot`)===`tooltip-trigger`||n.closest(`[data-slot="tooltip-trigger"]`)||n.closest(`[role="menuitem"]`)&&n.hasAttribute(`title`)||t.push(l(this,n,`icon-only ${n.tagName.toLowerCase()} with no tooltip trigger / aria-describedby`)));return t}},k={id:`unvirtualized-scroller`,part:`§0`,severity:`error`,description:`§0: every dynamic-length scrolling list virtualizes — a tall scroller with 60+ direct children is unvirtualized.`,check(e){let t=[];for(let n of e.els){let r=e.css(n);if(![r.overflowY,r.overflowX,r.overflow].some(e=>e===`auto`||e===`scroll`)||n.scrollHeight<=2*n.clientHeight)continue;let i=n.children.length;i<=60||t.push(l(this,n,`${i} direct children, scrollHeight ${n.scrollHeight} > 2×clientHeight ${n.clientHeight}`))}return t}},A={id:`scroll-no-fade`,part:`§1`,severity:`warn`,description:`§1: a horizontal scroller needs a truthful scroll fade — no scroll-fade-x/scroll-fade-x-js class and no mask-image.`,check(e){let t=[];for(let n of e.els){let r=e.css(n).overflowX;if(r!==`auto`&&r!==`scroll`||n.clientWidth<24||n.scrollWidth<=n.clientWidth+8)continue;let i=typeof n.className==`string`?n.className:``;/scroll-fade-x(-js)?/.test(i)||p(n,e.css(n))||t.push(l(this,n,`horizontal scroller (scrollWidth ${n.scrollWidth} > clientWidth ${n.clientWidth}) with no fade`))}return t}},j={id:`phantom-fade`,part:`§1`,severity:`warn`,description:`§1: a fade must NEVER paint when nothing is hidden — mask-image present but scrollWidth ≤ clientWidth (the fade lies).`,check(e){let t=[];for(let n of e.els)p(n,e.css(n))&&(n.scrollWidth>n.clientWidth||t.push(l(this,n,`mask-image fade but scrollWidth ${n.scrollWidth} ≤ clientWidth ${n.clientWidth}`)));return t}},M={id:`scrollbar-flush`,part:`Part 5`,severity:`warn`,description:`Part 5: scrollables carry asymmetric right padding so content clears the scrollbar — padding-right < 8px while overflowing vertically with content reaching the right edge.`,check(e){let t=[];for(let n of e.els){let r=e.css(n);if(![r.overflowY,r.overflow].some(e=>e===`auto`||e===`scroll`)||n.scrollHeight<=n.clientHeight)continue;let i=parseFloat(r.paddingRight)||0;if(i>=8)continue;let a=u(n);Array.from(n.children).some(e=>{let t=u(e);return a.right-t.right<=4})&&t.push(l(this,n,`padding-right ${i}px (<8px) with content flush to the scrollbar`))}return t}},N=/^("?(Geist|Geist Mono)"?|ui-monospace|ui-sans-serif|-apple-system|system-ui)/i,P={id:`font-family`,part:`Part 5`,severity:`warn`,description:`Part 5: Geist / Geist Mono ONLY — computed font-family does not start with the Geist / ui-monospace fallback chain.`,check(e){let t=[];for(let n of e.els){if(!K(n))continue;let r=e.css(n).fontFamily;r&&(N.test(r.trim())||t.push(l(this,n,`font-family: ${r}`)))}return t}},F={id:`uppercase-label`,part:`§25`,severity:`error`,description:`§25: group/section labels are sentence-case — never uppercase (text-transform: uppercase on small <13px text).`,check(e){let t=[];for(let n of e.els){if(!K(n))continue;let r=e.css(n);r.textTransform===`uppercase`&&((parseFloat(r.fontSize)||0)>=13||t.push(l(this,n,`text-transform: uppercase at ${r.fontSize}`)))}return t}},I={id:`hardcoded-status-hex`,part:`Part 5`,severity:`warn`,description:`Part 5: raw hex only for the status trio (#50E3C2/#EE0000/#F5A623) — a saturated non-token color on small UI text.`,check(e){let t=[],n=new Set;for(let e of g){let t=f(`rgb(${parseInt(e.slice(1,3),16)}, ${parseInt(e.slice(3,5),16)}, ${parseInt(e.slice(5,7),16)})`);t&&n.add(t)}try{let t=e.root,r=t.documentElement;if(r&&t.body){let i=e.css(r),a=t.createElement(`span`);a.setAttribute(`data-linear-grab`,`true`),a.style.display=`none`,t.body.appendChild(a);for(let e=0;e<i.length;e++){let t=i[e];if(!t.startsWith(`--`))continue;let r=i.getPropertyValue(t).trim();if(!r||r.length>40||(a.style.color=``,a.style.color=r,!a.style.color))continue;let o=f(getComputedStyle(a).color);o&&n.add(o)}a.remove()}}catch{}for(let r of e.els){if(!K(r))continue;let i=(r.getAttribute(`style`)??``).match(/#[0-9a-fA-F]{6}/g)??[],a=e.css(r);if((parseFloat(a.fontSize)||0)>=16)continue;for(let e of i)g.has(e.toLowerCase())||t.push(l(this,r,`hardcoded ${e} (not the brand status trio)`));let o=f(a.color);if(o&&i.length===0&&!n.has(o)){let[e,n,i]=o.split(`,`).map(e=>parseFloat(e));Number.isFinite(e)&&q(e,n,i)&&t.push(l(this,r,`saturated non-token color ${a.color} on small UI text`))}}return t}},L={id:`editor-shadow`,part:`Part 5`,severity:`error`,description:`Part 5: editor surfaces are FLAT — box-shadow that is not a 0.5px hairline ring / approved popover/card/input token shadow.`,check(e){let t=[];if(!(e.root.querySelector?.(`[data-editor-surface]`)||typeof location<`u`&&/\/editor\//.test(location.pathname)))return t;for(let n of e.els){if(!n.closest(`[data-editor-surface]`)&&!/\/editor\//.test(typeof location<`u`?location.pathname:``))continue;let r=e.css(n).boxShadow;!r||r===`none`||Y(r)||X(r)||t.push(l(this,n,`box-shadow: ${r}`))}return t}},R={id:`focusring-clip`,part:`§24`,severity:`warn`,description:`§24/Part 5: focus rings never clip — a focusable element whose nearest overflow-hidden ancestor sits <4px from its border box.`,check(e){let t=[];for(let n of e.els){if(!n.matches(_)&&n.getAttribute(`tabindex`)==null)continue;let r=n.parentElement;for(;r;){let t=e.css(r);if([t.overflow,t.overflowX,t.overflowY].some(e=>e===`hidden`))break;r=r.parentElement}if(!r)continue;let i=u(n),a=u(r);if(i.width===0&&i.height===0)continue;let o=Math.min(i.left-a.left,a.right-i.right,i.top-a.top,a.bottom-i.bottom);o>=4||t.push(l(this,n,`overflow-hidden ancestor ${o.toFixed(1)}px from border box — focus ring will clip`))}return t}},z={id:`fixed-width-in-pane`,part:`§24`,severity:`warn`,description:`§24: no fixed field widths in resizable panes — w-[Npx] (N ≥ 200) inside a [data-panel] / flex-basis sibling clips at the pane edge.`,check(e){let t=[];for(let n of e.els){if(!n.closest(`[data-panel]`))continue;let e=typeof n.className==`string`?n.className:``,r=n.getAttribute(`style`)??``,i=e.match(/w-\[(\d+)px\]/)??r.match(/(?:^|;|\s)width\s*:\s*(\d+)px/);if(!i)continue;let a=parseFloat(i[1]);a<200||t.push(l(this,n,`fixed width ${a}px inside a resizable pane — use w-full max-w-[${a}px]`))}return t}},ee={id:`menu-icons-all-or-none`,part:`§21`,severity:`error`,description:`§21: within one menu ALL items carry a leading icon or NONE do — a mixed set reads broken.`,check(e){let t=[],n=Array.from(e.root.querySelectorAll(`[role="menu"]`));for(let e of n){let n=Array.from(e.querySelectorAll(`[role="menuitem"]`));if(n.length<2)continue;let r=n.filter(e=>e.querySelector(`svg`)).length;r===0||r===n.length||t.push(l(this,e,`${r}/${n.length} menu items have a leading icon`))}return t}},B={id:`menu-icon-mixed-weight`,part:`§21`,severity:`warn`,description:`§21: menu icons are ONE stroke family — some filled (fill=currentColor) and some stroke-based, or rendered sizes differing >2px, mixes two design systems.`,check(e){let t=[],n=Array.from(e.root.querySelectorAll(`[role="menu"]`));for(let e of n){let n=Array.from(e.querySelectorAll(`[role="menuitem"] svg`));if(n.length<2)continue;let r=0,i=0,a=[];for(let e of n){Array.from(e.querySelectorAll(`path, circle, rect, polygon`)).some(e=>{let t=(e.getAttribute(`fill`)??``).toLowerCase();return t===`currentcolor`||t&&t!==`none`})?r++:i++;let t=u(e);a.push(Math.max(t.width,t.height))}let o=Math.max(...a),s=Math.min(...a),c=r>0&&i>0,d=o>0&&o-s>2;!c&&!d||t.push(l(this,e,c?`${r} filled + ${i} stroke-based icons in one menu`:`icon sizes differ by ${(o-s).toFixed(1)}px`))}return t}},V={id:`chrome-selectable`,part:`Part 5`,severity:`warn`,description:`Part 5: chrome (rails/pills/tabs) is select-none — a rail/pill/tab without user-select:none.`,check(e){let t=[];for(let n of e.els){if(!(n.matches(`[role="tab"]`)||n.closest(`[role="tablist"], [data-rail], nav`)||/\bpill\b/.test(typeof n.className==`string`?n.className:``)))continue;let r=e.css(n).userSelect||e.css(n).webkitUserSelect;r!==`none`&&t.push(l(this,n,`chrome element without user-select:none (user-select: ${r||`auto`})`))}return t}},H={id:`cursor-mismatch`,part:`Part 5`,severity:`warn`,description:`Part 5: cursor semantics — buttons get pointer, static text gets default. A button with cursor:text, or static text with cursor:pointer and no interactive ancestor.`,check(e){let t=[];for(let n of e.els){let r=e.css(n).cursor;if(n.matches(`button, [role="button"]`)&&r===`text`){t.push(l(this,n,`interactive control with cursor:text`));continue}if(r===`pointer`&&n.matches(`p, h1, h2, h3, h4, h5, h6, span`)){if(n.closest(`button, a, [role="button"], [onclick], label`)||n.matches(`button, a, [role="button"]`))continue;t.push(l(this,n,`static ${n.tagName.toLowerCase()} with cursor:pointer and no interactive ancestor`))}}return t}};function U(e){let t=e.transitionProperty.split(`,`).map(e=>e.trim()).filter(Boolean);return t.length===1&&t[0]===`none`?[]:t}var W=[C,w,T,E,D,O,k,A,j,M,P,F,I,L,R,z,ee,B,V,H,{id:`transition-all`,part:`§42`,severity:`error`,description:`§42: never transition-all — scope to what changes (transition-colors / transition-[transform,...]).`,check(e){let t=[];for(let n of e.els){if(!n.matches(_))continue;let r=U(e.css(n));r.includes(`all`)&&(s(n,r)||t.push(l(this,n,`transition-property: all`)))}return t}},{id:`transition-paint-prop`,part:`§42`,severity:`error`,description:`§42: never transition layout/paint props (box-shadow, width, height, top/left, margin, padding) — they reflow/repaint every frame; focus rings SNAP, shadows animate a pseudo-element opacity.`,check(e){let t=[];for(let r of e.els){let i=U(e.css(r)),a=n(i).filter(e=>e!==`all (includes layout/paint props)`);a.length!==0&&(s(r,i)||t.push(l(this,r,`transitions layout/paint props: ${a.join(`, `)}`)))}return t}},{id:`transition-too-slow`,part:`§42`,severity:`error`,description:`§42: interaction feedback is ≤100ms (duration-75 for small controls) — the 100ms theme default is fine; an explicit duration-150+ crept in.`,check(t){let n=[];for(let r of t.els){if(!r.matches(_))continue;let i=t.css(r),a=U(i);if(a.length===0||s(r,a))continue;let o=e(i.transitionDuration),c=Math.max(0,...o);c<=100||n.push(l(this,r,`transition-duration ${c}ms (>100ms) on [${a.join(`, `)}]`))}return n}},{id:`ease-in-feedback`,part:`§42`,severity:`warn`,description:`§42: house curve is cubic-bezier(.2,0,.1,1) ease-out — never ease-in / ease-in-out (or an ease-in-shaped bezier) on feedback.`,check(e){let t=[];for(let n of e.els){if(!n.matches(_))continue;let i=e.css(n),a=U(i);if(a.length===0||s(n,a))continue;let o=r(i.transitionTimingFunction);o&&t.push(l(this,n,`transition-timing-function: ${o}`))}return t}},{id:`press-instant-rule-missing`,part:`§42`,severity:`error`,description:"§42: the global press-instant rule (globals.css) must exist — `button:active{transition-duration:0s}` (or transition:none). Absent = press feedback animates instead of snapping.",check(e){let t=e.root.styleSheets?e.root:document,n=!1,r=e=>{for(let t of Array.from(e)){if(n)return;let e=t;if(e.cssRules&&e.cssRules.length){r(e.cssRules);continue}let i=t,a=i.selectorText;if(a&&/:active\b/.test(a)&&/(^|[\s,])(button|a|\[role=("|')?(button|tab|menuitem|option)("|')?\])/i.test(a))try{let e=i.style.transitionDuration,t=i.style.transition;if(e===`0s`||/(^|\s)none(\s|$)/.test(t)||/\b0s\b/.test(t)){n=!0;return}}catch{}}};try{for(let e of Array.from(t.styleSheets)){if(n)break;try{r(e.cssRules)}catch{}}}catch{}if(n)return[];let i=e.root.documentElement??e.els[0]??document.documentElement;return[l(this,i,"no `button:active { transition-duration: 0s }` rule on this page")]}}],G=[];function K(e){for(let t of Array.from(e.childNodes))if(t.nodeType===3&&(t.textContent??``).trim().length>0)return!0;return!1}function q(e,t,n){let r=Math.max(e,t,n);return r<40?!1:r-Math.min(e,t,n)>60}function J(e){let t=[],n=0,r=0;for(let i=0;i<e.length;i++){let a=e[i];a===`(`?n++:a===`)`?n--:a===`,`&&n===0&&(t.push(e.slice(r,i).trim()),r=i+1)}return t.push(e.slice(r).trim()),t.filter(Boolean)}function Y(e){return J(e).every(e=>{if(/\btransparent\b/.test(e)||/(?:rgba?|hsla?|oklch|oklab|lab|lch|color)\([^)]*[/,]\s*0(?:\.0+)?\s*\)/.test(e))return!0;let t=e.match(/-?\d*\.?\d+(?:px|em|rem)/g)??[];return t.length>0&&t.every(e=>parseFloat(e)===0)})}function X(e){return!!(/\b0px\s+0px\s+0px\s+0?\.?5px\b/.test(e)||/\b0px?\s+0px?\s+0px?\s+0?\.?5px\b/.test(e))}var Z=`#linear-grab-root, [data-linear-grab], [data-react-grab], #claude-agent-glow-border, [id^="react-scan"]`;function Q(e=typeof document<`u`?document:{}){let t=new Map,n=e=>{let n=t.get(e);return n||(n=getComputedStyle(e),t.set(e,n)),n};S=n;let r=Array.from(e.querySelectorAll(`*`)),i=[],a=e.documentElement??null,o=e.body??null,s=(a?.getBoundingClientRect().width??0)>0;for(let e of r){if(e===a||e===o||e.closest(Z))continue;let t=u(e);if(s){if(t.width<2||t.height<2)continue}else if(t.width===0&&t.height===0&&e.children.length===0)continue;i.push(e)}let c={root:e,els:i,css:n},l=[];for(let e of[...W,...G])try{l.push(...e.check(c))}catch{}S=e=>getComputedStyle(e);let d=new Map;for(let e of l){let t=`${e.ruleId}|${e.selector}|${e.evidence}`,n=d.get(t);n?n.count=(n.count??1)+1:d.set(t,e)}return[...d.values()]}function $(e){let t=new Map;for(let n of e){let e=t.get(n.ruleId);e?e.push(n):t.set(n.ruleId,[n])}let n=Array.from(t.entries()).sort((e,t)=>{let n=e[1][0].severity===`error`?0:1,r=t[1][0].severity===`error`?0:1;return n===r?t[1].length===e[1].length?e[0].localeCompare(t[0]):t[1].length-e[1].length:n-r}),r=e.length,i=e.filter(e=>e.severity===`error`).length,a=new Set(e.map(e=>e.page).filter(Boolean)).size>1,o=[`# Slop scan — ${i} error${i===1?``:`s`}, ${r-i} warn (${r} total)`,``];for(let[e,t]of n){let n=t[0],r=t.reduce((e,t)=>e+(t.count??1),0);o.push(`## ${e} — ${n.severity} ×${r} [${n.part}]`),o.push(n.description);for(let e of t){let t=e.component||e.source?` — ${[e.component,e.source].filter(Boolean).join(` @ `)}`:``,n=(e.count??1)>1?` ×${e.count}`:``;o.push(`- ${a&&e.page?`\`${e.page}\` `:``}\`${e.selector}\`${n} — ${e.evidence}${t}`)}o.push(``)}return o.join(`
2
2
  `)}function te(e){return[`Below is a design-system SLOP report from my running app — each finding is a`,"live-DOM violation of the Linear/Protocolbase primitives contract (`Linear","-primitives.md`). Every rule cites its §id. Goal: zero errors. Rules:",``,`1. Fix at the TOKEN / VARIANT level, not the call sites. Most of these come`,` from shared row/button/menu variants — change --row-radius, --selected,`,` .pb-row-action, the Button/Input variant, or the globals.css §42 rules —`,` not one component at a time.`,`2. Cite the primitives §id in your fix (each finding carries it). Radius →`,` §40 vocabulary {8,12,16,full}; active fill → §11 var(--selected); menus →`,` §21 all-or-none icons + one stroke family; uppercase labels → §25;`,` editor flatness → Part 5; scrollers → §0–1 virtualize + truthful fade.`,`3. §42 group (transition-all / paint-prop / too-slow / ease-in / press-`,` instant): scope transitions to what changes, drop box-shadow/width/height`,` from transition lists, keep feedback ≤100ms (duration-75 small controls),`,` use ease-out (cubic-bezier(.2,0,.1,1)), and keep the global`," `button:active{transition-duration:0s}` press-instant rule.",`4. Do NOT touch EXEMPT intentional motion — popover/menu/dialog entry-exit,`,` svg icon micro-motion, and panel width/height transitions are §42-blessed.`,` The scan already excludes them; do not "fix" them.`,`5. 'unverifiable' warnings mean the token collapsed to the anti-pattern this`,` theme — verify the intent in BOTH themes, do not just silence it.`,"6. Findings carry `Component @ file:line` when the fiber source resolved.",` When one file:line repeats across many components it is the nearest-`,` debug-fiber fallback — locate the real code by the selector classes.`,`7. PROOF REQUIRED: after the change I will re-run the scan — it must show 0`,` errors. State what you changed per token/variant; do not claim done blind.`,``,`---`,``,$(e)].join(`
3
- `)}function ne(){return Q(document).map(({el:e,...t})=>t)}async function re(){let e=Q(document),t=window.__REACT_GRAB__;if(t&&(t.getDisplayName||t.getSource)){let n=new Map;for(let t of e){let e=t.el?.deref();if(!e)continue;let r=n.get(e);r?r.push(t):n.set(e,[t])}let r=300;for(let[e,i]of n){if(r--<=0)break;let n=null,a=null;try{n=t.getDisplayName?.(e)??null}catch{}try{let n=await t.getSource?.(e);n?.filePath&&(a=`${n.filePath}${n.lineNumber==null?``:`:${n.lineNumber}`}`)}catch{}if(!(!n&&!a))for(let e of i)e.component=n,e.source=a}}return e.map(({el:e,...t})=>t)}window.__SLOP_SCAN__={version:`0.27.1`,run:ne,runAttributed:re,report:$,prompt:te}})();
3
+ `)}function ne(){return Q(document).map(({el:e,...t})=>t)}async function re(){let e=Q(document),t=window.__REACT_GRAB__;if(t&&(t.getDisplayName||t.getSource)){let n=new Map;for(let t of e){let e=t.el?.deref();if(!e)continue;let r=n.get(e);r?r.push(t):n.set(e,[t])}let r=300;for(let[e,i]of n){if(r--<=0)break;let n=null,a=null;try{n=t.getDisplayName?.(e)??null}catch{}try{let n=await t.getSource?.(e);n?.filePath&&(a=`${n.filePath}${n.lineNumber==null?``:`:${n.lineNumber}`}`)}catch{}if(!(!n&&!a))for(let e of i)e.component=n,e.source=a}}return e.map(({el:e,...t})=>t)}window.__SLOP_SCAN__={version:`0.27.2`,run:ne,runAttributed:re,report:$,prompt:te}})();