linear-grab 0.26.0 → 0.27.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/bin/linear-grab-bridge.mjs +859 -26
- package/bin/render-scan.global.js +6 -0
- package/bin/slop-scan.global.js +1 -1
- package/dist/index.global.js +9 -9
- package/dist/linear-grab.js +501 -466
- package/dist/render-scan.global.js +6 -0
- package/dist/slop-scan.global.js +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
(function(){var e=`bippy-0.5.43`,t=Object.defineProperty,n=Object.prototype.hasOwnProperty,r=()=>{},i=e=>{try{Function.prototype.toString.call(e).indexOf(`^_^`)>-1&&setTimeout(()=>{throw Error(`React is running in production mode, but dead code elimination has not been applied. Read how to correctly configure React for production: https://reactjs.org/link/perf-use-production-build`)})}catch{}},a=(e=globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__)=>!!(e&&`getFiberRoots`in e),o=!1,s,c=(e=globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__)=>o?!0:(e&&typeof e.inject==`function`&&(s=e.inject.toString()),!!s?.includes(`(injected)`)),l=new Set,u=new Set,d=n=>{let a=new Map,o=0,s={_instrumentationIsActive:!1,_instrumentationSource:e,checkDCE:i,hasUnsupportedRendererAttached:!1,inject(e){let t=++o;return a.set(t,e),u.add(e),s._instrumentationIsActive||(s._instrumentationIsActive=!0,l.forEach(e=>e())),t},on:r,onCommitFiberRoot:r,onCommitFiberUnmount:r,onPostCommitFiberRoot:r,renderers:a,supportsFiber:!0,supportsFlight:!0};try{t(globalThis,`__REACT_DEVTOOLS_GLOBAL_HOOK__`,{configurable:!0,enumerable:!0,get(){return s},set(e){if(e&&typeof e==`object`){let t=s.renderers;s=e,t.size>0&&(t.forEach((t,n)=>{u.add(t),e.renderers.set(n,t)}),f(n))}}});let e=window.hasOwnProperty,r=!1;t(window,`hasOwnProperty`,{configurable:!0,value:function(...t){try{if(!r&&t[0]===`__REACT_DEVTOOLS_GLOBAL_HOOK__`)return globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__=void 0,r=!0,-0}catch{}return e.apply(this,t)},writable:!0})}catch{f(n)}return s},f=t=>{t&&l.add(t);try{let n=globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;if(!n)return;if(!n._instrumentationSource){n.checkDCE=i,n.supportsFiber=!0,n.supportsFlight=!0,n.hasUnsupportedRendererAttached=!1,n._instrumentationSource=e,n._instrumentationIsActive=!1;let t=a(n);if(t||(n.on=r),n.renderers.size){n._instrumentationIsActive=!0,l.forEach(e=>e());return}let s=n.inject,d=c(n);d&&!t&&(o=!0,n.inject({scheduleRefresh(){}})&&(n._instrumentationIsActive=!0)),n.inject=e=>{let t=s(e);return u.add(e),d&&n.renderers.set(t,e),n._instrumentationIsActive=!0,l.forEach(e=>e()),t}}(n.renderers.size||n._instrumentationIsActive||c())&&t?.()}catch{}},ee=()=>n.call(globalThis,`__REACT_DEVTOOLS_GLOBAL_HOOK__`),p=e=>ee()?(f(e),globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__):d(e),te=()=>!!(typeof window<`u`&&(window.document?.createElement||window.navigator?.product===`ReactNative`)),m=()=>{try{te()&&p()}catch{}};m();var ne=60111,re=`Symbol(react.concurrent_mode)`,ie=`Symbol(react.async_mode)`,h=e=>{switch(e.tag){case 5:case 26:case 27:return!0;default:return typeof e.type==`string`}},ae=e=>{switch(e.tag){case 1:case 11:case 0:case 14:case 15:return!0;default:return!1}},g=e=>{let t=e.memoizedProps,n=e.alternate?.memoizedProps||{},r=e.flags??e.effectTag??0;switch(e.tag){case 1:case 9:case 11:case 0:case 14:case 15:return(r&1)==1;default:return!e.alternate||n!==t||e.alternate.memoizedState!==e.memoizedState||e.alternate.ref!==e.ref}},_=e=>{switch(e.tag){case 18:return!0;case 7:case 6:case 23:case 22:return!0;case 3:return!1;default:{let t=typeof e.type==`object`&&e.type!==null?e.type.$$typeof:e.type;switch(typeof t==`symbol`?t.toString():t){case ne:case re:case ie:return!0;default:return!1}}}},oe=(e,t=!1)=>{let n=v(e,h,t);return n||=v(e,h,!t),n};function v(e,t,n=!1){if(!e)return null;let r=t(e);if(r instanceof Promise)return(async()=>{if(await r===!0)return e;let i=n?e.return:e.child;for(;i;){let e=await b(i,t,n);if(e)return e;i=n?null:i.sibling}return null})();if(r===!0)return e;let i=n?e.return:e.child;for(;i;){let e=y(i,t,n);if(e)return e;i=n?null:i.sibling}return null}var y=(e,t,n=!1)=>{if(!e)return null;if(t(e)===!0)return e;let r=n?e.return:e.child;for(;r;){let e=y(r,t,n);if(e)return e;r=n?null:r.sibling}return null},b=async(e,t,n=!1)=>{if(!e)return null;if(await t(e)===!0)return e;let r=n?e.return:e.child;for(;r;){let e=await b(r,t,n);if(e)return e;r=n?null:r.sibling}return null},se=e=>{let t=e?.actualDuration??0,n=t,r=e?.child??null;for(;t>0&&r!=null;)n-=r.actualDuration??0,r=r.sibling;return{selfTime:n,totalTime:t}},x=e=>{let t=e;return typeof t==`function`?t:typeof t==`object`&&t?x(t.type||t.render):null},ce=e=>{let t=e;if(typeof t==`string`)return t;if(typeof t!=`function`&&!(typeof t==`object`&&t))return null;let n=t.displayName||t.name||null;if(n)return n;let r=x(t);return r&&(r.displayName||r.name)||null},S=0,C=new WeakMap,le=(e,t=S++)=>{C.set(e,t)},w=e=>{let t=C.get(e);return!t&&e.alternate&&(t=C.get(e.alternate)),t||(t=S++,le(e,t)),t},T=(e,t,n)=>{let r=t;for(;r!=null;){if(C.has(r)||w(r),!_(r)&&g(r)&&e(r,`mount`),r.tag===13)if(r.memoizedState!==null){let t=r.child,n=t?t.sibling:null;if(n){let t=n.child;t!==null&&T(e,t,!1)}}else{let t=null;r.child!==null&&(t=r.child.child),t!==null&&T(e,t,!1)}else r.child!=null&&T(e,r.child,!0);r=n?r.sibling:null}},E=(e,t,n,r)=>{if(C.has(t)||w(t),!n)return;C.has(n)||w(n);let i=t.tag===13,a=!_(t);a&&g(t)&&e(t,`update`);let o=i&&n.memoizedState!==null,s=i&&t.memoizedState!==null;if(o&&s){let r=t.child?.sibling??null,i=n.child?.sibling??null;r!==null&&i!==null&&E(e,r,i,t)}else if(o&&!s){let n=t.child;n!==null&&T(e,n,!0)}else if(!o&&s){O(e,n);let r=t.child?.sibling??null;r!==null&&T(e,r,!0)}else if(t.child!==n.child){let n=t.child;for(;n;){if(n.alternate){let i=n.alternate;E(e,n,i,a?t:r)}else T(e,n,!1);n=n.sibling}}},D=(e,t)=>{(t.tag===3||!_(t))&&e(t,`unmount`)},O=(e,t)=>{let n=t.tag===13&&t.memoizedState!==null,r=t.child;for(n&&(r=(t.child?.sibling??null)?.child??null);r!==null;)r.return!==null&&(D(e,r),O(e,r)),r=r.sibling},ue=0,k=new WeakMap,de=(e,t)=>{let n=`current`in e?e.current:e,r=k.get(e);r||(r={id:ue++,prevFiber:null},k.set(e,r));let{prevFiber:i}=r;if(!n)D(t,n);else if(i!==null){let e=i&&i.memoizedState!=null&&i.memoizedState.element!=null&&i.memoizedState.isDehydrated!==!0,r=n.memoizedState!=null&&n.memoizedState.element!=null&&n.memoizedState.isDehydrated!==!0;!e&&r?T(t,n,!1):e&&r?E(t,n,n.alternate,null):e&&!r&&D(t,n)}else T(t,n,!0);r.prevFiber=n},fe=t=>{let n=p(t.onActive);n._instrumentationSource=t.name??e;let r=n.onCommitFiberRoot;if(t.onCommitFiberRoot){let e=(n,i,a)=>{r!==e&&(r?.(n,i,a),t.onCommitFiberRoot?.(n,i,a))};n.onCommitFiberRoot=e}let i=n.onCommitFiberUnmount;if(t.onCommitFiberUnmount){let e=(r,a)=>{n.onCommitFiberUnmount===e&&(i?.(r,a),t.onCommitFiberUnmount?.(r,a))};n.onCommitFiberUnmount=e}let a=n.onPostCommitFiberRoot;if(t.onPostCommitFiberRoot){let e=(r,i)=>{n.onPostCommitFiberRoot===e&&(a?.(r,i),t.onPostCommitFiberRoot?.(r,i))};n.onPostCommitFiberRoot=e}return n};function A(e){return Math.round(e*10)/10}function j(e,t,n){return e.rules[t]?.severity??n}var pe=/^(HotReload|Head|AppRouterAnnouncer|HistoryUpdater|AppRouter|Router|ServerRoot|SegmentViewNode|SegmentViewStateNode|SegmentStateProvider|InnerLayoutRouter|OuterLayoutRouter|TemplateContext|RenderFromTemplateContext|ScrollAndFocusHandler|RedirectBoundary|RedirectErrorBoundary|LoadingBoundary|ErrorBoundary|ErrorBoundaryHandler|HTTPAccessFallbackBoundary|HTTPAccessFallbackErrorBoundary|DevRootHTTPAccessFallbackBoundary|__next_root_layout_boundary__|Preloads|NonIndex|ReplaySsrOnlyErrors|AppDevOverlay\w*|DevOverlay\w*|PseudoHtml\w*)$/;function me(e){return e?.length?e.map(e=>{try{let t=new RegExp(e);return e=>t.test(e)}catch{return t=>t===e}}):[]}function M(e,t){if(e.length<=2||pe.test(e))return!0;for(let n of t)if(n(e))return!0;return!1}function he(e){let t=new Map;for(let n of e)for(let e of n.entries){let n=t.get(e.name);n||(n={name:e.name,updates:[],selfTime:0,el:e.el??void 0},t.set(e.name,n)),n.selfTime+=e.selfTime,e.el&&!n.el&&(n.el=e.el),e.phase===`update`&&n.updates.push(e.changes)}return t}function ge(e,t,n){let r=[],i=me(n?.ignore),a=e.map(e=>({...e,entries:e.entries.filter(e=>!M(e.name,i)),mounts:Object.fromEntries(Object.entries(e.mounts).filter(([e])=>!M(e,i)))})),o=he(a),s=t.budgets;return P(r,()=>{let e=[];for(let n of o.values()){if(n.updates.length<5||!n.updates.every(e=>e.length===0))continue;let r=n.updates.length;e.push({ruleId:`R9`,shape:`B`,severity:j(t,`R9`,`error`),suspected:!0,description:`suspected R9 — ${n.name} re-rendered ${r}× with zero prop changes (dead memo or subscription-in-body)`,component:n.name,source:null,renders:r,selfTime:A(n.selfTime),changes:[],evidence:`${r} renders · ${A(n.selfTime)}ms self · all props identity-equal`,el:n.el})}return e}),P(r,()=>{let e=[];for(let n of o.values()){if(n.updates.length<5||!n.updates.every(e=>e.length>0&&e.every(e=>/\((?:fn|ref)\)$/.test(e))))continue;let r=Array.from(new Set(n.updates.flat())),i=n.updates.length,a=r.join(`, `);e.push({ruleId:`R5`,shape:`B`,severity:j(t,`R5`,`error`),suspected:!0,description:`suspected R5 — ${i} renders of ${n.name} with fresh ${a} identity per parent render (memo defeated)`,component:n.name,source:null,renders:i,selfTime:A(n.selfTime),changes:r,evidence:`${i} renders · ${A(n.selfTime)}ms self · churning ${a}`,el:n.el})}return e}),P(r,()=>{let e=[],n=/Tooltip|Menu|Popover|Context|Dialog|Dropdown/i;for(let r of a)for(let i of Object.keys(r.mounts)){let a=r.mounts[i];if(a<(n.test(i)?10:15))continue;let o=r.entries.find(e=>e.name===i&&e.el);e.push({ruleId:`R4`,shape:`A`,severity:j(t,`R4`,`error`),suspected:!0,description:`suspected R4 — ${a} ${i} instances mounted in one commit (per-row overlays should mount on intent)`,component:i,source:null,renders:a,selfTime:0,changes:[],evidence:`${i} ×${a} mounted in one commit`,el:o?.el??void 0})}return e}),P(r,()=>{let e=[];for(let t of a){if(t.duration<=s.commitMs)continue;let n=[...t.entries].sort((e,t)=>t.selfTime-e.selfTime).slice(0,3).map(e=>`${e.name} ${A(e.selfTime)}ms`).join(`, `),r=A(t.duration),i=t.duration>16?` · ~${Math.round(1e3/t.duration)} FPS`:``;e.push({ruleId:null,shape:null,severity:`error`,suspected:!1,description:`commit exceeded budget: ${r}ms of React work (> ${s.commitMs}ms) — top: ${n}`,component:null,source:null,renders:t.entries.length,selfTime:r,changes:[],evidence:`${r}ms commit · ${t.entries.length} components${i}`})}return e}),P(r,()=>{let e=[],t=new Map;for(let e of a){let n=new Map,r=new Map;for(let t of e.entries)n.set(t.name,(n.get(t.name)??0)+t.selfTime),t.el&&!r.has(t.name)&&r.set(t.name,t.el);for(let[e,i]of n){if(i<=s.selfMs)continue;let n=t.get(e)??{hits:0,worst:0,total:0,el:r.get(e)};n.hits+=1,n.worst=Math.max(n.worst,i),n.total+=i,n.el||=r.get(e),t.set(e,n)}}for(let[n,r]of t)r.hits<3||e.push({ruleId:null,shape:null,severity:`warn`,suspected:!1,description:`${n} is a hot component: ${r.hits} commits over the ${s.selfMs}ms self budget`,component:n,source:null,renders:r.hits,selfTime:A(r.total),changes:[],evidence:`${r.hits} commits over ${s.selfMs}ms self (worst ${A(r.worst)}ms)`,el:r.el});return e}),P(r,()=>{let n=[],r=Se(e.filter(e=>e.nearInput).map(e=>e.at).sort((e,t)=>e-t),1e3);return r>=5&&n.push({ruleId:`R10`,shape:`B`,severity:j(t,`R10`,`error`),suspected:!0,description:`suspected R10 — ${r} React commits during scroll/pointer movement (frame-rate state updates)`,component:null,source:null,renders:r,selfTime:0,changes:[],evidence:`${r} commits within a 1s scroll/pointer window`}),n}),P(r,()=>{let n=[],{count:r,span:i}=Ce(e.filter(e=>!e.nearInput).map(e=>e.at).sort((e,t)=>e-t),300);return r>=8&&n.push({ruleId:`R24`,shape:`B`,severity:j(t,`R24`,`warn`),suspected:!0,description:`suspected R24 — ${r} commits in ${Math.round(i)}ms burst (multi-row writes / un-coalesced pokes?)`,component:null,source:null,renders:r,selfTime:0,changes:[],evidence:`${r} commits in a ${Math.round(i)}ms burst (no input)`}),n}),P(r,()=>{let e=[];if(!N())return e;for(let n of o.values()){if(n.updates.length<3)continue;let r=n.el?.deref();if(!r)continue;let i=!1;try{let e=r.getBoundingClientRect();i=!r.isConnected||e.width===0&&e.height===0}catch{continue}if(!i)continue;let a=n.updates.length;e.push({ruleId:`R3`,shape:`C`,severity:j(t,`R3`,`error`),suspected:!0,description:`suspected R3 — ${n.name} re-rendered ${a}× while its DOM is hidden/detached (closed host without everOpened gate?) [R3/R7/R23 family]`,component:n.name,source:null,renders:a,selfTime:A(n.selfTime),changes:[],evidence:`${a} renders while hidden/detached · ${A(n.selfTime)}ms self`,el:n.el})}return e}),F(r)}var _e=`#linear-grab-root, #claude-agent-glow-border, [id^="react-scan"]`;function ve(e=typeof document<`u`?document:{}){let t=[];return P(t,()=>{let t=[],n=Array.from(e.querySelectorAll(`[role="slider"], [role="progressbar"], [role="switch"]`)),r=xe(e);for(let e of n){if(e.closest(_e))continue;if(r)try{let t=e.getBoundingClientRect();if(t.width<2||t.height<2)continue}catch{}if(!ye(e))continue;let n=e.getAttribute(`role`)??`widget`;t.push({ruleId:`R8`,shape:`D`,severity:`warn`,suspected:!0,description:`suspected R8 — interactive ${n} widget used as a static picture (mount a CSS mock instead)`,component:null,source:null,renders:0,selfTime:0,changes:[],evidence:be(e),el:typeof WeakRef<`u`?new WeakRef(e):void 0})}return t}),F(t)}function ye(e){if(e.hasAttribute(`disabled`)||e.getAttribute(`aria-disabled`)===`true`)return!0;let t=e;for(let e=0;t&&e<=3;e++){try{if(getComputedStyle(t).pointerEvents===`none`)return!0}catch{}t=t.parentElement}return!1}function be(e){let t=typeof e.className==`string`&&e.className?`.`+e.className.split(/\s+/).filter(Boolean).slice(0,2).join(`.`):``;return`${e.tagName.toLowerCase()}${t}`}function N(){return typeof document>`u`?!1:(document.documentElement?.getBoundingClientRect().width??0)>0}function xe(e){return((e.documentElement??(typeof document<`u`?document.documentElement:null))?.getBoundingClientRect().width??0)>0}function Se(e,t){let n=0,r=0;for(let i=0;i<e.length;i++){for(;e[i]-e[r]>t;)r++;n=Math.max(n,i-r+1)}return n}function Ce(e,t){let n=0,r=0,i=0;for(let a=0;a<e.length;a++){for(;e[a]-e[i]>t;)i++;let o=a-i+1;o>n&&(n=o,r=e[a]-e[i])}return{count:n,span:r}}function P(e,t){try{e.push(...t())}catch{}}function F(e){let t=new Map;for(let n of e){let e=`${n.ruleId}|${n.component}|${n.changes.join()}|${n.description}`,r=t.get(e);r?r.count=(r.count??1)+1:t.set(e,n)}return[...t.values()]}function I(e){let t=new Map;for(let n of e){let e=n.ruleId??`budget`,r=t.get(e);r?r.push(n):t.set(e,[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.reduce((e,t)=>e+(t.count??1),0),i=e.filter(e=>e.severity===`error`).reduce((e,t)=>e+(t.count??1),0),a=[`# Render scan — ${i} error${i===1?``:`s`}, ${r-i} warn (${r} total)`,``,`Diagnoses are SUSPECTED (heuristic). The numbers (renders, self ms) are measured.`,``];for(let[e,t]of n){let n=t[0],r=t.reduce((e,t)=>e+(t.count??1),0);a.push(`## ${e} — ${n.severity} ×${r}`);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}`:``;a.push(`- ${e.description}${n}${t}`),a.push(` ${e.evidence}`)}a.push(``)}return a.join(`
|
|
2
|
+
`)}function we(e,t){let n=Array.from(new Set(e.map(e=>e.ruleId).filter(e=>!!e))).sort(),r=[];for(let e of n){let n=t.rules[e];n&&n.fixText?r.push(`- ${e} (${n.slug}): ${n.fixText}`):r.push(`- ${e}: see React-rerender-primitives.md ${e}`)}return[`Below is a RENDER-SCAN report from my running app — each finding is a measured`,`React re-render pattern graded against the re-render contract`,"(`React-rerender-primitives.md`). Rules are R-numbered.",``,`READ THIS FIRST: the DIAGNOSES are SUSPECTED — they are heuristic guesses at the`,`cause. The NUMBERS (render counts, self ms, FPS) are EXACT measurements. Trust the`,`numbers; verify each diagnosis against the code before you "fix" it.`,``,`How to fix:`,`1. Fix at the source of the identity churn / wasted render, not the symptom —`,` stabilize props (useCallback/useMemo, hoist constants), gate work behind an`,` everOpened/visible check, mount per-row overlays on intent.`,`2. Each distinct rule and its canonical fix:`,...r.map(e=>` ${e}`),`3. PROOF REQUIRED: after the change I will re-record and re-scan — the flagged`,` components must show the render counts drop. State what you changed per`,` component; do not claim done blind.`,``,`---`,``,I(e)].join(`
|
|
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
|
+
`),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
|
+
`),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.0`,start:$,recording:Ne,finish:Ie,snapshot:Le,report:Re,prompt:ze},window.__RENDER_SCAN_AUTOSTART__){try{m()}catch{}$()}})();
|
package/dist/slop-scan.global.js
CHANGED
|
@@ -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(!q(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(!q(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.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(!q(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)&&J(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)||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}},B={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}},V={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}},H={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}},U={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 W(e){let t=e.transitionProperty.split(`,`).map(e=>e.trim()).filter(Boolean);return t.length===1&&t[0]===`none`?[]:t}var G=[C,w,T,E,D,O,k,A,j,M,P,F,I,L,R,z,B,V,H,U,{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=W(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=W(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=W(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=W(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")]}}],K=[];function q(e){for(let t of Array.from(e.childNodes))if(t.nodeType===3&&(t.textContent??``).trim().length>0)return!0;return!1}function J(e,t,n){let r=Math.max(e,t,n);return r<40?!1:r-Math.min(e,t,n)>60}function Y(e){return!!(/\b0px\s+0px\s+0px\s+0?\.?5px\b/.test(e)||/\b0px?\s+0px?\s+0px?\s+0?\.?5px\b/.test(e))}function X(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(`#linear-grab-root, #claude-agent-glow-border, [id^="react-scan"]`))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[...G,...K])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 Z(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 Q(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.`,``,`---`,``,Z(e)].join(`
|
|
3
|
-
`)}function $(){return X(document).map(({el:e,...t})=>t)}async function ee(){let e=X(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.
|
|
3
|
+
`)}function $(){return X(document).map(({el:e,...t})=>t)}async function ee(){let e=X(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.0`,run:$,runAttributed:ee,report:Z,prompt:Q}})();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "linear-grab",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Point at any React element in your dev app, draft a Linear issue with AI, delegate it to the Cursor agent, and track it live — in any browser.",
|
|
6
6
|
"exports": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"scripts": {
|
|
20
20
|
"dev": "wxt",
|
|
21
21
|
"build": "npm run build:lib && npm run build:ext",
|
|
22
|
-
"build:lib": "vite build --config vite.lib.config.ts && vite build --config vite.global.config.ts && vite build --config vite.slop.config.ts && node scripts/emit-dts.mjs && cp dist/slop-scan.global.js packages/bridge/slop-scan.global.js && cp dist/slop-scan.global.js bin/slop-scan.global.js",
|
|
22
|
+
"build:lib": "vite build --config vite.lib.config.ts && vite build --config vite.global.config.ts && vite build --config vite.slop.config.ts && vite build --config vite.render.config.ts && node scripts/emit-dts.mjs && cp dist/slop-scan.global.js packages/bridge/slop-scan.global.js && cp dist/slop-scan.global.js bin/slop-scan.global.js && cp dist/render-scan.global.js packages/bridge/render-scan.global.js && cp dist/render-scan.global.js bin/render-scan.global.js",
|
|
23
23
|
"build:ext": "wxt build",
|
|
24
24
|
"zip": "wxt zip",
|
|
25
25
|
"compile": "tsc --noEmit",
|