checkpoint-cli 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +393 -52
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -208,6 +208,129 @@ function trackingScript() {
208
208
  };
209
209
  }
210
210
 
211
+ function parseZIndex(value){
212
+ var n=parseInt(String(value||''),10);
213
+ return isNaN(n)?0:n;
214
+ }
215
+
216
+ function detectTransientKind(el){
217
+ if(!el||!el.tagName) return 'unknown';
218
+ try{
219
+ var role=(el.getAttribute&&el.getAttribute('role')||'').toLowerCase();
220
+ var classes=(el.className&&typeof el.className==='string'?el.className:'').toLowerCase();
221
+ var id=(el.id||'').toLowerCase();
222
+ var label=(role+' '+classes+' '+id).trim();
223
+ if(role==='dialog'||el.getAttribute('aria-modal')==='true'||label.indexOf('modal')>=0||label.indexOf('dialog')>=0){
224
+ return 'dialog';
225
+ }
226
+ if(label.indexOf('drawer')>=0||label.indexOf('sheet')>=0||label.indexOf('sidebar')>=0){
227
+ return 'drawer';
228
+ }
229
+ if(label.indexOf('popover')>=0) return 'popover';
230
+ if(role==='menu'||label.indexOf('menu')>=0||label.indexOf('dropdown')>=0) return 'menu';
231
+ if(role==='tooltip'||label.indexOf('tooltip')>=0) return 'tooltip';
232
+ }catch(e){}
233
+ return 'unknown';
234
+ }
235
+
236
+ function isTransientRootCandidate(el){
237
+ if(!el||!el.tagName) return false;
238
+ try{
239
+ var role=(el.getAttribute&&el.getAttribute('role')||'').toLowerCase();
240
+ var ariaModal=(el.getAttribute&&el.getAttribute('aria-modal')||'').toLowerCase();
241
+ var dataState=(el.getAttribute&&el.getAttribute('data-state')||'').toLowerCase();
242
+ var classes=(el.className&&typeof el.className==='string'?el.className:'').toLowerCase();
243
+ var id=(el.id||'').toLowerCase();
244
+ var style=window.getComputedStyle?window.getComputedStyle(el):null;
245
+ var position=style?style.position:'';
246
+ var zIndex=parseZIndex(style?style.zIndex:0);
247
+ if(role==='dialog'||role==='menu'||role==='tooltip') return true;
248
+ if(ariaModal==='true') return true;
249
+ if(dataState==='open'&&(classes.indexOf('modal')>=0||classes.indexOf('popover')>=0||classes.indexOf('drawer')>=0)) return true;
250
+ if(classes.indexOf('modal')>=0||classes.indexOf('dialog')>=0||classes.indexOf('drawer')>=0||classes.indexOf('popover')>=0||classes.indexOf('tooltip')>=0||classes.indexOf('dropdown')>=0) return true;
251
+ if(id.indexOf('modal')>=0||id.indexOf('dialog')>=0||id.indexOf('drawer')>=0||id.indexOf('popover')>=0) return true;
252
+ if((position==='fixed'||position==='sticky')&&zIndex>=100) return true;
253
+ }catch(e){}
254
+ return false;
255
+ }
256
+
257
+ function isLikelyPortaled(el){
258
+ if(!el||!el.parentElement) return false;
259
+ try{
260
+ var root=el;
261
+ var steps=0;
262
+ while(root&&root.parentElement&&steps<8){
263
+ root=root.parentElement;
264
+ steps++;
265
+ }
266
+ if(!root||!root.parentElement) return false;
267
+ if(root===document.body) return true;
268
+ if(el.parentElement===document.body) return true;
269
+ if(el.parentElement&&el.parentElement.id&&String(el.parentElement.id).toLowerCase().indexOf('portal')>=0) return true;
270
+ var classes=(el.className&&typeof el.className==='string'?el.className:'').toLowerCase();
271
+ if(classes.indexOf('portal')>=0) return true;
272
+ }catch(e){}
273
+ return false;
274
+ }
275
+
276
+ function getOpenerCandidates(){
277
+ var out=[];
278
+ var seen={};
279
+ var selectors=[
280
+ '[aria-haspopup=\"dialog\"]',
281
+ '[aria-controls]',
282
+ '[data-modal-trigger]',
283
+ '[data-dialog-trigger]',
284
+ 'button',
285
+ '[role=\"button\"]',
286
+ 'a'
287
+ ];
288
+ for(var s=0;s<selectors.length;s++){
289
+ var selector=selectors[s];
290
+ try{
291
+ var nodes=document.querySelectorAll(selector);
292
+ for(var i=0;i<nodes.length&&out.length<8;i++){
293
+ var node=nodes[i];
294
+ if(!node||node.nodeType!==1) continue;
295
+ var sel=elementSelector(node);
296
+ if(!sel||seen[sel]) continue;
297
+ seen[sel]=true;
298
+ var score=0.3;
299
+ var label=(node.getAttribute&&node.getAttribute('aria-label')||'').toLowerCase();
300
+ var text=shortText(node.textContent).toLowerCase();
301
+ if(label.indexOf('setting')>=0||text.indexOf('setting')>=0) score=0.9;
302
+ else if(label.indexOf('open')>=0||text.indexOf('open')>=0) score=0.7;
303
+ out.push({selector:sel,score:score});
304
+ }
305
+ }catch(e){}
306
+ }
307
+ out.sort(function(a,b){ return b.score-a.score; });
308
+ return out.slice(0,6);
309
+ }
310
+
311
+ function getTransientContext(el){
312
+ if(!el||typeof el.closest!=='function') return null;
313
+ var current=el;
314
+ var depth=0;
315
+ while(current&&current.nodeType===1&&depth<8){
316
+ if(isTransientRootCandidate(current)){
317
+ var style=window.getComputedStyle?window.getComputedStyle(current):null;
318
+ return {
319
+ kind:detectTransientKind(current),
320
+ root_selector_chain:getSelectorChain(current),
321
+ root_fingerprint:getDomFingerprint(current),
322
+ is_portaled:isLikelyPortaled(current),
323
+ z_index:parseZIndex(style?style.zIndex:0),
324
+ is_fixed:style?style.position==='fixed':false,
325
+ opener_candidates:getOpenerCandidates()
326
+ };
327
+ }
328
+ current=current.parentElement;
329
+ depth++;
330
+ }
331
+ return null;
332
+ }
333
+
211
334
  function buildCoordFallback(clientX,clientY,metrics){
212
335
  var safeViewW=metrics.viewWidth||1;
213
336
  var safeViewH=metrics.viewHeight||1;
@@ -235,6 +358,16 @@ function trackingScript() {
235
358
  textSelf=shortText(el&&el.textContent);
236
359
  textParent=shortText(el&&el.parentElement&&el.parentElement.textContent);
237
360
  }catch(e){}
361
+ var fallback=buildCoordFallback(clientX,clientY,metrics);
362
+ try{
363
+ var rect=el&&el.getBoundingClientRect?el.getBoundingClientRect():null;
364
+ if(rect){
365
+ var safeW=Math.max(1,rect.width||1);
366
+ var safeH=Math.max(1,rect.height||1);
367
+ fallback.element_x_percent=clampPercent(((clientX-rect.left)/safeW)*100);
368
+ fallback.element_y_percent=clampPercent(((clientY-rect.top)/safeH)*100);
369
+ }
370
+ }catch(e){}
238
371
  return {
239
372
  selector_chain:getSelectorChain(el),
240
373
  dom_fingerprint:getDomFingerprint(el),
@@ -243,14 +376,126 @@ function trackingScript() {
243
376
  parent:textParent
244
377
  },
245
378
  container_hint:getContainerHint(el),
246
- coord_fallback:buildCoordFallback(clientX,clientY,metrics)
379
+ transient_context:getTransientContext(el),
380
+ coord_fallback:fallback
247
381
  };
248
382
  }
249
383
 
250
- function resolveFromFingerprint(fingerprint){
384
+ function getTargetDocPoint(payload,metrics){
385
+ var fallback=payload&&payload.coord_fallback;
386
+ if(!fallback||typeof fallback!=='object') return null;
387
+ var docW=(typeof fallback.doc_width==='number'&&fallback.doc_width>0)
388
+ ? fallback.doc_width
389
+ : (metrics.docWidth||metrics.viewWidth||1);
390
+ var docH=(typeof fallback.doc_height==='number'&&fallback.doc_height>0)
391
+ ? fallback.doc_height
392
+ : (metrics.docHeight||metrics.viewHeight||1);
393
+ if(typeof fallback.doc_x_percent==='number'&&typeof fallback.doc_y_percent==='number'){
394
+ return {
395
+ x:(fallback.doc_x_percent/100)*docW,
396
+ y:(fallback.doc_y_percent/100)*docH
397
+ };
398
+ }
399
+ if(typeof fallback.x_percent==='number'&&typeof fallback.y_percent==='number'){
400
+ var viewW=(typeof fallback.viewport_width==='number'&&fallback.viewport_width>0)
401
+ ? fallback.viewport_width
402
+ : (metrics.viewWidth||1);
403
+ var viewH=(typeof fallback.viewport_height==='number'&&fallback.viewport_height>0)
404
+ ? fallback.viewport_height
405
+ : (metrics.viewHeight||1);
406
+ var scrollX=typeof fallback.scroll_x==='number'?fallback.scroll_x:metrics.scrollX;
407
+ var scrollY=typeof fallback.scroll_y==='number'?fallback.scroll_y:metrics.scrollY;
408
+ return {
409
+ x:(fallback.x_percent/100)*viewW+scrollX,
410
+ y:(fallback.y_percent/100)*viewH+scrollY
411
+ };
412
+ }
413
+ return null;
414
+ }
415
+
416
+ function getAnchorClientPoint(el,payload){
417
+ var rect=el.getBoundingClientRect();
418
+ var safeW=Math.max(2,rect.width||2);
419
+ var safeH=Math.max(2,rect.height||2);
420
+ var relX=0.5;
421
+ var relY=0.5;
422
+ var fallback=payload&&payload.coord_fallback;
423
+ if(fallback&&typeof fallback.element_x_percent==='number'){
424
+ relX=clampPercent(fallback.element_x_percent)/100;
425
+ }
426
+ if(fallback&&typeof fallback.element_y_percent==='number'){
427
+ relY=clampPercent(fallback.element_y_percent)/100;
428
+ }
429
+ return {
430
+ clientX:rect.left+Math.max(1,Math.min(safeW-1,safeW*relX)),
431
+ clientY:rect.top+Math.max(1,Math.min(safeH-1,safeH*relY))
432
+ };
433
+ }
434
+
435
+ function siblingPathDistance(el,path){
436
+ if(!Array.isArray(path)||path.length===0) return 0;
437
+ var current=getSiblingPath(el);
438
+ var maxLen=Math.max(current.length,path.length);
439
+ var dist=Math.abs(current.length-path.length)*5;
440
+ var cOffset=maxLen-current.length;
441
+ var pOffset=maxLen-path.length;
442
+ for(var i=0;i<maxLen;i++){
443
+ var cVal=current[i-cOffset];
444
+ var pVal=path[i-pOffset];
445
+ if(typeof cVal==='number'&&typeof pVal==='number'){
446
+ dist+=Math.abs(cVal-pVal);
447
+ }else{
448
+ dist+=2;
449
+ }
450
+ }
451
+ return dist;
452
+ }
453
+
454
+ function pickBestElement(nodes,payload,metrics,fingerprintPath){
455
+ if(!nodes||nodes.length===0) return null;
456
+ var target=getTargetDocPoint(payload,metrics);
457
+ var best=null;
458
+ var bestScore=Infinity;
459
+ for(var i=0;i<nodes.length;i++){
460
+ var node=nodes[i];
461
+ if(!node||node.nodeType!==1) continue;
462
+ var anchorPoint=getAnchorClientPoint(node,payload);
463
+ var docX=anchorPoint.clientX+metrics.scrollX;
464
+ var docY=anchorPoint.clientY+metrics.scrollY;
465
+ var score=0;
466
+ if(target){
467
+ var dx=docX-target.x;
468
+ var dy=docY-target.y;
469
+ score+=Math.sqrt(dx*dx+dy*dy);
470
+ }
471
+ if(Array.isArray(fingerprintPath)&&fingerprintPath.length>0){
472
+ score+=siblingPathDistance(node,fingerprintPath)*40;
473
+ }
474
+ if(score<bestScore){
475
+ best=node;
476
+ bestScore=score;
477
+ }
478
+ }
479
+ return best;
480
+ }
481
+
482
+ function querySelectorAllSafe(scope,selector){
483
+ if(!scope||!selector) return [];
484
+ try{
485
+ var nodeList=scope.querySelectorAll(selector);
486
+ return nodeList?Array.prototype.slice.call(nodeList):[];
487
+ }catch(e){}
488
+ return [];
489
+ }
490
+
491
+ function resolveFromFingerprint(fingerprint,payload,metrics,scope){
251
492
  if(!fingerprint) return null;
493
+ var searchScope=scope||document;
252
494
  if(fingerprint.id){
253
- var byId=document.getElementById(fingerprint.id);
495
+ var byId=searchScope.getElementById?searchScope.getElementById(fingerprint.id):null;
496
+ if(!byId&&searchScope.querySelector){
497
+ try{ byId=searchScope.querySelector('#'+cssEscape(fingerprint.id)); }catch(e){}
498
+ }
254
499
  if(byId) return byId;
255
500
  }
256
501
  var tag=fingerprint.tag||'*';
@@ -258,56 +503,110 @@ function trackingScript() {
258
503
  if(Array.isArray(fingerprint.classes)&&fingerprint.classes.length>0){
259
504
  selector+= '.'+fingerprint.classes.map(cssEscape).join('.');
260
505
  }
261
- try{
262
- var nodes=document.querySelectorAll(selector);
263
- if(nodes&&nodes.length>0) return nodes[0];
264
- }catch(e){}
265
- return null;
506
+ var nodes=querySelectorAllSafe(searchScope,selector);
507
+ return pickBestElement(nodes,payload,metrics,fingerprint.sibling_path);
508
+ }
509
+
510
+ function isStrongContainerHint(hint){
511
+ if(!hint||typeof hint!=='object') return false;
512
+ if(typeof hint.id==='string'&&hint.id.length>0) return true;
513
+ if(typeof hint.selector==='string'){
514
+ if(hint.selector.indexOf('#')===0) return true;
515
+ if(hint.selector.indexOf('[data-testid')>=0) return true;
516
+ if(hint.selector.indexOf('[role=')>=0) return true;
517
+ }
518
+ return false;
519
+ }
520
+
521
+ function resolveTransientRoot(payload,metrics){
522
+ var transient=payload&&payload.transient_context;
523
+ if(!transient||typeof transient!=='object') return null;
524
+ var root=null;
525
+ if(Array.isArray(transient.root_selector_chain)){
526
+ for(var i=0;i<transient.root_selector_chain.length;i++){
527
+ var candidate=transient.root_selector_chain[i];
528
+ if(!candidate||typeof candidate.selector!=='string') continue;
529
+ var nodes=querySelectorAllSafe(document,candidate.selector);
530
+ root=pickBestElement(nodes,payload,metrics,transient.root_fingerprint&&transient.root_fingerprint.sibling_path);
531
+ if(root) return root;
532
+ }
533
+ }
534
+ root=resolveFromFingerprint(transient.root_fingerprint,payload,metrics,document);
535
+ return root;
266
536
  }
267
537
 
268
538
  function resolveAnchor(payload){
269
- if(!payload||typeof payload!=='object') return {coordFallback:null,strategy:'none',matchedSelector:null};
539
+ if(!payload||typeof payload!=='object') return {coordFallback:null,strategy:'none',matchedSelector:null,status:'none'};
270
540
  var element=null;
271
541
  var strategy='none';
272
542
  var matchedSelector=null;
273
- if(Array.isArray(payload.selector_chain)){
543
+ var metrics=getMetrics();
544
+ var transientRoot=null;
545
+ var hasTransientContext=!!(payload.transient_context&&typeof payload.transient_context==='object');
546
+ if(hasTransientContext){
547
+ transientRoot=resolveTransientRoot(payload,metrics);
548
+ if(!transientRoot){
549
+ return {coordFallback:null,strategy:'transient_missing',matchedSelector:null,status:'transient_missing'};
550
+ }
551
+ }
552
+
553
+ function trySelectorChain(scope,scopeTag){
554
+ if(!Array.isArray(payload.selector_chain)) return false;
274
555
  for(var i=0;i<payload.selector_chain.length;i++){
275
556
  var candidate=payload.selector_chain[i];
276
557
  if(!candidate||typeof candidate.selector!=='string') continue;
277
- try{
278
- var found=document.querySelector(candidate.selector);
279
- if(found){
280
- element=found;
281
- strategy='selector';
282
- matchedSelector=candidate.selector;
283
- break;
284
- }
285
- }catch(e){}
558
+ var nodes=querySelectorAllSafe(scope,candidate.selector);
559
+ var found=pickBestElement(nodes,payload,metrics,null);
560
+ if(found){
561
+ element=found;
562
+ strategy='selector';
563
+ matchedSelector=(scopeTag?scopeTag+': ':'')+candidate.selector;
564
+ return true;
565
+ }
286
566
  }
567
+ return false;
287
568
  }
288
- if(!element&&payload.container_hint&&typeof payload.container_hint.selector==='string'){
289
- try{
290
- var containerEl=document.querySelector(payload.container_hint.selector);
291
- if(containerEl){
292
- element=containerEl;
293
- strategy='container';
294
- matchedSelector=payload.container_hint.selector;
295
- }
296
- }catch(e){}
569
+
570
+ if(transientRoot){
571
+ trySelectorChain(transientRoot,'transient_root');
297
572
  }
298
573
  if(!element){
299
- var fpEl=resolveFromFingerprint(payload.dom_fingerprint);
574
+ trySelectorChain(document,'document');
575
+ }
576
+
577
+ if(
578
+ !element&&
579
+ payload.container_hint&&
580
+ typeof payload.container_hint.selector==='string'&&
581
+ isStrongContainerHint(payload.container_hint)
582
+ ){
583
+ var containerNodesInRoot=transientRoot?querySelectorAllSafe(transientRoot,payload.container_hint.selector):[];
584
+ var containerEl=pickBestElement(containerNodesInRoot,payload,metrics,null);
585
+ if(!containerEl){
586
+ var containerNodes=querySelectorAllSafe(document,payload.container_hint.selector);
587
+ containerEl=pickBestElement(containerNodes,payload,metrics,null);
588
+ }
589
+ if(containerEl){
590
+ element=containerEl;
591
+ strategy='container';
592
+ matchedSelector=payload.container_hint.selector;
593
+ }
594
+ }
595
+ if(!element){
596
+ var fpEl=resolveFromFingerprint(payload.dom_fingerprint,payload,metrics,transientRoot||undefined);
597
+ if(!fpEl){
598
+ fpEl=resolveFromFingerprint(payload.dom_fingerprint,payload,metrics,document);
599
+ }
300
600
  if(fpEl){
301
601
  element=fpEl;
302
602
  strategy='fingerprint';
303
603
  }
304
604
  }
305
- if(!element) return {coordFallback:null,strategy:'none',matchedSelector:null};
306
- var rect=element.getBoundingClientRect();
307
- var clientX=rect.left+Math.max(1,Math.min(rect.width-1,rect.width*0.5));
308
- var clientY=rect.top+Math.max(1,Math.min(rect.height-1,rect.height*0.5));
309
- var metrics=getMetrics();
310
- return {coordFallback:buildCoordFallback(clientX,clientY,metrics),strategy:strategy,matchedSelector:matchedSelector};
605
+ if(!element) return {coordFallback:null,strategy:'fallback',matchedSelector:null,status:'fallback_only'};
606
+ var point=getAnchorClientPoint(element,payload);
607
+ var clientX=point.clientX;
608
+ var clientY=point.clientY;
609
+ return {coordFallback:buildCoordFallback(clientX,clientY,metrics),strategy:strategy,matchedSelector:matchedSelector,status:'resolved'};
311
610
  }
312
611
 
313
612
  function setPickMode(enabled){
@@ -400,8 +699,59 @@ function trackingScript() {
400
699
  });
401
700
  }
402
701
  }
702
+
703
+ var mutationTick=false;
704
+ function reportDomMutation(reason){
705
+ try{
706
+ window.parent.postMessage({
707
+ type:'checkpoint:domMutation',
708
+ reason:reason||'mutation',
709
+ path:location.pathname+location.search+location.hash
710
+ },'*');
711
+ }catch(e){}
712
+ }
713
+
714
+ function onMutation(){
715
+ if(mutationTick) return;
716
+ mutationTick=true;
717
+ requestAnimationFrame(function(){
718
+ mutationTick=false;
719
+ reportScroll();
720
+ reportDomMutation('mutation');
721
+ });
722
+ }
723
+
724
+ function setupMutationObserver(){
725
+ try{
726
+ if(!window.MutationObserver||!document.documentElement) return;
727
+ var observer=new MutationObserver(function(mutations){
728
+ for(var i=0;i<mutations.length;i++){
729
+ var m=mutations[i];
730
+ if(m.type==='childList'){
731
+ if((m.addedNodes&&m.addedNodes.length>0)||(m.removedNodes&&m.removedNodes.length>0)){
732
+ onMutation();
733
+ return;
734
+ }
735
+ }
736
+ if(m.type==='attributes'){
737
+ onMutation();
738
+ return;
739
+ }
740
+ }
741
+ });
742
+ observer.observe(document.documentElement,{
743
+ childList:true,
744
+ subtree:true,
745
+ attributes:true,
746
+ attributeFilter:['class','style','aria-hidden','aria-modal','data-state','open']
747
+ });
748
+ window.addEventListener('beforeunload',function(){ try{observer.disconnect();}catch(e){}; });
749
+ }catch(e){}
750
+ }
751
+
403
752
  reportNav();
404
753
  reportScroll();
754
+ setupMutationObserver();
405
755
  var _ps=history.pushState,_rs=history.replaceState;
406
756
  history.pushState=function(){_ps.apply(this,arguments);reportNav();};
407
757
  history.replaceState=function(){_rs.apply(this,arguments);reportNav();};
@@ -423,23 +773,14 @@ function trackingScript() {
423
773
  var item=e.data.items[i];
424
774
  if(!item||typeof item.id!=='string') continue;
425
775
  var result=resolveAnchor(item.anchorPayload);
426
- if(result.coordFallback){
427
- positions.push({
428
- id:item.id,
429
- found:true,
430
- strategy:result.strategy,
431
- matchedSelector:result.matchedSelector,
432
- coordFallback:result.coordFallback
433
- });
434
- } else {
435
- positions.push({
436
- id:item.id,
437
- found:false,
438
- strategy:'fallback',
439
- matchedSelector:null,
440
- coordFallback:null
441
- });
442
- }
776
+ positions.push({
777
+ id:item.id,
778
+ found:!!result.coordFallback,
779
+ status:result.status||'none',
780
+ strategy:result.strategy,
781
+ matchedSelector:result.matchedSelector,
782
+ coordFallback:result.coordFallback||null
783
+ });
443
784
  }
444
785
  window.parent.postMessage({
445
786
  type:'checkpoint:anchorsResolved',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "checkpoint-cli",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Share your localhost with reviewers — get visual feedback directly on the page",
5
5
  "keywords": [
6
6
  "checkpoint",