checkpoint-cli 0.1.9 → 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 +214 -190
  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,6 +473,138 @@ 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
+
590
608
  function textMatchScore(node,payload){
591
609
  if(!payload||!payload.text_context) return 0;
592
610
  var tc=payload.text_context;
@@ -605,17 +623,13 @@ function trackingScript() {
605
623
  }
606
624
 
607
625
  function pickBestElement(nodes,payload,metrics,fingerprintPath){
608
- if(!nodes||nodes.length===0) return { element:null, ambiguous:false, bestScore:Infinity };
626
+ if(!nodes||nodes.length===0) return { element:null, bestScore:Infinity };
609
627
  var target=getTargetDocPoint(payload,metrics);
610
628
  var best=null;
611
629
  var bestScore=Infinity;
612
- var secondScore=Infinity;
613
- var matchCount=0;
614
- var bestTextBonus=0;
615
630
  for(var i=0;i<nodes.length;i++){
616
631
  var node=nodes[i];
617
632
  if(!node||node.nodeType!==1) continue;
618
- matchCount++;
619
633
  var anchorPoint=getAnchorClientPoint(node,payload);
620
634
  var docX=anchorPoint.clientX+metrics.scrollX;
621
635
  var docY=anchorPoint.clientY+metrics.scrollY;
@@ -631,24 +645,12 @@ function trackingScript() {
631
645
  var tBonus=textMatchScore(node,payload);
632
646
  score=Math.max(0,score-tBonus);
633
647
  if(score<bestScore){
634
- secondScore=bestScore;
635
648
  best=node;
636
649
  bestScore=score;
637
- bestTextBonus=tBonus;
638
- }else if(score<secondScore){
639
- secondScore=score;
640
650
  }
641
651
  }
642
- if(!best) return { element:null, ambiguous:false, bestScore:Infinity };
643
- var ambiguous=false;
644
- if(matchCount>1){
645
- if(!target&&bestTextBonus===0){
646
- ambiguous=true;
647
- }else if((secondScore-bestScore)<30&&bestTextBonus<80){
648
- ambiguous=true;
649
- }
650
- }
651
- return { element:best, ambiguous:ambiguous, bestScore:bestScore };
652
+ if(!best) return { element:null, bestScore:Infinity };
653
+ return { element:best, bestScore:bestScore };
652
654
  }
653
655
 
654
656
  function querySelectorAllSafe(scope,selector){
@@ -661,14 +663,14 @@ function trackingScript() {
661
663
  }
662
664
 
663
665
  function resolveFromFingerprint(fingerprint,payload,metrics,scope){
664
- if(!fingerprint) return { element:null, ambiguous:false, bestScore:Infinity };
666
+ if(!fingerprint) return { element:null, bestScore:Infinity };
665
667
  var searchScope=scope||document;
666
668
  if(fingerprint.id){
667
669
  var byId=searchScope.getElementById?searchScope.getElementById(fingerprint.id):null;
668
670
  if(!byId&&searchScope.querySelector){
669
671
  try{ byId=searchScope.querySelector('#'+cssEscape(fingerprint.id)); }catch(e){}
670
672
  }
671
- if(byId) return { element:byId, ambiguous:false, bestScore:0 };
673
+ if(byId) return { element:byId, bestScore:0 };
672
674
  }
673
675
  var tag=fingerprint.tag||'*';
674
676
  var selector=tag;
@@ -679,6 +681,65 @@ function trackingScript() {
679
681
  return pickBestElement(nodes,payload,metrics,fingerprint.sibling_path);
680
682
  }
681
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
+
682
743
  function isStrongContainerHint(hint){
683
744
  if(!hint||typeof hint!=='object') return false;
684
745
  if(typeof hint.id==='string'&&hint.id.length>0) return true;
@@ -710,19 +771,10 @@ function trackingScript() {
710
771
 
711
772
  function resolveAnchor(payload){
712
773
  if(!payload||typeof payload!=='object') return {coordFallback:null,strategy:'none',matchedSelector:null,status:'none'};
713
- if(!viewContextMatches(payload.view_context)){
714
- return {
715
- coordFallback:payload.coord_fallback||null,
716
- strategy:'context_mismatch',
717
- matchedSelector:null,
718
- status:'context_mismatch'
719
- };
720
- }
721
774
  var element=null;
722
775
  var strategy='none';
723
776
  var matchedSelector=null;
724
777
  var metrics=getMetrics();
725
- var ambiguous=false;
726
778
  var transientRoot=null;
727
779
  var hasTransientContext=!!(payload.transient_context&&typeof payload.transient_context==='object');
728
780
  if(hasTransientContext){
@@ -741,7 +793,6 @@ function trackingScript() {
741
793
  var found=pickBestElement(nodes,payload,metrics,null);
742
794
  if(found&&found.element){
743
795
  element=found.element;
744
- ambiguous=!!found.ambiguous;
745
796
  strategy='selector';
746
797
  matchedSelector=(scopeTag?scopeTag+': ':'')+candidate.selector;
747
798
  return true;
@@ -753,6 +804,15 @@ function trackingScript() {
753
804
  if(transientRoot){
754
805
  trySelectorChain(transientRoot,'transient_root');
755
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
+ }
756
816
  if(!element){
757
817
  trySelectorChain(document,'document');
758
818
  }
@@ -771,7 +831,6 @@ function trackingScript() {
771
831
  }
772
832
  if(containerMatch.element){
773
833
  element=containerMatch.element;
774
- ambiguous=!!containerMatch.ambiguous;
775
834
  strategy='container';
776
835
  matchedSelector=payload.container_hint.selector;
777
836
  }
@@ -783,45 +842,10 @@ function trackingScript() {
783
842
  }
784
843
  if(fpMatch.element){
785
844
  element=fpMatch.element;
786
- ambiguous=!!fpMatch.ambiguous;
787
845
  strategy='fingerprint';
788
846
  }
789
847
  }
790
- if(!element&&payload.scroll_context){
791
- var fromContainer=getPointFromScrollContext(payload.scroll_context);
792
- if(fromContainer){
793
- return {
794
- coordFallback:buildCoordFallback(fromContainer.clientX,fromContainer.clientY,metrics),
795
- strategy:'scroll_container',
796
- matchedSelector:null,
797
- status:'resolved'
798
- };
799
- }
800
- }
801
848
  if(!element) return {coordFallback:null,strategy:'fallback',matchedSelector:null,status:'fallback_only'};
802
- if(ambiguous){
803
- return {
804
- coordFallback:payload.coord_fallback||null,
805
- strategy:'ambiguous',
806
- matchedSelector:matchedSelector,
807
- status:'ambiguous'
808
- };
809
- }
810
- var target=getTargetDocPoint(payload,metrics);
811
- if(target){
812
- var anchorPoint=getAnchorClientPoint(element,payload);
813
- var distX=(anchorPoint.clientX+metrics.scrollX)-target.x;
814
- var distY=(anchorPoint.clientY+metrics.scrollY)-target.y;
815
- var dist=Math.sqrt(distX*distX+distY*distY);
816
- if(dist>220){
817
- return {
818
- coordFallback:payload.coord_fallback||null,
819
- strategy:'ambiguous',
820
- matchedSelector:matchedSelector,
821
- status:'ambiguous'
822
- };
823
- }
824
- }
825
849
  var point=getAnchorClientPoint(element,payload);
826
850
  var clientX=point.clientX;
827
851
  var clientY=point.clientY;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "checkpoint-cli",
3
- "version": "0.1.9",
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",