gitmaps 1.1.13 → 1.1.14

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
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, test } from 'bun:test';
2
- import { estimatePreviewCharsPerLine, estimatePreviewLineCapacity, estimatePreviewMaxScroll, estimateTitleCharsPerLine, getLowZoomPreviewText, getLowZoomScale, wrapPreviewText } from './low-zoom-preview';
2
+ import { collectPreviewDiffMarkers, estimatePreviewCharsPerLine, estimatePreviewLineCapacity, estimatePreviewMaxScroll, estimateTitleCharsPerLine, getLowZoomPreviewText, getLowZoomScale, getPreviewScrollMetrics, wrapPreviewText } from './low-zoom-preview';
3
3
 
4
4
  describe('low zoom preview helpers', () => {
5
5
  test('anchors preview text to approximate saved scroll position', () => {
@@ -52,6 +52,23 @@ describe('low zoom preview helpers', () => {
52
52
  expect(estimatePreviewMaxScroll(longFile, 700, 1)).toBeGreaterThan(estimatePreviewMaxScroll(shortFile, 700, 1));
53
53
  });
54
54
 
55
+ test('scroll metrics produce a visible thumb position', () => {
56
+ const file = { content: Array.from({ length: 120 }, (_, i) => `line-${i}`).join('\n') };
57
+ const metrics = getPreviewScrollMetrics(file, 700, 1, 120);
58
+ expect(metrics.thumbHeight).toBeGreaterThan(0);
59
+ expect(metrics.thumbY).toBeGreaterThanOrEqual(metrics.trackPadding);
60
+ });
61
+
62
+ test('collects diff markers from added and deleted lines', () => {
63
+ const markers = collectPreviewDiffMarkers({
64
+ addedLines: new Set([2, 10]),
65
+ deletedBeforeLine: new Map([[5, ['gone']]]),
66
+ }, 20);
67
+ expect(markers.length).toBe(3);
68
+ expect(markers.some((m) => m.color === '#22c55e')).toBe(true);
69
+ expect(markers.some((m) => m.color === '#ef4444')).toBe(true);
70
+ });
71
+
55
72
  test('title typography is larger than body typography for readability', () => {
56
73
  const scale = getLowZoomScale(0.18);
57
74
  expect(scale.titleFont).toBeGreaterThan(scale.bodyFont);
@@ -122,6 +122,33 @@ export function estimatePreviewMaxScroll(file: any, height: number, zoom: number
122
122
  return Math.max(0, (totalLines - visibleLines) * 20);
123
123
  }
124
124
 
125
+ export function getPreviewScrollMetrics(file: any, height: number, zoom: number, scrollTop: number) {
126
+ const totalLines = Math.max(1, String(file?.content || '').split('\n').length);
127
+ const visibleLines = Math.max(1, estimatePreviewLineCapacity(height, zoom));
128
+ const maxScroll = Math.max(0, (totalLines - visibleLines) * 20);
129
+ const trackPadding = 10;
130
+ const trackHeight = Math.max(24, height - trackPadding * 2);
131
+ const thumbHeight = Math.max(18, (visibleLines / totalLines) * trackHeight);
132
+ const thumbTravel = Math.max(0, trackHeight - thumbHeight);
133
+ const thumbY = trackPadding + (maxScroll > 0 ? (scrollTop / maxScroll) * thumbTravel : 0);
134
+ return { totalLines, visibleLines, maxScroll, trackPadding, trackHeight, thumbHeight, thumbY };
135
+ }
136
+
137
+ export function collectPreviewDiffMarkers(file: any, totalLines: number) {
138
+ const markers: Array<{ ratio: number; color: string }> = [];
139
+ const safeTotal = Math.max(1, totalLines);
140
+ const added = file?.addedLines instanceof Set ? Array.from(file.addedLines) : [];
141
+ const deletedBefore = file?.deletedBeforeLine instanceof Map ? Array.from(file.deletedBeforeLine.keys()) : [];
142
+
143
+ for (const line of added) {
144
+ markers.push({ ratio: Math.max(0, Math.min(1, (line - 1) / safeTotal)), color: '#22c55e' });
145
+ }
146
+ for (const line of deletedBefore) {
147
+ markers.push({ ratio: Math.max(0, Math.min(1, (line - 1) / safeTotal)), color: '#ef4444' });
148
+ }
149
+ return markers;
150
+ }
151
+
125
152
  export function renderLowZoomPreviewCanvas(
126
153
  canvas: HTMLCanvasElement,
127
154
  params: {
@@ -140,6 +167,7 @@ export function renderLowZoomPreviewCanvas(
140
167
  const scale = getLowZoomScale(zoom);
141
168
  const ctx = canvas.getContext('2d');
142
169
  if (!ctx) return;
170
+ const scrollMetrics = getPreviewScrollMetrics(file, height, zoom, scrollTop);
143
171
 
144
172
  canvas.width = Math.max(1, Math.floor(width * dpr));
145
173
  canvas.height = Math.max(1, Math.floor(height * dpr));
@@ -157,12 +185,15 @@ export function renderLowZoomPreviewCanvas(
157
185
  ctx.fill();
158
186
 
159
187
  const accentWidth = Math.max(6, width * 0.012);
188
+ const scrollbarWidth = 6;
189
+ const markerLaneWidth = 4;
190
+ const rightRailWidth = scrollbarWidth + markerLaneWidth + 8;
160
191
  ctx.fillStyle = accentColor;
161
192
  ctx.fillRect(0, 0, accentWidth, height);
162
193
 
163
194
  const leftInset = scale.padding + accentWidth + scale.gap * 0.8;
164
195
  const topInset = scale.padding;
165
- const maxTextWidth = Math.max(40, width - leftInset - scale.padding);
196
+ const maxTextWidth = Math.max(40, width - leftInset - scale.padding - rightRailWidth);
166
197
 
167
198
  ctx.textBaseline = 'top';
168
199
  ctx.font = `700 ${scale.titleFont}px "JetBrains Mono", monospace`;
@@ -206,6 +237,26 @@ export function renderLowZoomPreviewCanvas(
206
237
  if (y > height - scale.padding) return;
207
238
  ctx.fillText(line, leftInset, y);
208
239
  });
240
+
241
+ const trackX = width - scrollbarWidth - 4;
242
+ const markerX = trackX - markerLaneWidth - 3;
243
+ const trackY = scrollMetrics.trackPadding;
244
+ const trackHeight = scrollMetrics.trackHeight;
245
+
246
+ ctx.fillStyle = 'rgba(255,255,255,0.08)';
247
+ roundRect(ctx, trackX, trackY, scrollbarWidth, trackHeight, scrollbarWidth / 2);
248
+ ctx.fill();
249
+
250
+ const markers = collectPreviewDiffMarkers(file, scrollMetrics.totalLines);
251
+ for (const marker of markers) {
252
+ const y = trackY + marker.ratio * trackHeight;
253
+ ctx.fillStyle = marker.color;
254
+ ctx.fillRect(markerX, Math.max(trackY, y), markerLaneWidth, 3);
255
+ }
256
+
257
+ ctx.fillStyle = 'rgba(196,181,253,0.9)';
258
+ roundRect(ctx, trackX, scrollMetrics.thumbY, scrollbarWidth, scrollMetrics.thumbHeight, scrollbarWidth / 2);
259
+ ctx.fill();
209
260
  }
210
261
 
211
262
  function trimToWidth(ctx: CanvasRenderingContext2D, text: string, maxWidth: number) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitmaps",
3
- "version": "1.1.13",
3
+ "version": "1.1.14",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "gitmaps": "cli.ts"