gitmaps 1.1.3 → 1.1.5

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,68 @@
1
+ import path from 'path';
2
+ import { existsSync } from 'fs';
3
+ import { validateRepoPath } from '../validate-path';
4
+
5
+ function parsePdfInfoPageCount(output: string): number | null {
6
+ const match = output.match(/^Pages:\s+(\d+)/mi);
7
+ if (!match) return null;
8
+ const value = parseInt(match[1], 10);
9
+ return Number.isFinite(value) && value > 0 ? value : null;
10
+ }
11
+
12
+ export async function GET(req: Request) {
13
+ const url = new URL(req.url);
14
+ const repoPath = url.searchParams.get('path') || '';
15
+ const filePath = url.searchParams.get('file') || '';
16
+
17
+ if (!repoPath || !filePath) {
18
+ return Response.json({ error: 'path and file params required' }, { status: 400 });
19
+ }
20
+
21
+ const blocked = validateRepoPath(repoPath);
22
+ if (blocked) return blocked;
23
+
24
+ if (filePath.includes('..') || filePath.startsWith('/')) {
25
+ return Response.json({ error: 'Invalid file path' }, { status: 400 });
26
+ }
27
+
28
+ const fullPath = path.join(repoPath, filePath);
29
+ if (!existsSync(fullPath)) {
30
+ return Response.json({ error: 'File not found' }, { status: 404 });
31
+ }
32
+
33
+ try {
34
+ const info = Bun.spawnSync(['pdfinfo', fullPath], {
35
+ stdout: 'pipe',
36
+ stderr: 'pipe',
37
+ timeout: 15000,
38
+ });
39
+
40
+ if (info.exitCode === 0) {
41
+ const pageCount = parsePdfInfoPageCount(new TextDecoder().decode(info.stdout));
42
+ return Response.json({ pageCount });
43
+ }
44
+ } catch {
45
+ // pdfinfo unavailable
46
+ }
47
+
48
+ try {
49
+ const identify = Bun.spawnSync(['magick', 'identify', fullPath], {
50
+ stdout: 'pipe',
51
+ stderr: 'pipe',
52
+ timeout: 20000,
53
+ });
54
+
55
+ if (identify.exitCode === 0) {
56
+ const lines = new TextDecoder().decode(identify.stdout)
57
+ .split(/\r?\n/)
58
+ .map((line) => line.trim())
59
+ .filter(Boolean);
60
+ const pageCount = lines.length > 0 ? lines.length : null;
61
+ return Response.json({ pageCount });
62
+ }
63
+ } catch {
64
+ // magick unavailable
65
+ }
66
+
67
+ return Response.json({ pageCount: null });
68
+ }
package/app/lib/cards.tsx CHANGED
@@ -965,6 +965,116 @@ export function _buildFileContentHTML(
965
965
  return `<div class="file-content-preview"><pre><code>${code}</code></pre>${truncNote}</div>`;
966
966
  }
967
967
 
968
+ function buildPdfThumbUrl(repoPath: string, filePath: string, page: number) {
969
+ return `/api/repo/pdf-thumb?path=${encodeURIComponent(repoPath)}&file=${encodeURIComponent(filePath)}&page=${page}`;
970
+ }
971
+
972
+ function buildPdfPreviewHTML(repoPath: string, file: any) {
973
+ const thumbUrl = buildPdfThumbUrl(repoPath, file.path, 0);
974
+ return `<div class="file-content-preview file-image-preview pdf-preview" data-file="${escapeHtml(file.path)}" data-page="0" data-page-count="" style="display:flex;align-items:center;justify-content:center;height:100%;background:var(--bg-card);overflow:hidden;position:relative;">
975
+ <img class="pdf-preview-image" src="${thumbUrl}"
976
+ alt="${escapeHtml(file.name)}"
977
+ style="max-width:100%;max-height:100%;object-fit:contain;"
978
+ loading="lazy"
979
+ onerror="this.parentElement.innerHTML='<pre><code><span class=\\'error-notice\\'>PDF preview unavailable</span></code></pre>'" />
980
+ <div class="pdf-preview-toolbar" style="position:absolute;left:8px;right:8px;bottom:8px;display:flex;align-items:center;justify-content:center;gap:8px;padding:6px 8px;background:rgba(10,10,15,0.72);backdrop-filter:blur(8px);border:1px solid rgba(255,255,255,0.08);border-radius:10px;z-index:2;">
981
+ <button class="pdf-page-prev" type="button" style="min-width:28px;height:28px;border-radius:7px;border:1px solid rgba(255,255,255,0.12);background:rgba(255,255,255,0.06);color:var(--text-primary);cursor:pointer;" disabled>‹</button>
982
+ <span class="pdf-page-indicator" style="font-size:11px;color:var(--text-primary);min-width:72px;text-align:center;">Page 1</span>
983
+ <button class="pdf-page-next" type="button" style="min-width:28px;height:28px;border-radius:7px;border:1px solid rgba(255,255,255,0.12);background:rgba(255,255,255,0.06);color:var(--text-primary);cursor:pointer;">›</button>
984
+ </div>
985
+ </div>`;
986
+ }
987
+
988
+ async function setupPdfPreviewControls(ctx: CanvasContext, card: HTMLElement, file: any) {
989
+ const repoPath = ctx.snap().context.repoPath || "";
990
+ if (!repoPath) return;
991
+
992
+ const preview = card.querySelector('.pdf-preview') as HTMLElement | null;
993
+ const img = card.querySelector('.pdf-preview-image') as HTMLImageElement | null;
994
+ const prevBtn = card.querySelector('.pdf-page-prev') as HTMLButtonElement | null;
995
+ const nextBtn = card.querySelector('.pdf-page-next') as HTMLButtonElement | null;
996
+ const indicator = card.querySelector('.pdf-page-indicator') as HTMLElement | null;
997
+ if (!preview || !img || !prevBtn || !nextBtn || !indicator) return;
998
+
999
+ let page = 0;
1000
+ let pageCount: number | null = null;
1001
+ let lastGoodPage = 0;
1002
+ let loading = false;
1003
+
1004
+ const updateUi = () => {
1005
+ preview.dataset.page = String(page);
1006
+ preview.dataset.pageCount = pageCount == null ? '' : String(pageCount);
1007
+ indicator.textContent = pageCount && pageCount > 0 ? `Page ${page + 1} / ${pageCount}` : `Page ${page + 1}`;
1008
+ prevBtn.disabled = loading || page <= 0;
1009
+ nextBtn.disabled = loading || (pageCount != null && page >= pageCount - 1);
1010
+ prevBtn.style.opacity = prevBtn.disabled ? '0.45' : '1';
1011
+ nextBtn.style.opacity = nextBtn.disabled ? '0.45' : '1';
1012
+ prevBtn.style.cursor = prevBtn.disabled ? 'default' : 'pointer';
1013
+ nextBtn.style.cursor = nextBtn.disabled ? 'default' : 'pointer';
1014
+ };
1015
+
1016
+ const loadPage = (nextPage: number) => {
1017
+ if (loading || nextPage < 0) return;
1018
+ if (pageCount != null && nextPage >= pageCount) return;
1019
+
1020
+ loading = true;
1021
+ page = nextPage;
1022
+ updateUi();
1023
+
1024
+ const nextSrc = buildPdfThumbUrl(repoPath, file.path, nextPage);
1025
+ const rollbackPage = lastGoodPage;
1026
+
1027
+ img.onload = () => {
1028
+ lastGoodPage = nextPage;
1029
+ loading = false;
1030
+ img.onload = null;
1031
+ img.onerror = null;
1032
+ updateUi();
1033
+ };
1034
+ img.onerror = () => {
1035
+ loading = false;
1036
+ if (pageCount == null && nextPage > rollbackPage) {
1037
+ pageCount = nextPage;
1038
+ }
1039
+ page = rollbackPage;
1040
+ img.onload = () => {
1041
+ lastGoodPage = rollbackPage;
1042
+ img.onload = null;
1043
+ img.onerror = null;
1044
+ updateUi();
1045
+ };
1046
+ img.onerror = null;
1047
+ img.src = buildPdfThumbUrl(repoPath, file.path, rollbackPage);
1048
+ updateUi();
1049
+ };
1050
+ img.src = nextSrc;
1051
+ };
1052
+
1053
+ prevBtn.addEventListener('click', (e) => {
1054
+ e.stopPropagation();
1055
+ loadPage(page - 1);
1056
+ });
1057
+ nextBtn.addEventListener('click', (e) => {
1058
+ e.stopPropagation();
1059
+ loadPage(page + 1);
1060
+ });
1061
+
1062
+ updateUi();
1063
+
1064
+ try {
1065
+ const res = await fetch(`/api/repo/pdf-meta?path=${encodeURIComponent(repoPath)}&file=${encodeURIComponent(file.path)}`);
1066
+ if (res.ok) {
1067
+ const data = await res.json();
1068
+ if (Number.isFinite(data?.pageCount) && data.pageCount > 0) {
1069
+ pageCount = data.pageCount;
1070
+ updateUi();
1071
+ }
1072
+ }
1073
+ } catch {
1074
+ // Metadata is optional — controls still work with optimistic next/prev.
1075
+ }
1076
+ }
1077
+
968
1078
  // ─── Create all-file card (working tree) ────────────────
969
1079
  export function createAllFileCard(
970
1080
  ctx: CanvasContext,
@@ -1026,13 +1136,7 @@ export function createAllFileCard(
1026
1136
  loading="lazy" />
1027
1137
  </div>`;
1028
1138
  } else if (isPdf) {
1029
- contentHTML = `<div class="file-content-preview file-image-preview" style="display:flex;align-items:center;justify-content:center;height:100%;background:var(--bg-card);overflow:hidden;">
1030
- <img src="/api/repo/pdf-thumb?path=${encodeURIComponent(ctx.snap().context.repoPath || "")}&file=${encodeURIComponent(file.path)}"
1031
- alt="${escapeHtml(file.name)}"
1032
- style="max-width:100%;max-height:100%;object-fit:contain;"
1033
- loading="lazy"
1034
- onerror="this.parentElement.innerHTML='<pre><code><span class=\\'error-notice\\'>PDF preview unavailable</span></code></pre>'" />
1035
- </div>`;
1139
+ contentHTML = buildPdfPreviewHTML(ctx.snap().context.repoPath || "", file);
1036
1140
  } else if (file.isBinary) {
1037
1141
  contentHTML = `<div class="file-content-preview"><pre><code><span class="error-notice">Binary file</span></code></pre></div>`;
1038
1142
  } else if (file.content) {
@@ -1208,6 +1312,10 @@ export function createAllFileCard(
1208
1312
  });
1209
1313
  }
1210
1314
 
1315
+ if (isPdf) {
1316
+ setupPdfPreviewControls(ctx, card, file);
1317
+ }
1318
+
1211
1319
  if (canvasOptions) {
1212
1320
  const previewEl = card.querySelector(".canvas-container") as HTMLElement;
1213
1321
  if (previewEl) {
@@ -0,0 +1,27 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { getLowZoomPreviewText, getLowZoomScale } from './low-zoom-preview';
3
+
4
+ describe('low zoom preview helpers', () => {
5
+ test('anchors preview text to approximate saved scroll position', () => {
6
+ const file = {
7
+ path: 'src/example.ts',
8
+ ext: 'ts',
9
+ content: Array.from({ length: 12 }, (_, i) => `line-${i + 1}`).join('\n'),
10
+ };
11
+
12
+ expect(getLowZoomPreviewText(file, 0).startsWith('line-1')).toBe(true);
13
+ expect(getLowZoomPreviewText(file, 40).startsWith('line-3')).toBe(true);
14
+ });
15
+
16
+ test('skips binary or unsupported files', () => {
17
+ expect(getLowZoomPreviewText({ path: 'image.png', ext: 'png', content: 'abc' }, 0)).toBe('');
18
+ expect(getLowZoomPreviewText({ path: 'bin.dat', ext: 'dat', isBinary: true, content: 'abc' }, 0)).toBe('');
19
+ });
20
+
21
+ test('increases world-space font size as zoom goes down', () => {
22
+ const near = getLowZoomScale(0.25);
23
+ const far = getLowZoomScale(0.1);
24
+ expect(far.titleFont).toBeGreaterThan(near.titleFont);
25
+ expect(far.bodyFont).toBeGreaterThan(near.bodyFont);
26
+ });
27
+ });
@@ -0,0 +1,31 @@
1
+ const PREVIEWABLE_EXTS = new Set([
2
+ 'ts', 'tsx', 'js', 'jsx', 'json', 'css', 'scss', 'html', 'md', 'py', 'rs', 'go', 'vue', 'svelte', 'toml', 'yaml', 'yml', 'sh', 'sql', 'txt'
3
+ ]);
4
+
5
+ export function getLowZoomScale(zoom: number) {
6
+ const clampedZoom = Math.max(0.08, Math.min(0.25, zoom));
7
+ const progress = (0.25 - clampedZoom) / (0.25 - 0.08);
8
+ const desiredScreenTitle = 10 + progress * 4;
9
+ const desiredScreenBody = 8 + progress * 4;
10
+ return {
11
+ titleFont: desiredScreenTitle / clampedZoom,
12
+ bodyFont: desiredScreenBody / clampedZoom,
13
+ bodyLineHeight: (desiredScreenBody * 1.45) / clampedZoom,
14
+ padding: (10 + progress * 4) / clampedZoom,
15
+ gap: (6 + progress * 3) / clampedZoom,
16
+ radius: 8 / clampedZoom,
17
+ };
18
+ }
19
+
20
+ export function getLowZoomPreviewText(file: any, scrollTop: number): string {
21
+ if (!file || file.isBinary || !file.content) return '';
22
+
23
+ const ext = (file.ext || file.path?.split('.').pop() || '').toLowerCase();
24
+ if (!PREVIEWABLE_EXTS.has(ext)) return '';
25
+
26
+ const normalized = String(file.content).replace(/\t/g, ' ');
27
+ const lines = normalized.split('\n');
28
+ const approxLineHeight = 20;
29
+ const startLine = Math.max(0, Math.floor(scrollTop / approxLineHeight));
30
+ return lines.slice(startLine, startLine + 60).join('\n').trim();
31
+ }
@@ -23,6 +23,7 @@
23
23
  */
24
24
  import { measure } from 'measure-fn';
25
25
  import type { CanvasContext } from './context';
26
+ import { getLowZoomPreviewText, getLowZoomScale } from './low-zoom-preview';
26
27
  import { materializeViewport } from './xydraw-bridge';
27
28
 
28
29
  // ── Culling state ──────────────────────────────────────────
@@ -89,7 +90,7 @@ export function getPinnedCards(): Set<string> {
89
90
  return _pinnedCards;
90
91
  }
91
92
 
92
- // ── Status colors for pill cards
93
+ // ── Status colors for low-zoom cards
93
94
  const PILL_COLORS: Record<string, string> = {
94
95
  'ts': '#3178c6',
95
96
  'tsx': '#3178c6',
@@ -118,25 +119,31 @@ function getPillColor(path: string, isChanged: boolean): string {
118
119
  return PILL_COLORS[ext] || '#6b7280'; // Default gray
119
120
  }
120
121
 
122
+ function getSavedScrollTop(ctx: CanvasContext, path: string): number {
123
+ const saved = ctx.positions.get(`scroll:${path}`);
124
+ return saved?.x || 0;
125
+ }
126
+
121
127
  /**
122
128
  * Create a lightweight pill placeholder for a file.
123
129
  * ~3 DOM nodes vs ~100+ for a full card = massive perf win at low zoom.
124
130
  * Uses vertical text to fit file names in compact card footprint.
125
131
  */
126
- function createPillCard(path: string, x: number, y: number, w: number, h: number, isChanged: boolean, animate = false): HTMLElement {
132
+ function createPillCard(ctx: CanvasContext, file: any, path: string, x: number, y: number, w: number, h: number, isChanged: boolean, zoom: number, animate = false): HTMLElement {
127
133
  const pill = document.createElement('div');
128
134
  pill.className = 'file-pill';
129
135
  pill.dataset.path = path;
136
+ pill.dataset.previewMode = 'content';
130
137
  pill.style.cssText = `
131
138
  position: absolute;
132
139
  left: ${x}px;
133
140
  top: ${y}px;
134
141
  width: ${w}px;
135
142
  height: ${h}px;
136
- background: ${getPillColor(path, isChanged)};
143
+ background: linear-gradient(180deg, rgba(15,23,42,0.96) 0%, rgba(2,6,23,0.96) 100%);
137
144
  border-radius: 6px;
138
- opacity: ${animate ? '0' : '0.9'};
139
- contain: layout style;
145
+ opacity: ${animate ? '0' : '0.94'};
146
+ contain: layout style paint;
140
147
  box-shadow: 0 2px 8px rgba(0,0,0,0.3);
141
148
  border: 1px solid rgba(255,255,255,0.12);
142
149
  overflow: hidden;
@@ -146,45 +153,109 @@ function createPillCard(path: string, x: number, y: number, w: number, h: number
146
153
  transform: ${animate ? 'scale(0.92)' : 'scale(1)'};
147
154
  `;
148
155
 
156
+ const accent = document.createElement('div');
157
+ accent.className = 'file-pill-accent';
158
+ accent.style.cssText = `
159
+ position:absolute;
160
+ top:0;
161
+ left:0;
162
+ width:${Math.max(10, w * 0.02)}px;
163
+ height:100%;
164
+ background:${getPillColor(path, isChanged)};
165
+ opacity:0.95;
166
+ pointer-events:none;
167
+ `;
168
+ pill.appendChild(accent);
169
+
170
+ const inner = document.createElement('div');
171
+ inner.className = 'file-pill-inner';
172
+ inner.style.cssText = 'position:absolute; inset:0; transform-origin:top left; pointer-events:none;';
173
+ pill.appendChild(inner);
174
+
175
+ const title = document.createElement('div');
176
+ title.className = 'file-pill-title';
177
+ title.textContent = path.split('/').pop() || path;
178
+ inner.appendChild(title);
179
+
180
+ const sub = document.createElement('div');
181
+ sub.className = 'file-pill-subtitle';
182
+ sub.textContent = path.includes('/') ? path.split('/').slice(0, -1).join('/') : 'root';
183
+ inner.appendChild(sub);
184
+
185
+ const preview = document.createElement('div');
186
+ preview.className = 'file-pill-preview';
187
+ preview.textContent = getLowZoomPreviewText(file, getSavedScrollTop(ctx, path)) || 'Preview unavailable';
188
+ inner.appendChild(preview);
189
+
190
+ updatePillCardLayout(pill, zoom);
191
+
149
192
  // Animate pill entrance
150
193
  if (animate) {
151
194
  requestAnimationFrame(() => {
152
- pill.style.opacity = '0.9';
195
+ pill.style.opacity = '0.94';
153
196
  pill.style.transform = 'scale(1)';
154
197
  });
155
198
  }
156
199
 
157
- // File name label — show parent dir for common ambiguous filenames
158
- const parts = path.split('/');
159
- const filename = parts.pop() || path;
160
- const AMBIGUOUS = ['route.ts', 'route.tsx', 'page.tsx', 'page.ts', 'index.ts', 'index.tsx', 'index.js', 'layout.tsx', 'middleware.ts'];
161
- const name = AMBIGUOUS.includes(filename) && parts.length > 0
162
- ? `${parts[parts.length - 1]}/${filename}`
163
- : filename;
164
- const label = document.createElement('span');
165
- label.className = 'file-pill-label';
166
- label.textContent = name;
167
- label.style.cssText = `
168
- position: absolute;
169
- top: 50%;
170
- left: 50%;
171
- transform: translate(-50%, -50%) rotate(-90deg);
172
- white-space: nowrap;
173
- font-size: 48px;
174
- font-weight: 700;
175
- color: #fff;
176
- overflow: hidden;
177
- text-overflow: ellipsis;
178
- max-width: ${h - 40}px;
179
- line-height: 1;
180
- letter-spacing: 2px;
181
- font-family: 'JetBrains Mono', monospace;
182
- text-shadow: 0 2px 8px rgba(0,0,0,0.7);
183
- pointer-events: none;
200
+ return pill;
201
+ }
202
+
203
+ function updatePillCardLayout(pill: HTMLElement, zoom: number) {
204
+ const x = parseFloat(pill.style.left) || 0;
205
+ const y = parseFloat(pill.style.top) || 0;
206
+ const w = parseFloat(pill.style.width) || 580;
207
+ const h = parseFloat(pill.style.height) || 700;
208
+ const scale = getLowZoomScale(zoom);
209
+ pill.dataset.zoomBucket = zoom.toFixed(3);
210
+ pill.style.borderRadius = `${Math.max(6, scale.radius)}px`;
211
+
212
+ const inner = pill.querySelector('.file-pill-inner') as HTMLElement | null;
213
+ const title = pill.querySelector('.file-pill-title') as HTMLElement | null;
214
+ const sub = pill.querySelector('.file-pill-subtitle') as HTMLElement | null;
215
+ const preview = pill.querySelector('.file-pill-preview') as HTMLElement | null;
216
+ if (!inner || !title || !sub || !preview) return;
217
+
218
+ inner.style.padding = `${scale.padding}px ${scale.padding}px ${scale.padding}px ${scale.padding + Math.max(14, w * 0.02)}px`;
219
+ inner.style.display = 'flex';
220
+ inner.style.flexDirection = 'column';
221
+ inner.style.gap = `${scale.gap}px`;
222
+
223
+ title.style.cssText = `
224
+ color:#f8fafc;
225
+ font-family:'JetBrains Mono', monospace;
226
+ font-size:${scale.titleFont}px;
227
+ font-weight:700;
228
+ line-height:1.05;
229
+ white-space:nowrap;
230
+ overflow:hidden;
231
+ text-overflow:ellipsis;
232
+ text-shadow:0 2px 8px rgba(0,0,0,0.45);
184
233
  `;
185
- pill.appendChild(label);
186
234
 
187
- return pill;
235
+ sub.style.cssText = `
236
+ color:rgba(226,232,240,0.72);
237
+ font-family:'JetBrains Mono', monospace;
238
+ font-size:${Math.max(scale.bodyFont * 0.78, 8 / Math.max(zoom, 0.08))}px;
239
+ line-height:1.05;
240
+ white-space:nowrap;
241
+ overflow:hidden;
242
+ text-overflow:ellipsis;
243
+ `;
244
+
245
+ preview.style.cssText = `
246
+ color:rgba(226,232,240,0.92);
247
+ font-family:'JetBrains Mono', monospace;
248
+ font-size:${scale.bodyFont}px;
249
+ line-height:${scale.bodyLineHeight}px;
250
+ white-space:pre-wrap;
251
+ overflow-wrap:anywhere;
252
+ word-break:break-word;
253
+ overflow:hidden;
254
+ flex:1 1 auto;
255
+ text-shadow:0 1px 4px rgba(0,0,0,0.3);
256
+ mask-image:linear-gradient(to bottom, black 0%, black 84%, transparent 100%);
257
+ -webkit-mask-image:linear-gradient(to bottom, black 0%, black 84%, transparent 100%);
258
+ `;
188
259
  }
189
260
 
190
261
  /**
@@ -399,9 +470,11 @@ export function performViewportCulling(ctx: CanvasContext) {
399
470
  );
400
471
 
401
472
  if (inView && !pillCards.has(path)) {
402
- const pill = createPillCard(path, x, y, cardW, cardH, !!isChanged, true);
473
+ const pill = createPillCard(ctx, file, path, x, y, cardW, cardH, !!isChanged, zoom, true);
403
474
  ctx.canvas.appendChild(pill);
404
475
  pillCards.set(path, pill);
476
+ } else if (inView && pillCards.has(path)) {
477
+ updatePillCardLayout(pillCards.get(path)!, zoom);
405
478
  } else if (!inView && pillCards.has(path)) {
406
479
  removePillForPath(path);
407
480
  }
@@ -424,10 +497,13 @@ export function performViewportCulling(ctx: CanvasContext) {
424
497
  );
425
498
 
426
499
  if (inView) {
427
- const pill = createPillCard(path, x, y, w, h, isChanged, true);
500
+ const file = ctx.allFilesData?.find(f => f.path === path) || ctx.commitFilesData?.find(f => f.path === path) || null;
501
+ const pill = createPillCard(ctx, file, path, x, y, w, h, isChanged, zoom, true);
428
502
  ctx.canvas.appendChild(pill);
429
503
  pillCards.set(path, pill);
430
504
  }
505
+ } else {
506
+ updatePillCardLayout(pillCards.get(path)!, zoom);
431
507
  }
432
508
  }
433
509
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitmaps",
3
- "version": "1.1.3",
3
+ "version": "1.1.5",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "gitmaps": "cli.ts"
@@ -16,13 +16,14 @@
16
16
  "scripts": {
17
17
  "dev": "bun run server.ts",
18
18
  "start": "bun run server.ts",
19
- "test": "bun test app/api/repo/load/route.test.ts app/lib/route-catchall.test.ts app/lib/status-bar.test.ts app/lib/xydraw.test.ts app/lib/transclusion-smoke.test.ts app/lib/repo-select.test.ts packages/galaxydraw/perf.test.ts",
19
+ "test": "bun test app/api/repo/load/route.test.ts app/lib/route-catchall.test.ts app/lib/status-bar.test.ts app/lib/xydraw.test.ts app/lib/transclusion-smoke.test.ts app/lib/repo-select.test.ts app/lib/low-zoom-preview.test.ts packages/galaxydraw/perf.test.ts",
20
20
  "smoke:browser": "bun scripts/browser-smoke-local.ts",
21
21
  "smoke:browser-tools": "bash scripts/browser-smoke-local.sh",
22
22
  "smoke:browser-tools:load": "bash scripts/browser-repo-load-smoke.sh",
23
23
  "smoke:browser-tools:guard": "bash scripts/browser-smoke-guard.sh",
24
24
  "smoke:browser-tools:self-check": "bash scripts/browser-smoke-self-check.sh",
25
25
  "smoke:browser-tools:check": "bash scripts/browser-smoke-check.sh",
26
+ "smoke:docker-image": "bash scripts/docker-image-smoke.sh",
26
27
  "prepublishOnly": "echo 'Publishing gitmaps to npm'"
27
28
  },
28
29
  "keywords": [