checkpoint-cli 0.1.8 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +233 -189
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -339,132 +339,19 @@ function trackingScript() {
339
339
  return '';
340
340
  }
341
341
 
342
- function getNavSignature(){
343
- try{
344
- var candidates=[];
345
- var activeSelectors=[
346
- '[aria-current=\"page\"]',
347
- '[aria-selected=\"true\"]',
348
- '[data-state=\"active\"]',
349
- '.active',
350
- '.is-active',
351
- '.selected'
352
- ];
353
- for(var i=0;i<activeSelectors.length;i++){
354
- var nodes=document.querySelectorAll(activeSelectors[i]);
355
- for(var j=0;j<nodes.length&&candidates.length<8;j++){
356
- var text=getVisibleText(nodes[j]);
357
- if(text) candidates.push(text);
358
- }
359
- }
360
- if(candidates.length===0){
361
- var heading=document.querySelector('main h1, [role=\"main\"] h1, h1, h2');
362
- var fallbackText=getVisibleText(heading);
363
- if(fallbackText) candidates.push(fallbackText);
342
+ function getSourceDebugInfo(fiber){
343
+ var current=fiber;
344
+ var steps=0;
345
+ while(current&&steps<24){
346
+ if(current._debugSource){
347
+ return current._debugSource;
364
348
  }
365
- if(candidates.length===0) return '';
366
- return candidates.slice(0,4).join('|');
367
- }catch(e){}
368
- return '';
369
- }
370
-
371
- function getHeadingSignature(){
372
- try{
373
- var heading=document.querySelector('main h1, [role=\"main\"] h1, h1, h2');
374
- return getVisibleText(heading);
375
- }catch(e){}
376
- return '';
377
- }
378
-
379
- function getViewContext(){
380
- return {
381
- nav_signature:getNavSignature()||undefined,
382
- heading_signature:getHeadingSignature()||undefined
383
- };
384
- }
385
-
386
- function viewContextMatches(expected){
387
- if(!expected||typeof expected!=='object') return true;
388
- var current=getViewContext();
389
- if(expected.nav_signature&&current.nav_signature&&expected.nav_signature!==current.nav_signature){
390
- return false;
391
- }
392
- if(expected.heading_signature&&current.heading_signature&&expected.heading_signature!==current.heading_signature){
393
- return false;
394
- }
395
- return true;
396
- }
397
-
398
- function isScrollable(el){
399
- if(!el||el.nodeType!==1||!window.getComputedStyle) return false;
400
- try{
401
- var style=window.getComputedStyle(el);
402
- var overflowY=String(style.overflowY||'').toLowerCase();
403
- var overflowX=String(style.overflowX||'').toLowerCase();
404
- var canScrollY=(overflowY==='auto'||overflowY==='scroll'||overflowY==='overlay')&&(el.scrollHeight-el.clientHeight>1);
405
- var canScrollX=(overflowX==='auto'||overflowX==='scroll'||overflowX==='overlay')&&(el.scrollWidth-el.clientWidth>1);
406
- return !!(canScrollX||canScrollY);
407
- }catch(e){}
408
- return false;
409
- }
410
-
411
- function getNearestScrollContainer(el){
412
- if(!el) return null;
413
- var current=el.parentElement;
414
- var depth=0;
415
- while(current&&depth<10){
416
- if(current===document.body||current===document.documentElement) return null;
417
- if(isScrollable(current)) return current;
418
- current=current.parentElement;
419
- depth++;
349
+ current=current.return||null;
350
+ steps++;
420
351
  }
421
352
  return null;
422
353
  }
423
354
 
424
- function getScrollContext(el,clientX,clientY){
425
- var container=getNearestScrollContainer(el);
426
- if(!container) return null;
427
- var rect=container.getBoundingClientRect?container.getBoundingClientRect():null;
428
- if(!rect) return null;
429
- var safeW=Math.max(1,rect.width||1);
430
- var safeH=Math.max(1,rect.height||1);
431
- return {
432
- container_selector_chain:getSelectorChain(container),
433
- container_fingerprint:getDomFingerprint(container),
434
- container_x_percent:clampPercent(((clientX-rect.left)/safeW)*100),
435
- container_y_percent:clampPercent(((clientY-rect.top)/safeH)*100)
436
- };
437
- }
438
-
439
- function getPointFromScrollContext(scrollContext){
440
- if(!scrollContext||typeof scrollContext!=='object') return null;
441
- var container=null;
442
- if(Array.isArray(scrollContext.container_selector_chain)){
443
- for(var i=0;i<scrollContext.container_selector_chain.length;i++){
444
- var candidate=scrollContext.container_selector_chain[i];
445
- if(!candidate||typeof candidate.selector!=='string') continue;
446
- var nodes=querySelectorAllSafe(document,candidate.selector);
447
- var match=pickBestElement(nodes,{coord_fallback:null},getMetrics(),null);
448
- if(match&&match.element){
449
- container=match.element;
450
- break;
451
- }
452
- }
453
- }
454
- if(!container&&scrollContext.container_fingerprint){
455
- var fp=resolveFromFingerprint(scrollContext.container_fingerprint,{coord_fallback:null},getMetrics(),document);
456
- container=fp&&fp.element?fp.element:null;
457
- }
458
- if(!container||!container.getBoundingClientRect) return null;
459
- var rect=container.getBoundingClientRect();
460
- var relX=typeof scrollContext.container_x_percent==='number'?clampPercent(scrollContext.container_x_percent)/100:0.5;
461
- var relY=typeof scrollContext.container_y_percent==='number'?clampPercent(scrollContext.container_y_percent)/100:0.5;
462
- return {
463
- clientX:rect.left+(rect.width*relX),
464
- clientY:rect.top+(rect.height*relY)
465
- };
466
- }
467
-
468
355
  function buildCoordFallback(clientX,clientY,metrics){
469
356
  var safeViewW=metrics.viewWidth||1;
470
357
  var safeViewH=metrics.viewHeight||1;
@@ -511,8 +398,7 @@ function trackingScript() {
511
398
  },
512
399
  container_hint:getContainerHint(el),
513
400
  transient_context:getTransientContext(el),
514
- view_context:getViewContext(),
515
- scroll_context:getScrollContext(el,clientX,clientY),
401
+ source_anchor:getSourceAnchor(el),
516
402
  coord_fallback:fallback
517
403
  };
518
404
  }
@@ -587,17 +473,163 @@ function trackingScript() {
587
473
  return dist;
588
474
  }
589
475
 
476
+ function getReactFiberNode(el){
477
+ if(!el||typeof el!=='object') return null;
478
+ try{
479
+ var keys=Object.keys(el);
480
+ for(var i=0;i<keys.length;i++){
481
+ var key=keys[i];
482
+ if(key.indexOf('__reactFiber$')===0||key.indexOf('__reactInternalInstance$')===0){
483
+ return el[key];
484
+ }
485
+ }
486
+ }catch(e){}
487
+ return null;
488
+ }
489
+
490
+ function getReactFiberName(fiber){
491
+ try{
492
+ if(!fiber) return '';
493
+ var t=fiber.type||fiber.elementType;
494
+ if(typeof t==='string') return t;
495
+ if(t&&typeof t.displayName==='string'&&t.displayName) return t.displayName;
496
+ if(t&&typeof t.name==='string'&&t.name) return t.name;
497
+ }catch(e){}
498
+ return '';
499
+ }
500
+
501
+ function getReactOwnerPath(el){
502
+ var fiber=getReactFiberNode(el);
503
+ if(!fiber) return [];
504
+ var path=[];
505
+ var current=fiber;
506
+ var steps=0;
507
+ while(current&&steps<24){
508
+ var name=getReactFiberName(current);
509
+ if(name){
510
+ var keyVal=current.key!=null?String(current.key):'';
511
+ path.push(keyVal?name+'#'+keyVal:name);
512
+ }
513
+ current=current.return||null;
514
+ steps++;
515
+ }
516
+ path.reverse();
517
+ if(path.length>12){
518
+ path=path.slice(path.length-12);
519
+ }
520
+ return path;
521
+ }
522
+
523
+ function getExplicitAnchorId(el){
524
+ if(!el||typeof el.closest!=='function') return '';
525
+ var holder=el.closest('[data-checkpoint-anchor]');
526
+ if(!holder||!holder.getAttribute) return '';
527
+ return holder.getAttribute('data-checkpoint-anchor')||'';
528
+ }
529
+
530
+ function getSourceAnchor(el){
531
+ var fiber=getReactFiberNode(el);
532
+ var ownerPath=getReactOwnerPath(el);
533
+ var source=getSourceDebugInfo(fiber);
534
+ var component=getReactFiberName(fiber);
535
+ var explicitId=getExplicitAnchorId(el);
536
+ var reactKey=fiber&&fiber.key!=null?String(fiber.key):'';
537
+ var hasData=!!(explicitId||component||ownerPath.length>0||source||reactKey);
538
+ if(!hasData) return null;
539
+ return {
540
+ explicit_id:explicitId||undefined,
541
+ component_name:component||undefined,
542
+ owner_path:ownerPath,
543
+ source_file:source&&source.fileName?String(source.fileName):undefined,
544
+ source_line:source&&typeof source.lineNumber==='number'?source.lineNumber:undefined,
545
+ source_column:source&&typeof source.columnNumber==='number'?source.columnNumber:undefined,
546
+ react_key:reactKey||undefined,
547
+ host_tag:el&&el.tagName?String(el.tagName).toLowerCase():undefined
548
+ };
549
+ }
550
+
551
+ function ownerPathSuffixMatches(current,expected){
552
+ if(!Array.isArray(current)||!Array.isArray(expected)||current.length===0||expected.length===0) return 0;
553
+ var i=current.length-1;
554
+ var j=expected.length-1;
555
+ var matches=0;
556
+ while(i>=0&&j>=0){
557
+ if(current[i]!==expected[j]) break;
558
+ matches++;
559
+ i--;
560
+ j--;
561
+ }
562
+ return matches;
563
+ }
564
+
565
+ function sourceMatchScore(node,sourceAnchor){
566
+ if(!sourceAnchor||typeof sourceAnchor!=='object') return 0;
567
+ var score=0;
568
+ var explicit=getExplicitAnchorId(node);
569
+ if(sourceAnchor.explicit_id){
570
+ if(explicit===sourceAnchor.explicit_id) score+=1400;
571
+ else if(explicit) score-=300;
572
+ }
573
+ var fiber=getReactFiberNode(node);
574
+ if(fiber){
575
+ var component=getReactFiberName(fiber);
576
+ if(sourceAnchor.component_name&&component===sourceAnchor.component_name){
577
+ score+=180;
578
+ }
579
+ if(sourceAnchor.react_key&&fiber.key!=null&&String(fiber.key)===sourceAnchor.react_key){
580
+ score+=260;
581
+ }
582
+ var currentPath=getReactOwnerPath(node);
583
+ var suffix=ownerPathSuffixMatches(currentPath,sourceAnchor.owner_path);
584
+ if(suffix>0){
585
+ score+=suffix*80;
586
+ }
587
+ var source=getSourceDebugInfo(fiber);
588
+ if(source&&sourceAnchor.source_file){
589
+ var currentFile=String(source.fileName||'');
590
+ var targetFile=String(sourceAnchor.source_file||'');
591
+ if(currentFile===targetFile){
592
+ score+=260;
593
+ if(typeof sourceAnchor.source_line==='number'&&source.lineNumber===sourceAnchor.source_line){
594
+ score+=280;
595
+ }
596
+ if(typeof sourceAnchor.source_column==='number'&&source.columnNumber===sourceAnchor.source_column){
597
+ score+=60;
598
+ }
599
+ }
600
+ }
601
+ }
602
+ if(sourceAnchor.host_tag&&node&&node.tagName&&String(node.tagName).toLowerCase()===sourceAnchor.host_tag){
603
+ score+=35;
604
+ }
605
+ return score;
606
+ }
607
+
608
+ function textMatchScore(node,payload){
609
+ if(!payload||!payload.text_context) return 0;
610
+ var tc=payload.text_context;
611
+ var selfText=getVisibleText(node);
612
+ var parentText=node.parentElement?getVisibleText(node.parentElement):'';
613
+ var bonus=0;
614
+ if(tc.self&&selfText&&tc.self.length>2){
615
+ if(selfText===tc.self.toLowerCase()) bonus+=200;
616
+ else if(selfText.indexOf(tc.self.toLowerCase())>=0||tc.self.toLowerCase().indexOf(selfText)>=0) bonus+=80;
617
+ }
618
+ if(tc.parent&&parentText&&tc.parent.length>2){
619
+ if(parentText===tc.parent.toLowerCase()) bonus+=100;
620
+ else if(parentText.indexOf(tc.parent.toLowerCase())>=0||tc.parent.toLowerCase().indexOf(parentText)>=0) bonus+=40;
621
+ }
622
+ return bonus;
623
+ }
624
+
590
625
  function pickBestElement(nodes,payload,metrics,fingerprintPath){
591
- if(!nodes||nodes.length===0) return { element:null, ambiguous:false, bestScore:Infinity };
626
+ if(!nodes||nodes.length===0) return { element:null, bestScore:Infinity };
592
627
  var target=getTargetDocPoint(payload,metrics);
593
628
  var best=null;
594
629
  var bestScore=Infinity;
595
- var secondScore=Infinity;
596
- var matchCount=0;
597
630
  for(var i=0;i<nodes.length;i++){
598
631
  var node=nodes[i];
599
632
  if(!node||node.nodeType!==1) continue;
600
- matchCount++;
601
633
  var anchorPoint=getAnchorClientPoint(node,payload);
602
634
  var docX=anchorPoint.clientX+metrics.scrollX;
603
635
  var docY=anchorPoint.clientY+metrics.scrollY;
@@ -610,24 +642,15 @@ function trackingScript() {
610
642
  if(Array.isArray(fingerprintPath)&&fingerprintPath.length>0){
611
643
  score+=siblingPathDistance(node,fingerprintPath)*40;
612
644
  }
645
+ var tBonus=textMatchScore(node,payload);
646
+ score=Math.max(0,score-tBonus);
613
647
  if(score<bestScore){
614
- secondScore=bestScore;
615
648
  best=node;
616
649
  bestScore=score;
617
- }else if(score<secondScore){
618
- secondScore=score;
619
- }
620
- }
621
- if(!best) return { element:null, ambiguous:false, bestScore:Infinity };
622
- var ambiguous=false;
623
- if(matchCount>1){
624
- if(!target){
625
- ambiguous=true;
626
- }else if((secondScore-bestScore)<18){
627
- ambiguous=true;
628
650
  }
629
651
  }
630
- return { element:best, ambiguous:ambiguous, bestScore:bestScore };
652
+ if(!best) return { element:null, bestScore:Infinity };
653
+ return { element:best, bestScore:bestScore };
631
654
  }
632
655
 
633
656
  function querySelectorAllSafe(scope,selector){
@@ -640,14 +663,14 @@ function trackingScript() {
640
663
  }
641
664
 
642
665
  function resolveFromFingerprint(fingerprint,payload,metrics,scope){
643
- if(!fingerprint) return { element:null, ambiguous:false, bestScore:Infinity };
666
+ if(!fingerprint) return { element:null, bestScore:Infinity };
644
667
  var searchScope=scope||document;
645
668
  if(fingerprint.id){
646
669
  var byId=searchScope.getElementById?searchScope.getElementById(fingerprint.id):null;
647
670
  if(!byId&&searchScope.querySelector){
648
671
  try{ byId=searchScope.querySelector('#'+cssEscape(fingerprint.id)); }catch(e){}
649
672
  }
650
- if(byId) return { element:byId, ambiguous:false, bestScore:0 };
673
+ if(byId) return { element:byId, bestScore:0 };
651
674
  }
652
675
  var tag=fingerprint.tag||'*';
653
676
  var selector=tag;
@@ -658,6 +681,65 @@ function trackingScript() {
658
681
  return pickBestElement(nodes,payload,metrics,fingerprint.sibling_path);
659
682
  }
660
683
 
684
+ function resolveFromSourceAnchor(sourceAnchor,payload,metrics,scope){
685
+ if(!sourceAnchor||typeof sourceAnchor!=='object'){
686
+ return { element:null, score:0 };
687
+ }
688
+ var searchScope=scope||document;
689
+ var best=null;
690
+ var bestScore=0;
691
+ var target=getTargetDocPoint(payload,metrics);
692
+
693
+ function consider(nodes){
694
+ for(var i=0;i<nodes.length;i++){
695
+ var node=nodes[i];
696
+ if(!node||node.nodeType!==1) continue;
697
+ var score=sourceMatchScore(node,sourceAnchor);
698
+ if(score<=0) continue;
699
+ if(target){
700
+ var point=getAnchorClientPoint(node,payload);
701
+ var dx=(point.clientX+metrics.scrollX)-target.x;
702
+ var dy=(point.clientY+metrics.scrollY)-target.y;
703
+ var dist=Math.sqrt(dx*dx+dy*dy);
704
+ score-=Math.min(280,dist*0.7);
705
+ }
706
+ if(score>bestScore){
707
+ best=node;
708
+ bestScore=score;
709
+ }
710
+ }
711
+ }
712
+
713
+ if(sourceAnchor.explicit_id){
714
+ var explicitNodes=querySelectorAllSafe(searchScope,'[data-checkpoint-anchor=\"'+cssEscape(sourceAnchor.explicit_id)+'\"]');
715
+ consider(explicitNodes);
716
+ if(!best&&searchScope!==document){
717
+ consider(querySelectorAllSafe(document,'[data-checkpoint-anchor=\"'+cssEscape(sourceAnchor.explicit_id)+'\"]'));
718
+ }
719
+ if(best){
720
+ return { element:best, score:bestScore };
721
+ }
722
+ }
723
+
724
+ var tag=sourceAnchor.host_tag||((payload.dom_fingerprint&&payload.dom_fingerprint.tag)||'*');
725
+ var nodes=querySelectorAllSafe(searchScope,tag);
726
+ if(nodes.length>1800){
727
+ nodes=nodes.slice(0,1800);
728
+ }
729
+ consider(nodes);
730
+ if(!best&&searchScope!==document){
731
+ var docNodes=querySelectorAllSafe(document,tag);
732
+ if(docNodes.length>1800){
733
+ docNodes=docNodes.slice(0,1800);
734
+ }
735
+ consider(docNodes);
736
+ }
737
+ if(bestScore<220){
738
+ return { element:null, score:bestScore };
739
+ }
740
+ return { element:best, score:bestScore };
741
+ }
742
+
661
743
  function isStrongContainerHint(hint){
662
744
  if(!hint||typeof hint!=='object') return false;
663
745
  if(typeof hint.id==='string'&&hint.id.length>0) return true;
@@ -689,19 +771,10 @@ function trackingScript() {
689
771
 
690
772
  function resolveAnchor(payload){
691
773
  if(!payload||typeof payload!=='object') return {coordFallback:null,strategy:'none',matchedSelector:null,status:'none'};
692
- if(!viewContextMatches(payload.view_context)){
693
- return {
694
- coordFallback:payload.coord_fallback||null,
695
- strategy:'context_mismatch',
696
- matchedSelector:null,
697
- status:'context_mismatch'
698
- };
699
- }
700
774
  var element=null;
701
775
  var strategy='none';
702
776
  var matchedSelector=null;
703
777
  var metrics=getMetrics();
704
- var ambiguous=false;
705
778
  var transientRoot=null;
706
779
  var hasTransientContext=!!(payload.transient_context&&typeof payload.transient_context==='object');
707
780
  if(hasTransientContext){
@@ -720,7 +793,6 @@ function trackingScript() {
720
793
  var found=pickBestElement(nodes,payload,metrics,null);
721
794
  if(found&&found.element){
722
795
  element=found.element;
723
- ambiguous=!!found.ambiguous;
724
796
  strategy='selector';
725
797
  matchedSelector=(scopeTag?scopeTag+': ':'')+candidate.selector;
726
798
  return true;
@@ -732,6 +804,15 @@ function trackingScript() {
732
804
  if(transientRoot){
733
805
  trySelectorChain(transientRoot,'transient_root');
734
806
  }
807
+ if(!element&&payload.source_anchor){
808
+ var sourceScope=transientRoot||document;
809
+ var sourceMatch=resolveFromSourceAnchor(payload.source_anchor,payload,metrics,sourceScope);
810
+ if(sourceMatch&&sourceMatch.element){
811
+ element=sourceMatch.element;
812
+ strategy='source';
813
+ matchedSelector='source_anchor';
814
+ }
815
+ }
735
816
  if(!element){
736
817
  trySelectorChain(document,'document');
737
818
  }
@@ -750,7 +831,6 @@ function trackingScript() {
750
831
  }
751
832
  if(containerMatch.element){
752
833
  element=containerMatch.element;
753
- ambiguous=!!containerMatch.ambiguous;
754
834
  strategy='container';
755
835
  matchedSelector=payload.container_hint.selector;
756
836
  }
@@ -762,45 +842,10 @@ function trackingScript() {
762
842
  }
763
843
  if(fpMatch.element){
764
844
  element=fpMatch.element;
765
- ambiguous=!!fpMatch.ambiguous;
766
845
  strategy='fingerprint';
767
846
  }
768
847
  }
769
- if(!element&&payload.scroll_context){
770
- var fromContainer=getPointFromScrollContext(payload.scroll_context);
771
- if(fromContainer){
772
- return {
773
- coordFallback:buildCoordFallback(fromContainer.clientX,fromContainer.clientY,metrics),
774
- strategy:'scroll_container',
775
- matchedSelector:null,
776
- status:'resolved'
777
- };
778
- }
779
- }
780
848
  if(!element) return {coordFallback:null,strategy:'fallback',matchedSelector:null,status:'fallback_only'};
781
- if(ambiguous){
782
- return {
783
- coordFallback:payload.coord_fallback||null,
784
- strategy:'ambiguous',
785
- matchedSelector:matchedSelector,
786
- status:'ambiguous'
787
- };
788
- }
789
- var target=getTargetDocPoint(payload,metrics);
790
- if(target){
791
- var anchorPoint=getAnchorClientPoint(element,payload);
792
- var distX=(anchorPoint.clientX+metrics.scrollX)-target.x;
793
- var distY=(anchorPoint.clientY+metrics.scrollY)-target.y;
794
- var dist=Math.sqrt(distX*distX+distY*distY);
795
- if(dist>220){
796
- return {
797
- coordFallback:payload.coord_fallback||null,
798
- strategy:'ambiguous',
799
- matchedSelector:matchedSelector,
800
- status:'ambiguous'
801
- };
802
- }
803
- }
804
849
  var point=getAnchorClientPoint(element,payload);
805
850
  var clientX=point.clientX;
806
851
  var clientY=point.clientY;
@@ -893,7 +938,6 @@ function trackingScript() {
893
938
  scrollTick=true;
894
939
  requestAnimationFrame(function(){
895
940
  reportScroll();
896
- reportDomMutation('scroll');
897
941
  scrollTick=false;
898
942
  });
899
943
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "checkpoint-cli",
3
- "version": "0.1.8",
3
+ "version": "0.2.0",
4
4
  "description": "Share your localhost with reviewers — get visual feedback directly on the page",
5
5
  "keywords": [
6
6
  "checkpoint",