gitmaps 1.1.15 → 1.1.17

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.
package/app/analytics.db CHANGED
Binary file
@@ -69,6 +69,13 @@ describe('low zoom preview helpers', () => {
69
69
  expect(markers.some((m) => m.color === '#ef4444')).toBe(true);
70
70
  });
71
71
 
72
+ test('whole-file added/deleted states create full-height rail markers', () => {
73
+ const added = collectPreviewDiffMarkers({ status: 'added' }, 20);
74
+ const deleted = collectPreviewDiffMarkers({ status: 'deleted' }, 20);
75
+ expect(added[0]?.height).toBe(1);
76
+ expect(deleted[0]?.height).toBe(1);
77
+ });
78
+
72
79
  test('title typography is larger than body typography for readability', () => {
73
80
  const scale = getLowZoomScale(0.18);
74
81
  expect(scale.titleFont).toBeGreaterThan(scale.bodyFont);
@@ -135,11 +135,20 @@ export function getPreviewScrollMetrics(file: any, height: number, zoom: number,
135
135
  }
136
136
 
137
137
  export function collectPreviewDiffMarkers(file: any, totalLines: number) {
138
- const markers: Array<{ ratio: number; color: string }> = [];
138
+ const markers: Array<{ ratio: number; color: string; height?: number }> = [];
139
139
  const safeTotal = Math.max(1, totalLines);
140
140
  const added = file?.addedLines instanceof Set ? Array.from(file.addedLines) : [];
141
141
  const deletedBefore = file?.deletedBeforeLine instanceof Map ? Array.from(file.deletedBeforeLine.keys()) : [];
142
142
 
143
+ if (file?.status === 'added') {
144
+ markers.push({ ratio: 0, color: '#22c55e', height: 1 });
145
+ return markers;
146
+ }
147
+ if (file?.status === 'deleted') {
148
+ markers.push({ ratio: 0, color: '#ef4444', height: 1 });
149
+ return markers;
150
+ }
151
+
143
152
  for (const line of added) {
144
153
  markers.push({ ratio: Math.max(0, Math.min(1, (line - 1) / safeTotal)), color: '#22c55e' });
145
154
  }
@@ -185,9 +194,9 @@ export function renderLowZoomPreviewCanvas(
185
194
  ctx.fill();
186
195
 
187
196
  const accentWidth = Math.max(6, width * 0.012);
188
- const scrollbarWidth = 6;
189
- const markerLaneWidth = 4;
190
- const rightRailWidth = scrollbarWidth + markerLaneWidth + 8;
197
+ const scrollbarWidth = 7;
198
+ const markerLaneWidth = 8;
199
+ const rightRailWidth = scrollbarWidth + markerLaneWidth + 12;
191
200
  ctx.fillStyle = accentColor;
192
201
  ctx.fillRect(0, 0, accentWidth, height);
193
202
 
@@ -238,23 +247,29 @@ export function renderLowZoomPreviewCanvas(
238
247
  ctx.fillText(line, leftInset, y);
239
248
  });
240
249
 
241
- const trackX = width - scrollbarWidth - 4;
242
- const markerX = trackX - markerLaneWidth - 3;
250
+ const trackX = width - scrollbarWidth - 5;
251
+ const markerX = trackX - markerLaneWidth - 4;
243
252
  const trackY = scrollMetrics.trackPadding;
244
253
  const trackHeight = scrollMetrics.trackHeight;
245
254
 
246
255
  ctx.fillStyle = 'rgba(255,255,255,0.08)';
256
+ roundRect(ctx, markerX, trackY, markerLaneWidth, trackHeight, 3);
257
+ ctx.fill();
258
+
259
+ ctx.fillStyle = 'rgba(255,255,255,0.12)';
247
260
  roundRect(ctx, trackX, trackY, scrollbarWidth, trackHeight, scrollbarWidth / 2);
248
261
  ctx.fill();
249
262
 
250
263
  const markers = collectPreviewDiffMarkers(file, scrollMetrics.totalLines);
251
264
  for (const marker of markers) {
252
- const y = trackY + marker.ratio * trackHeight;
265
+ const markerHeight = marker.height === 1 ? trackHeight : 5;
266
+ const y = marker.height === 1 ? trackY : trackY + marker.ratio * Math.max(0, trackHeight - markerHeight);
253
267
  ctx.fillStyle = marker.color;
254
- ctx.fillRect(markerX, Math.max(trackY, y), markerLaneWidth, 3);
268
+ roundRect(ctx, markerX, Math.max(trackY, y), markerLaneWidth, markerHeight, 2);
269
+ ctx.fill();
255
270
  }
256
271
 
257
- ctx.fillStyle = 'rgba(196,181,253,0.9)';
272
+ ctx.fillStyle = 'rgba(196,181,253,0.96)';
258
273
  roundRect(ctx, trackX, scrollMetrics.thumbY, scrollbarWidth, scrollMetrics.thumbHeight, scrollbarWidth / 2);
259
274
  ctx.fill();
260
275
  }
@@ -23,7 +23,7 @@
23
23
  */
24
24
  import { measure } from 'measure-fn';
25
25
  import type { CanvasContext } from './context';
26
- import { estimatePreviewMaxScroll, getLowZoomScale, renderLowZoomPreviewCanvas } from './low-zoom-preview';
26
+ import { collectPreviewDiffMarkers, estimatePreviewMaxScroll, getLowZoomScale, getPreviewScrollMetrics, renderLowZoomPreviewCanvas } from './low-zoom-preview';
27
27
  import { materializeViewport } from './xydraw-bridge';
28
28
 
29
29
  // ── Culling state ──────────────────────────────────────────
@@ -204,6 +204,11 @@ function createPillCard(ctx: CanvasContext, file: any, path: string, x: number,
204
204
  canvas.style.cssText = 'display:block;width:100%;height:100%;pointer-events:none;';
205
205
  pill.appendChild(canvas);
206
206
 
207
+ const diffOverlay = document.createElement('div');
208
+ diffOverlay.className = 'file-pill-diff-overlay';
209
+ diffOverlay.style.cssText = 'position:absolute;top:10px;right:15px;bottom:10px;width:12px;pointer-events:none;z-index:2;';
210
+ pill.appendChild(diffOverlay);
211
+
207
212
  (pill as any)._fileData = file;
208
213
  updatePillCardLayout(ctx, pill, zoom, isChanged);
209
214
 
@@ -223,8 +228,10 @@ function updatePillCardLayout(ctx: CanvasContext, pill: HTMLElement, zoom: numbe
223
228
  const scale = getLowZoomScale(zoom);
224
229
  const path = pill.dataset.path || '';
225
230
  const canvas = pill.querySelector('.file-pill-canvas') as HTMLCanvasElement | null;
231
+ const diffOverlay = pill.querySelector('.file-pill-diff-overlay') as HTMLElement | null;
226
232
  const file = (pill as any)._fileData || ctx.allFilesData?.find(f => f.path === path) || ctx.commitFilesData?.find(f => f.path === path) || null;
227
233
  const changed = isChanged ?? pill.dataset.changed === 'true';
234
+ const scrollTop = getSavedScrollTop(ctx, path);
228
235
  pill.dataset.zoomBucket = zoom.toFixed(3);
229
236
  pill.style.borderRadius = `${Math.max(6, scale.radius)}px`;
230
237
  if (!canvas) return;
@@ -235,10 +242,49 @@ function updatePillCardLayout(ctx: CanvasContext, pill: HTMLElement, zoom: numbe
235
242
  width: w,
236
243
  height: h,
237
244
  zoom,
238
- scrollTop: getSavedScrollTop(ctx, path),
245
+ scrollTop,
239
246
  accentColor: getPillColor(path, changed),
240
247
  isChanged: changed,
241
248
  });
249
+
250
+ if (diffOverlay) {
251
+ const metrics = getPreviewScrollMetrics(file, h, zoom, scrollTop);
252
+ const markers = collectPreviewDiffMarkers(file, metrics.totalLines);
253
+ diffOverlay.innerHTML = '';
254
+ diffOverlay.style.top = `${metrics.trackPadding}px`;
255
+ diffOverlay.style.bottom = `${metrics.trackPadding}px`;
256
+ diffOverlay.style.right = '15px';
257
+ diffOverlay.style.width = '12px';
258
+ diffOverlay.style.pointerEvents = 'auto';
259
+
260
+ for (const marker of markers) {
261
+ const btn = document.createElement('button');
262
+ const markerHeight = marker.height === 1 ? metrics.trackHeight : 8;
263
+ const y = marker.height === 1 ? 0 : marker.ratio * Math.max(0, metrics.trackHeight - markerHeight);
264
+ btn.type = 'button';
265
+ btn.className = 'file-pill-diff-marker';
266
+ btn.title = marker.color === '#22c55e' ? 'Jump to added lines' : 'Jump to deleted lines';
267
+ btn.style.cssText = `position:absolute;left:0;width:12px;height:${markerHeight}px;top:${y}px;border:none;border-radius:3px;background:${marker.color};box-shadow:0 0 0 1px rgba(255,255,255,0.18),0 0 10px ${marker.color}66;cursor:pointer;padding:0;pointer-events:auto;`;
268
+ btn.addEventListener('mousedown', (e) => {
269
+ e.stopPropagation();
270
+ });
271
+ btn.addEventListener('click', (e) => {
272
+ e.preventDefault();
273
+ e.stopPropagation();
274
+ const maxScroll = estimatePreviewMaxScroll(file, h, zoom);
275
+ const next = marker.height === 1 ? 0 : Math.max(0, Math.min(maxScroll, marker.ratio * maxScroll));
276
+ const key = `scroll:${path}`;
277
+ const existing = ctx.positions.get(key) || {};
278
+ ctx.positions.set(key, { ...existing, x: next, y: existing.y || 0 });
279
+ try {
280
+ const { debounceSaveScroll } = require('./cards');
281
+ debounceSaveScroll(ctx, path, next);
282
+ } catch { }
283
+ updatePillCardLayout(ctx, pill, zoom, changed);
284
+ });
285
+ diffOverlay.appendChild(btn);
286
+ }
287
+ }
242
288
  }
243
289
 
244
290
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitmaps",
3
- "version": "1.1.15",
3
+ "version": "1.1.17",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "gitmaps": "cli.ts"