wikimem 0.8.1 → 0.8.3

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.
@@ -2633,7 +2633,7 @@
2633
2633
  .tl-play-btn.playing { background:var(--accent); color:var(--bg-deep); border-color:var(--accent); }
2634
2634
  .tl-speed { background:var(--bg-card); border:1px solid var(--border); color:var(--text); padding:4px 6px; border-radius:var(--radius); font-size:11px; cursor:pointer; }
2635
2635
  .tl-slider-wrap { flex:1; display:flex; flex-direction:column; gap:2px; }
2636
- .tl-slider { width:100%; height:6px; -webkit-appearance:none; appearance:none; background:linear-gradient(to right, var(--accent) 0%, var(--accent) var(--slider-pct, 0%), var(--border) var(--slider-pct, 0%), var(--border) 100%); border-radius:3px; outline:none; cursor:pointer; transition:background 0.1s ease; }
2636
+ .tl-slider { width:100%; height:6px; -webkit-appearance:none; appearance:none; background:linear-gradient(to right, var(--accent) 0%, var(--accent) var(--slider-pct, 0%), var(--border) var(--slider-pct, 0%), var(--border) 100%); border-radius:3px; outline:none; cursor:pointer; }
2637
2637
  .tl-slider::-webkit-slider-thumb { -webkit-appearance:none; width:16px; height:16px; border-radius:50%; background:var(--text-bright); cursor:grab; box-shadow:0 1px 6px rgba(0,0,0,0.4), 0 0 0 2px var(--accent); transition:transform 0.1s ease, box-shadow 0.15s ease; }
2638
2638
  .tl-slider::-webkit-slider-thumb:hover { transform:scale(1.2); box-shadow:0 1px 8px rgba(0,0,0,0.5), 0 0 0 3px var(--accent); }
2639
2639
  .tl-slider::-webkit-slider-thumb:active { cursor:grabbing; transform:scale(1.1); }
@@ -8897,6 +8897,7 @@
8897
8897
  _tlPlayRaf: null,
8898
8898
  _tlDragging: false,
8899
8899
  _tlRenderDebounce: null,
8900
+ _tlJustScrubbed: false,
8900
8901
  };
8901
8902
 
8902
8903
  function railTimelapseClick() {
@@ -9454,8 +9455,8 @@
9454
9455
  if(o) {
9455
9456
  n.x = o.x;
9456
9457
  n.y = o.y;
9457
- n.vx = (o.vx || 0) * 0.32;
9458
- n.vy = (o.vy || 0) * 0.32;
9458
+ n.vx = (o.vx || 0) * (tlState.playing ? 0.1 : 0.32);
9459
+ n.vy = (o.vy || 0) * (tlState.playing ? 0.1 : 0.32);
9459
9460
  } else if(p) {
9460
9461
  n.x = p.x;
9461
9462
  n.y = p.y;
@@ -9469,7 +9470,10 @@
9469
9470
  sim.force('charge', d3.forceManyBody().strength(-220));
9470
9471
  sim.force('center', d3.forceCenter(width/2, height/2));
9471
9472
  sim.force('collision', d3.forceCollide().radius(d => isHub(d)?26:18));
9472
- sim.alpha(tlState.playing ? 0.15 : 0.3).restart();
9473
+ const scrubAlpha = tlState.playing ? 0.08 : (tlState._tlJustScrubbed ? 0.15 : 0.3);
9474
+ const scrubDecay = tlState.playing ? 0.04 : (tlState._tlJustScrubbed ? 0.035 : 0.0228);
9475
+ sim.alpha(scrubAlpha).alphaDecay(scrubDecay).restart();
9476
+ tlState._tlJustScrubbed = false;
9473
9477
  } else {
9474
9478
  if(tlState._tlGraphSim) {
9475
9479
  tlState._tlGraphSim.stop();
@@ -9486,13 +9490,16 @@
9486
9490
  }
9487
9491
 
9488
9492
  const soft = reuseSim;
9493
+ const fast = tlState.playing; // ultra-fast transitions during playback
9489
9494
 
9490
9495
  const lk = g.select('g.tl-links').selectAll('line').data(data.links, linkKey);
9491
- lk.exit().transition().duration(soft ? 200 : 300).attr('stroke-opacity',0).remove();
9496
+ lk.exit().transition().duration(fast ? 80 : soft ? 200 : 300).attr('stroke-opacity',0).remove();
9492
9497
  const lkE = lk.enter().append('line')
9493
9498
  .attr('stroke',cs('--border-subtle')).attr('stroke-width',0.8)
9494
- .attr('stroke-opacity', soft ? 0.2 : 0.4);
9495
- if(soft) {
9499
+ .attr('stroke-opacity', fast ? 0.35 : soft ? 0.2 : 0.4);
9500
+ if(fast) {
9501
+ lkE.transition().duration(120).attr('stroke-opacity',0.4);
9502
+ } else if(soft) {
9496
9503
  lkE.transition().duration(380).attr('stroke-opacity',0.4);
9497
9504
  } else {
9498
9505
  lkE.attr('stroke-dasharray','500').attr('stroke-dashoffset','500')
@@ -9500,26 +9507,27 @@
9500
9507
  .on('end', function(){ d3.select(this).attr('stroke-dasharray',null); });
9501
9508
  }
9502
9509
  const links = lkE.merge(lk);
9503
- lk.transition().duration(soft ? 320 : 300).attr('stroke-opacity',0.4);
9510
+ lk.transition().duration(fast ? 100 : soft ? 320 : 300).attr('stroke-opacity',0.4);
9504
9511
 
9505
9512
  const rg = g.select('g.tl-rings').selectAll('circle').data(data.nodes.filter(isHub), d=>d.id);
9506
- rg.exit().transition().duration(soft ? 200 : 300).attr('opacity',0).remove();
9513
+ rg.exit().transition().duration(fast ? 60 : soft ? 200 : 300).attr('opacity',0).remove();
9507
9514
  const rgE = rg.enter().append('circle')
9508
- .attr('r',d=>nR(d)+5).attr('fill','none').attr('stroke','#d4a843').attr('stroke-width',1.5).attr('opacity', soft ? 0.35 : 0);
9509
- rgE.transition().duration(soft ? 260 : 300).delay(soft ? 0 : 100).attr('opacity',0.5);
9515
+ .attr('r',d=>nR(d)+5).attr('fill','none').attr('stroke','#d4a843').attr('stroke-width',1.5).attr('opacity', fast ? 0.4 : soft ? 0.35 : 0);
9516
+ rgE.transition().duration(fast ? 100 : soft ? 260 : 300).delay(fast ? 0 : soft ? 0 : 100).attr('opacity',0.5);
9510
9517
  const rings = rgE.merge(rg);
9511
- rg.transition().duration(soft ? 300 : 300).attr('r',d=>nR(d)+5);
9518
+ rg.transition().duration(fast ? 80 : soft ? 300 : 300).attr('r',d=>nR(d)+5);
9512
9519
 
9513
9520
  const nd = g.select('g.tl-nodes').selectAll('circle').data(data.nodes, d=>d.id);
9514
- nd.exit().transition().duration(soft ? 220 : 300).attr('opacity',0).attr('r',0).remove();
9521
+ nd.exit().transition().duration(fast ? 60 : soft ? 220 : 300).attr('opacity',0).attr('r',0).remove();
9515
9522
  const ndE = nd.enter().append('circle')
9516
- .attr('r', soft ? d=>nR(d) : 0).attr('opacity', soft ? 0.75 : 0)
9523
+ .attr('r', fast ? d=>nR(d)*0.7 : soft ? d=>nR(d) : 0)
9524
+ .attr('opacity', fast ? 0.85 : soft ? 0.75 : 0)
9517
9525
  .attr('fill',d=>cColor(d.community))
9518
9526
  .attr('stroke',d=>isHub(d)?'#d4a843':cs('--bg'))
9519
9527
  .attr('stroke-width',d=>isHub(d)?2:1.5)
9520
9528
  .attr('cursor','pointer');
9521
- ndE.transition().duration(soft ? 420 : 300).attr('r',d=>nR(d)).attr('opacity',1);
9522
- nd.transition().duration(soft ? 450 : 300)
9529
+ ndE.transition().duration(fast ? 100 : soft ? 420 : 300).attr('r',d=>nR(d)).attr('opacity',1);
9530
+ nd.transition().duration(fast ? 120 : soft ? 450 : 300)
9523
9531
  .attr('r',d=>nR(d)).attr('fill',d=>cColor(d.community))
9524
9532
  .attr('stroke',d=>isHub(d)?'#d4a843':cs('--bg'));
9525
9533
  const nodes = ndE.merge(nd);
@@ -9539,7 +9547,7 @@
9539
9547
  .attr('fill',d=>isHub(d)?cs('--text-bright'):cs('--text-dim'))
9540
9548
  .attr('text-anchor','middle').attr('dy',d=>nR(d)+13)
9541
9549
  .attr('pointer-events','none').attr('opacity',0);
9542
- lbE.transition().duration(300).delay(soft ? 40 : 200).attr('opacity',d=>isHub(d)?1:0);
9550
+ lbE.transition().duration(fast ? 80 : 300).delay(fast ? 0 : soft ? 40 : 200).attr('opacity',d=>isHub(d)?1:0);
9543
9551
  const labels = lbE.merge(lb);
9544
9552
  labels.text(d=>d.title.length>22?d.title.slice(0,20)+'\u2026':d.title);
9545
9553
 
@@ -9593,6 +9601,7 @@
9593
9601
 
9594
9602
  let lastTime = null;
9595
9603
  let fractionalIdx = tlState.currentIdx;
9604
+ let rendering = false;
9596
9605
 
9597
9606
  function animate(ts) {
9598
9607
  if(!tlState.playing || tlState._tlDragging) return;
@@ -9609,14 +9618,16 @@
9609
9618
  slider.style.setProperty('--slider-pct', `${pct}%`);
9610
9619
  slider.value = String(Math.min(Math.round(fractionalIdx), max));
9611
9620
 
9612
- // Only render at integer commit boundaries
9621
+ // Only render at integer commit boundaries — skip if prior render still in flight
9613
9622
  const targetIdx = Math.floor(fractionalIdx);
9614
- if(targetIdx > tlState.currentIdx && targetIdx <= max) {
9615
- renderTimelapseAt(targetIdx, { crossfade: false });
9623
+ if(targetIdx > tlState.currentIdx && targetIdx <= max && !rendering) {
9624
+ rendering = true;
9625
+ renderTimelapseAt(targetIdx, { crossfade: false }).finally(() => { rendering = false; });
9616
9626
  }
9617
9627
 
9618
9628
  if(fractionalIdx >= max) {
9619
- renderTimelapseAt(max, { crossfade: false });
9629
+ rendering = true;
9630
+ renderTimelapseAt(max, { crossfade: false }).finally(() => { rendering = false; });
9620
9631
  tlPause();
9621
9632
  return;
9622
9633
  }
@@ -9737,6 +9748,7 @@
9737
9748
  cancelAnimationFrame(tlState._tlSliderRaf);
9738
9749
  tlState._tlSliderRaf = null;
9739
9750
  }
9751
+ tlState._tlJustScrubbed = true;
9740
9752
  renderTimelapseAt(tlState._tlSliderPendingIdx != null ? tlState._tlSliderPendingIdx : tlState.currentIdx, { crossfade: true });
9741
9753
  }
9742
9754
  document.getElementById('tl-slider').addEventListener('pointerup', tlSliderPointerUp);
@@ -9750,18 +9762,26 @@
9750
9762
  e.target.style.setProperty('--slider-pct', `${(val / max) * 100}%`);
9751
9763
  tlState._tlSliderPendingIdx = val;
9752
9764
 
9753
- // Update commit info label immediately (lightweight DOM update)
9765
+ // Update commit info + counter immediately (lightweight DOM updates, no debounce)
9754
9766
  const commit = tlState.commits[val];
9755
9767
  if(commit) {
9756
9768
  document.getElementById('tl-label-current').textContent = `${val + 1} / ${tlState.commits.length}`;
9769
+ const date = new Date(commit.date);
9770
+ const dateStr = date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], {hour:'2-digit',minute:'2-digit'});
9771
+ const badge = commit.isWiki ? '<span class="commit-badge wiki">wiki</span>' : '<span class="commit-badge code">code</span>';
9772
+ document.getElementById('tl-commit-info').innerHTML =
9773
+ `<span class="tl-hash">${commit.hashShort}</span>${badge}<span>${esc(commit.message)}</span><span style="margin-left:auto;color:var(--text-muted)">${dateStr} · ${commit.author}</span>`;
9757
9774
  }
9758
9775
 
9759
- // Debounce the expensive tree/graph render (80ms) to avoid blocking the UI thread
9776
+ // In graph mode, skip heavy renders during drag only render on release
9777
+ if(tlState.mode === 'graph') return;
9778
+
9779
+ // Debounce the expensive tree render (100ms) to avoid blocking the UI thread
9760
9780
  if(tlState._tlRenderDebounce != null) clearTimeout(tlState._tlRenderDebounce);
9761
9781
  tlState._tlRenderDebounce = setTimeout(() => {
9762
9782
  tlState._tlRenderDebounce = null;
9763
9783
  renderTimelapseAt(val, { crossfade: false });
9764
- }, 80);
9784
+ }, 100);
9765
9785
  });
9766
9786
 
9767
9787
  // change: fires once on slider release (mouse up / touch end) — final authoritative render