depwire-cli 0.6.2 → 0.7.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.
@@ -0,0 +1,475 @@
1
+ let temporalData = null;
2
+ let currentIndex = 0;
3
+ let isPlaying = false;
4
+ let playSpeed = 1;
5
+ let playInterval = null;
6
+ let svg = null;
7
+ let g = null;
8
+ let filePositions = new Map();
9
+
10
+ async function init() {
11
+ try {
12
+ const response = await fetch('/api/data');
13
+ temporalData = await response.json();
14
+
15
+ document.getElementById('projectName').textContent = temporalData.projectName;
16
+ document.getElementById('snapshotCount').textContent = temporalData.snapshots.length;
17
+
18
+ setupTimeline();
19
+ renderSnapshot(0);
20
+ setupControls();
21
+ setupSearch();
22
+
23
+ window.addEventListener('resize', () => {
24
+ renderSnapshot(currentIndex);
25
+ });
26
+ } catch (error) {
27
+ console.error('Failed to load temporal data:', error);
28
+ }
29
+ }
30
+
31
+ function setupTimeline() {
32
+ const rail = document.getElementById('timelineRail');
33
+ const scrubber = document.getElementById('timelineScrubber');
34
+ const dots = document.getElementById('timelineDots');
35
+
36
+ temporalData.timeline.forEach((item, index) => {
37
+ const dot = document.createElement('div');
38
+ dot.className = 'timeline-dot';
39
+ dot.style.left = `${(index / (temporalData.timeline.length - 1)) * 100}%`;
40
+ dot.title = `${item.shortHash}: ${item.message}`;
41
+ dot.addEventListener('click', () => {
42
+ goToSnapshot(index);
43
+ });
44
+ dots.appendChild(dot);
45
+ });
46
+
47
+ let isDragging = false;
48
+
49
+ scrubber.addEventListener('mousedown', () => {
50
+ isDragging = true;
51
+ });
52
+
53
+ document.addEventListener('mousemove', (e) => {
54
+ if (!isDragging) return;
55
+
56
+ const rect = rail.getBoundingClientRect();
57
+ const x = Math.max(0, Math.min(e.clientX - rect.left, rect.width));
58
+ const progress = x / rect.width;
59
+ const index = Math.round(progress * (temporalData.snapshots.length - 1));
60
+
61
+ goToSnapshot(index);
62
+ });
63
+
64
+ document.addEventListener('mouseup', () => {
65
+ isDragging = false;
66
+ });
67
+
68
+ rail.addEventListener('click', (e) => {
69
+ if (e.target === scrubber) return;
70
+
71
+ const rect = rail.getBoundingClientRect();
72
+ const x = e.clientX - rect.left;
73
+ const progress = x / rect.width;
74
+ const index = Math.round(progress * (temporalData.snapshots.length - 1));
75
+
76
+ goToSnapshot(index);
77
+ });
78
+ }
79
+
80
+ function setupControls() {
81
+ const playBtn = document.getElementById('playBtn');
82
+
83
+ playBtn.addEventListener('click', () => {
84
+ if (isPlaying) {
85
+ pausePlayback();
86
+ } else {
87
+ startPlayback();
88
+ }
89
+ });
90
+
91
+ document.querySelectorAll('.speed-btn').forEach((btn) => {
92
+ btn.addEventListener('click', () => {
93
+ document.querySelectorAll('.speed-btn').forEach((b) => b.classList.remove('active'));
94
+ btn.classList.add('active');
95
+ playSpeed = parseFloat(btn.dataset.speed);
96
+
97
+ if (isPlaying) {
98
+ pausePlayback();
99
+ startPlayback();
100
+ }
101
+ });
102
+ });
103
+ }
104
+
105
+ function setupSearch() {
106
+ const searchInput = document.getElementById('searchInput');
107
+ let searchTimeout = null;
108
+
109
+ searchInput.addEventListener('input', (e) => {
110
+ clearTimeout(searchTimeout);
111
+ searchTimeout = setTimeout(() => {
112
+ const query = e.target.value.trim().toLowerCase();
113
+ highlightSearchResults(query);
114
+ }, 300);
115
+ });
116
+ }
117
+
118
+ function highlightSearchResults(query) {
119
+ if (!query) {
120
+ d3.selectAll('.file-bar').classed('search-match', false).classed('search-dim', false);
121
+ return;
122
+ }
123
+
124
+ d3.selectAll('.file-bar').each(function (d) {
125
+ const matches = d.path.toLowerCase().includes(query);
126
+ d3.select(this).classed('search-match', matches).classed('search-dim', !matches);
127
+ });
128
+ }
129
+
130
+ function startPlayback() {
131
+ isPlaying = true;
132
+ document.getElementById('playBtn').innerHTML = `
133
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
134
+ <rect x="3" y="2" width="4" height="12"/>
135
+ <rect x="9" y="2" width="4" height="12"/>
136
+ </svg>
137
+ `;
138
+
139
+ const interval = 2000 / playSpeed;
140
+
141
+ playInterval = setInterval(() => {
142
+ if (currentIndex < temporalData.snapshots.length - 1) {
143
+ goToSnapshot(currentIndex + 1);
144
+ } else {
145
+ pausePlayback();
146
+ }
147
+ }, interval);
148
+ }
149
+
150
+ function pausePlayback() {
151
+ isPlaying = false;
152
+ clearInterval(playInterval);
153
+ document.getElementById('playBtn').innerHTML = `
154
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
155
+ <path d="M3 2v12l10-6L3 2z"/>
156
+ </svg>
157
+ `;
158
+ }
159
+
160
+ function goToSnapshot(index) {
161
+ currentIndex = Math.max(0, Math.min(index, temporalData.snapshots.length - 1));
162
+ renderSnapshot(currentIndex);
163
+ updateTimeline();
164
+ }
165
+
166
+ function updateTimeline() {
167
+ const progress = currentIndex / (temporalData.snapshots.length - 1);
168
+ document.getElementById('timelineProgress').style.width = `${progress * 100}%`;
169
+ document.getElementById('timelineScrubber').style.left = `${progress * 100}%`;
170
+
171
+ const snapshot = temporalData.snapshots[currentIndex];
172
+ const timeline = temporalData.timeline[currentIndex];
173
+
174
+ document.getElementById('currentCommit').textContent = timeline.shortHash;
175
+ document.getElementById('currentDate').textContent = new Date(timeline.date).toLocaleDateString();
176
+
177
+ document.querySelectorAll('.timeline-dot').forEach((dot, i) => {
178
+ dot.classList.toggle('active', i === currentIndex);
179
+ });
180
+ }
181
+
182
+ function renderSnapshot(index) {
183
+ const snapshot = temporalData.snapshots[index];
184
+
185
+ updateDetails(snapshot);
186
+ renderArcDiagram(snapshot);
187
+ updateChart();
188
+ }
189
+
190
+ function updateDetails(snapshot) {
191
+ document.getElementById('detailCommit').textContent = snapshot.commitHash.substring(0, 8);
192
+ document.getElementById('detailDate').textContent = new Date(snapshot.commitDate).toLocaleDateString();
193
+ document.getElementById('detailMessage').textContent = snapshot.commitMessage;
194
+ document.getElementById('detailAuthor').textContent = snapshot.commitAuthor;
195
+
196
+ document.getElementById('statFiles').textContent = snapshot.stats.totalFiles;
197
+ document.getElementById('statSymbols').textContent = snapshot.stats.totalSymbols;
198
+ document.getElementById('statEdges').textContent = snapshot.stats.totalEdges;
199
+
200
+ if (snapshot.diff) {
201
+ updateDelta('deltaFiles', snapshot.diff.statsChange.files);
202
+ updateDelta('deltaSymbols', snapshot.diff.statsChange.symbols);
203
+ updateDelta('deltaEdges', snapshot.diff.statsChange.edges);
204
+ } else {
205
+ document.getElementById('deltaFiles').textContent = '';
206
+ document.getElementById('deltaSymbols').textContent = '';
207
+ document.getElementById('deltaEdges').textContent = '';
208
+ }
209
+ }
210
+
211
+ function updateDelta(elementId, value) {
212
+ const elem = document.getElementById(elementId);
213
+ if (value > 0) {
214
+ elem.textContent = `+${value}`;
215
+ elem.className = 'stat-delta positive';
216
+ } else if (value < 0) {
217
+ elem.textContent = value;
218
+ elem.className = 'stat-delta negative';
219
+ } else {
220
+ elem.textContent = '';
221
+ elem.className = 'stat-delta';
222
+ }
223
+ }
224
+
225
+ function renderArcDiagram(snapshot) {
226
+ const container = document.querySelector('.diagram-container');
227
+ const width = container.clientWidth;
228
+ const height = container.clientHeight;
229
+
230
+ filePositions.clear();
231
+
232
+ d3.select('#diagram').selectAll('*').remove();
233
+
234
+ svg = d3.select('#diagram').attr('width', width).attr('height', height);
235
+
236
+ g = svg.append('g');
237
+
238
+ const zoom = d3.zoom().scaleExtent([0.5, 4]).on('zoom', (event) => {
239
+ g.attr('transform', event.transform);
240
+ });
241
+
242
+ svg.call(zoom);
243
+
244
+ const margin = { top: 60, right: 40, bottom: 120, left: 40 };
245
+ const plotWidth = width - margin.left - margin.right;
246
+ const plotHeight = height - margin.top - margin.bottom;
247
+ const baseline = margin.top + plotHeight;
248
+
249
+ const totalSymbols = d3.sum(snapshot.files, (d) => d.symbols);
250
+ const minBarWidth = 4;
251
+ const gap = 2;
252
+
253
+ let x = margin.left;
254
+ filePositions.clear();
255
+
256
+ snapshot.files.forEach((file) => {
257
+ const barWidth = Math.max(minBarWidth, (file.symbols / totalSymbols) * plotWidth * 0.8);
258
+ filePositions.set(file.path, {
259
+ x: x + barWidth / 2,
260
+ width: barWidth,
261
+ file: file,
262
+ });
263
+ x += barWidth + gap;
264
+ });
265
+
266
+ const positions = Array.from(filePositions.values());
267
+ const maxDistance = d3.max(snapshot.arcs, (arc) => {
268
+ const sourcePos = filePositions.get(arc.source);
269
+ const targetPos = filePositions.get(arc.target);
270
+ if (!sourcePos || !targetPos) return 0;
271
+ return Math.abs(targetPos.x - sourcePos.x);
272
+ });
273
+
274
+ const addedFiles = new Set(snapshot.diff?.addedFiles || []);
275
+ const removedFiles = new Set(snapshot.diff?.removedFiles || []);
276
+
277
+ const arcs = g
278
+ .selectAll('.arc')
279
+ .data(snapshot.arcs)
280
+ .enter()
281
+ .append('path')
282
+ .attr('class', 'arc')
283
+ .attr('d', (d) => {
284
+ const sourcePos = filePositions.get(d.source);
285
+ const targetPos = filePositions.get(d.target);
286
+ if (!sourcePos || !targetPos) return null;
287
+
288
+ const x1 = sourcePos.x;
289
+ const x2 = targetPos.x;
290
+ const distance = Math.abs(x2 - x1);
291
+ const arcHeight = Math.min(plotHeight * 0.8, distance * 0.6);
292
+ const midX = (x1 + x2) / 2;
293
+
294
+ return `M ${x1},${baseline} Q ${midX},${baseline - arcHeight} ${x2},${baseline}`;
295
+ })
296
+ .attr('stroke', (d) => {
297
+ const sourcePos = filePositions.get(d.source);
298
+ const targetPos = filePositions.get(d.target);
299
+ if (!sourcePos || !targetPos) return '#00E5FF';
300
+
301
+ const distance = Math.abs(targetPos.x - sourcePos.x);
302
+ const ratio = distance / maxDistance;
303
+
304
+ if (ratio < 0.2) return '#10b981';
305
+ if (ratio < 0.5) return '#00E5FF';
306
+ return '#7c3aed';
307
+ })
308
+ .attr('stroke-width', (d) => Math.min(3, 0.5 + Math.log(d.weight + 1)))
309
+ .attr('fill', 'none')
310
+ .attr('opacity', 0.6);
311
+
312
+ const bars = g
313
+ .selectAll('.file-bar')
314
+ .data(snapshot.files)
315
+ .enter()
316
+ .append('rect')
317
+ .attr('class', 'file-bar')
318
+ .attr('x', (d) => filePositions.get(d.path).x - filePositions.get(d.path).width / 2)
319
+ .attr('y', baseline)
320
+ .attr('width', (d) => filePositions.get(d.path).width)
321
+ .attr('height', 8)
322
+ .attr('fill', (d) => {
323
+ if (addedFiles.has(d.path)) return '#22C55E';
324
+ if (removedFiles.has(d.path)) return '#EF4444';
325
+ return '#00E5FF';
326
+ })
327
+ .attr('rx', 2)
328
+ .style('cursor', 'pointer')
329
+ .on('mouseenter', function (event, d) {
330
+ d3.select(this).attr('fill', '#ffffff');
331
+
332
+ highlightConnections(d.path);
333
+
334
+ showTooltip(event, d);
335
+ })
336
+ .on('mouseleave', function (event, d) {
337
+ d3.select(this).attr('fill', (d) => {
338
+ if (addedFiles.has(d.path)) return '#22C55E';
339
+ if (removedFiles.has(d.path)) return '#EF4444';
340
+ return '#00E5FF';
341
+ });
342
+
343
+ clearHighlight();
344
+ hideTooltip();
345
+ });
346
+
347
+ if (addedFiles.size > 0 || removedFiles.size > 0) {
348
+ setTimeout(() => {
349
+ bars.attr('fill', (d) => {
350
+ if (addedFiles.has(d.path) || removedFiles.has(d.path)) return '#00E5FF';
351
+ return '#00E5FF';
352
+ });
353
+ }, 1000);
354
+ }
355
+ }
356
+
357
+ function highlightConnections(filePath) {
358
+ d3.selectAll('.arc').attr('opacity', (d) => {
359
+ if (d.source === filePath || d.target === filePath) {
360
+ return 1;
361
+ }
362
+ return 0.1;
363
+ });
364
+
365
+ d3.selectAll('.file-bar').attr('opacity', (d) => {
366
+ if (d.path === filePath) return 1;
367
+
368
+ const snapshot = temporalData.snapshots[currentIndex];
369
+ const connected = snapshot.arcs.some(
370
+ (arc) =>
371
+ (arc.source === filePath && arc.target === d.path) ||
372
+ (arc.target === filePath && arc.source === d.path)
373
+ );
374
+
375
+ return connected ? 1 : 0.2;
376
+ });
377
+ }
378
+
379
+ function clearHighlight() {
380
+ d3.selectAll('.arc').attr('opacity', 0.6);
381
+ d3.selectAll('.file-bar').attr('opacity', 1);
382
+ }
383
+
384
+ function showTooltip(event, file) {
385
+ const tooltip = d3.select('body').append('div').attr('class', 'tooltip').style('opacity', 0);
386
+
387
+ const snapshot = temporalData.snapshots[currentIndex];
388
+ const connections = snapshot.arcs.filter((arc) => arc.source === file.path || arc.target === file.path).length;
389
+
390
+ tooltip
391
+ .html(
392
+ `
393
+ <strong>${file.path}</strong><br/>
394
+ Symbols: ${file.symbols}<br/>
395
+ Connections: ${connections}
396
+ `
397
+ )
398
+ .style('left', event.pageX + 10 + 'px')
399
+ .style('top', event.pageY - 28 + 'px')
400
+ .transition()
401
+ .duration(200)
402
+ .style('opacity', 0.95);
403
+ }
404
+
405
+ function hideTooltip() {
406
+ d3.selectAll('.tooltip').remove();
407
+ }
408
+
409
+ function updateChart() {
410
+ const canvas = document.getElementById('evolutionChart');
411
+ const ctx = canvas.getContext('2d');
412
+ const width = canvas.width;
413
+ const height = canvas.height;
414
+
415
+ ctx.clearRect(0, 0, width, height);
416
+
417
+ const snapshots = temporalData.snapshots.slice(0, currentIndex + 1);
418
+ if (snapshots.length === 0) return;
419
+
420
+ const maxFiles = d3.max(snapshots, (d) => d.stats.totalFiles);
421
+ const maxSymbols = d3.max(snapshots, (d) => d.stats.totalSymbols);
422
+ const maxEdges = d3.max(snapshots, (d) => d.stats.totalEdges);
423
+
424
+ const padding = 30;
425
+ const chartWidth = width - padding * 2;
426
+ const chartHeight = height - padding * 2;
427
+
428
+ const xScale = (i) => padding + (i / (snapshots.length - 1)) * chartWidth;
429
+ const yScaleFiles = (v) => height - padding - (v / maxFiles) * chartHeight;
430
+ const yScaleSymbols = (v) => height - padding - (v / maxSymbols) * chartHeight;
431
+ const yScaleEdges = (v) => height - padding - (v / maxEdges) * chartHeight;
432
+
433
+ ctx.strokeStyle = '#4a9eff';
434
+ ctx.lineWidth = 2;
435
+ ctx.beginPath();
436
+ snapshots.forEach((s, i) => {
437
+ const x = xScale(i);
438
+ const y = yScaleFiles(s.stats.totalFiles);
439
+ if (i === 0) ctx.moveTo(x, y);
440
+ else ctx.lineTo(x, y);
441
+ });
442
+ ctx.stroke();
443
+
444
+ ctx.strokeStyle = '#10b981';
445
+ ctx.lineWidth = 2;
446
+ ctx.beginPath();
447
+ snapshots.forEach((s, i) => {
448
+ const x = xScale(i);
449
+ const y = yScaleSymbols(s.stats.totalSymbols);
450
+ if (i === 0) ctx.moveTo(x, y);
451
+ else ctx.lineTo(x, y);
452
+ });
453
+ ctx.stroke();
454
+
455
+ ctx.strokeStyle = '#7c3aed';
456
+ ctx.lineWidth = 2;
457
+ ctx.beginPath();
458
+ snapshots.forEach((s, i) => {
459
+ const x = xScale(i);
460
+ const y = yScaleEdges(s.stats.totalEdges);
461
+ if (i === 0) ctx.moveTo(x, y);
462
+ else ctx.lineTo(x, y);
463
+ });
464
+ ctx.stroke();
465
+
466
+ ctx.strokeStyle = '#ff4a4a';
467
+ ctx.lineWidth = 2;
468
+ const markerX = xScale(currentIndex);
469
+ ctx.beginPath();
470
+ ctx.moveTo(markerX, padding);
471
+ ctx.lineTo(markerX, height - padding);
472
+ ctx.stroke();
473
+ }
474
+
475
+ init();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "depwire-cli",
3
- "version": "0.6.2",
3
+ "version": "0.7.0",
4
4
  "description": "Code cross-reference visualization and AI context engine for TypeScript, JavaScript, Python, and Go. Zero native dependencies — works on Windows, macOS, and Linux.",
5
5
  "type": "module",
6
6
  "bin": {