gitmaps 1.1.22 → 1.1.24

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
package/app/globals.css CHANGED
@@ -1145,24 +1145,17 @@ body {
1145
1145
 
1146
1146
  /* ── Sticky Zoom Controls ── */
1147
1147
  body.repo-loading .sticky-zoom-pill,
1148
- body.repo-loading .floating-top-controls,
1148
+ body.repo-loading .detail-mode-switch,
1149
1149
  body.landing-placeholder-visible .sticky-zoom-pill,
1150
- body.landing-placeholder-visible .floating-top-controls {
1150
+ body.landing-placeholder-visible .detail-mode-switch {
1151
1151
  display: none;
1152
1152
  }
1153
1153
 
1154
- .floating-top-controls {
1154
+ .detail-mode-switch {
1155
1155
  position: fixed;
1156
1156
  top: 12px;
1157
1157
  right: 144px;
1158
1158
  z-index: 10001;
1159
- display: flex;
1160
- align-items: center;
1161
- gap: 8px;
1162
- }
1163
-
1164
- .detail-mode-switch {
1165
- position: relative;
1166
1159
  }
1167
1160
 
1168
1161
  .detail-mode-btn {
@@ -1199,15 +1192,6 @@ body.landing-placeholder-visible .floating-top-controls {
1199
1192
  box-shadow: 0 10px 28px rgba(59, 130, 246, 0.16);
1200
1193
  }
1201
1194
 
1202
- .detail-mode-btn--secondary {
1203
- border-color: rgba(148, 163, 184, 0.28);
1204
- color: #e2e8f0;
1205
- }
1206
-
1207
- .detail-mode-btn--secondary:hover {
1208
- border-color: rgba(148, 163, 184, 0.48);
1209
- }
1210
-
1211
1195
  .detail-mode-label {
1212
1196
  opacity: 0.72;
1213
1197
  text-transform: uppercase;
package/app/layout.tsx CHANGED
@@ -875,16 +875,10 @@ export default function RootLayout({ children }: { children: any }) {
875
875
  </div>
876
876
 
877
877
  {/* Sticky Zoom Controls — floating pill, bottom-right */}
878
- <div className="floating-top-controls">
879
- <div id="detailModeSwitch" className="detail-mode-switch" title="Toggle preview-focused detail mode">
880
- <button id="toggleDetailMode" type="button" className="detail-mode-btn">
881
- <span className="detail-mode-label">Renderer</span>
882
- <span id="detailModeState" className="detail-mode-state">Preview</span>
883
- </button>
884
- </div>
885
- <button id="openSettingsFloating" type="button" className="detail-mode-btn detail-mode-btn--secondary" title="Open settings">
886
- <span className="detail-mode-label">Settings</span>
887
- <span className="detail-mode-state">Tune</span>
878
+ <div id="detailModeSwitch" className="detail-mode-switch" title="Toggle preview-focused detail mode">
879
+ <button id="toggleDetailMode" type="button" className="detail-mode-btn">
880
+ <span className="detail-mode-label">Renderer</span>
881
+ <span id="detailModeState" className="detail-mode-state">Preview</span>
888
882
  </button>
889
883
  </div>
890
884
 
@@ -943,6 +937,22 @@ export default function RootLayout({ children }: { children: any }) {
943
937
  <path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7" />
944
938
  </svg>
945
939
  </button>
940
+ <div className="sz-divider" />
941
+ <button id="openSettingsBottom" className="sz-btn sz-fit" title="Open settings">
942
+ <svg
943
+ viewBox="0 0 24 24"
944
+ width="14"
945
+ height="14"
946
+ fill="none"
947
+ stroke="currentColor"
948
+ strokeWidth="2"
949
+ strokeLinecap="round"
950
+ strokeLinejoin="round"
951
+ >
952
+ <circle cx="12" cy="12" r="3" />
953
+ <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" />
954
+ </svg>
955
+ </button>
946
956
  </div>
947
957
 
948
958
  {/* Bottom Layers Bar */}
@@ -50,32 +50,32 @@ function ContextMenu({ onAction, onActionLayer, onSelectFolder, isInActiveLayer,
50
50
 
51
51
  return (
52
52
  <>
53
- <button className="ctx-item" onClick={() => onAction('copy-path')}>📋 Copy path</button>
54
- <button className="ctx-item" onClick={() => onAction('select')}>☑️ Select</button>
53
+ <button type="button" className="ctx-item" onClick={() => onAction('copy-path')}>📋 Copy path</button>
54
+ <button type="button" className="ctx-item" onClick={() => onAction('select')}>☑️ Select</button>
55
55
  {ancestors.length > 0 ? (
56
56
  <div className="ctx-item ctx-dropdown">
57
57
  <span>📁 Select from folder ▸</span>
58
58
  <div className="ctx-dropdown-content">
59
59
  {ancestors.map(dir => (
60
- <button key={dir} className="ctx-item" onClick={() => onSelectFolder(dir)}>
60
+ <button type="button" key={dir} className="ctx-item" onClick={() => onSelectFolder(dir)}>
61
61
  📂 {dir}
62
62
  </button>
63
63
  ))}
64
64
  </div>
65
65
  </div>
66
66
  ) : (
67
- <button className="ctx-item" onClick={() => onSelectFolder('')}>📁 Select all (root)</button>
67
+ <button type="button" className="ctx-item" onClick={() => onSelectFolder('')}>📁 Select all (root)</button>
68
68
  )}
69
- <button className="ctx-item" onClick={() => onAction('pin')}>{pinned ? '📌 Unpin card' : '📌 Pin card'}</button>
69
+ <button type="button" className="ctx-item" onClick={() => onAction('pin')}>{pinned ? '📌 Unpin card' : '📌 Pin card'}</button>
70
70
  <div className="ctx-divider"></div>
71
- <button className="ctx-item" onClick={() => onAction('expand')}>📖 Open in Editor</button>
72
- <button className="ctx-item" onClick={() => onAction('edit')}>✏️ Edit file</button>
73
- <button className="ctx-item" onClick={() => onAction('blame')}>👤 Git blame</button>
74
- <button className="ctx-item" onClick={() => onAction('connect')}>🔗 Connect to...</button>
75
- <button className="ctx-item" onClick={() => onAction('fit-content')}>📏 Fit content</button>
76
- <button className="ctx-item" onClick={() => onAction('fit-screen')}>📺 Fit screen</button>
71
+ <button type="button" className="ctx-item" onClick={() => onAction('expand')}>📖 Open in Editor</button>
72
+ <button type="button" className="ctx-item" onClick={() => onAction('edit')}>✏️ Edit file</button>
73
+ <button type="button" className="ctx-item" onClick={() => onAction('blame')}>👤 Git blame</button>
74
+ <button type="button" className="ctx-item" onClick={() => onAction('connect')}>🔗 Connect to...</button>
75
+ <button type="button" className="ctx-item" onClick={() => onAction('fit-content')}>📏 Fit content</button>
76
+ <button type="button" className="ctx-item" onClick={() => onAction('fit-screen')}>📺 Fit screen</button>
77
77
  <div className="ctx-divider"></div>
78
- <button className="ctx-item" onClick={() => onAction('history')}>🕰️ File history</button>
78
+ <button type="button" className="ctx-item" onClick={() => onAction('history')}>🕰️ File history</button>
79
79
  <div className="ctx-item ctx-dropdown">
80
80
  <span>📦 Move to Layer ▸</span>
81
81
  <div className="ctx-dropdown-content">
@@ -83,24 +83,24 @@ function ContextMenu({ onAction, onActionLayer, onSelectFolder, isInActiveLayer,
83
83
  <div className="ctx-item" style="opacity: 0.5; pointer-events: none">No custom layers</div>
84
84
  ) : (
85
85
  customLayers.map(l => (
86
- <button key={l.id} className="ctx-item" onClick={() => onActionLayer(l.id)}>
86
+ <button type="button" key={l.id} className="ctx-item" onClick={() => onActionLayer(l.id)}>
87
87
  + {l.name}
88
88
  </button>
89
89
  ))
90
90
  )}
91
91
  <div className="ctx-divider"></div>
92
- <button className="ctx-item" onClick={() => onActionLayer('new')}>✨ Create New Layer</button>
92
+ <button type="button" className="ctx-item" onClick={() => onActionLayer('new')}>✨ Create New Layer</button>
93
93
  </div>
94
94
  </div>
95
95
  {isInActiveLayer && (
96
- <button className="ctx-item" onClick={() => onAction('remove-from-layer')} style="color: #60a5fa">
96
+ <button type="button" className="ctx-item" onClick={() => onAction('remove-from-layer')} style="color: #60a5fa">
97
97
  ↩ Move to Main
98
98
  </button>
99
99
  )}
100
100
  <div className="ctx-divider"></div>
101
- <button className="ctx-item" onClick={() => onAction('hide')} style="color: #f59e0b">🙈 Hide file</button>
102
- <button className="ctx-item" onClick={() => onAction('rename')}>✏️ Rename / Move</button>
103
- <button className="ctx-item" onClick={() => onAction('delete')} style="color: #ef4444">🗑️ Delete file</button>
101
+ <button type="button" className="ctx-item" onClick={() => onAction('hide')} style="color: #f59e0b">🙈 Hide file</button>
102
+ <button type="button" className="ctx-item" onClick={() => onAction('rename')}>✏️ Rename / Move</button>
103
+ <button type="button" className="ctx-item" onClick={() => onAction('delete')} style="color: #ef4444">🗑️ Delete file</button>
104
104
  </>
105
105
  );
106
106
  }
@@ -94,7 +94,7 @@ export function createCanvasContext(actor: any): CanvasContext {
94
94
  allFilesData: null,
95
95
  commitFilesData: null,
96
96
  deferredCards: new Map(),
97
- controlMode: (localStorage.getItem('gitcanvas:controlMode') as any) || 'advanced',
97
+ controlMode: (localStorage.getItem('gitcanvas:controlMode') as any) || 'simple',
98
98
  };
99
99
 
100
100
  currentCanvasContext = ctx;
@@ -744,7 +744,7 @@ export function setupEventListeners(ctx: CanvasContext) {
744
744
  import('./settings-modal').then(({ openSettingsModal }) => openSettingsModal(ctx));
745
745
  };
746
746
  document.getElementById('openSettings')?.addEventListener('click', openSettings);
747
- document.getElementById('openSettingsFloating')?.addEventListener('click', openSettings);
747
+ document.getElementById('openSettingsBottom')?.addEventListener('click', openSettings);
748
748
 
749
749
  // Global search
750
750
  document.getElementById('openGlobalSearch')?.addEventListener('click', () => {
@@ -906,6 +906,24 @@ export function setupEventListeners(ctx: CanvasContext) {
906
906
  return;
907
907
  }
908
908
 
909
+ // Ctrl+= / Ctrl+- / Ctrl+0 = preview text size
910
+ if ((e.ctrlKey || e.metaKey) && !e.shiftKey && (e.key === '=' || e.key === '+' || e.key === '-' || e.key === '0')) {
911
+ e.preventDefault();
912
+ import('./settings').then(({ getSettings, updateSettings }) => {
913
+ const settings = getSettings();
914
+ let next = settings.previewFontPx;
915
+ if (e.key === '=' || e.key === '+') next = Math.min(16, settings.previewFontPx + 1);
916
+ else if (e.key === '-') next = Math.max(7, settings.previewFontPx - 1);
917
+ else next = 10;
918
+ if (next !== settings.previewFontPx) {
919
+ updateSettings({ previewFontPx: next });
920
+ window.dispatchEvent(new CustomEvent('gitcanvas:preview-settings-changed'));
921
+ showToast(`Preview text: ${next}px`, 'info');
922
+ }
923
+ });
924
+ return;
925
+ }
926
+
909
927
  // Ctrl+Shift+E = Export canvas as PNG
910
928
  if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key.toLowerCase() === 'e') {
911
929
  e.preventDefault();
@@ -35,7 +35,7 @@ let currentCardPath: string | null = null;
35
35
  let isInitialized = false;
36
36
  let _ctx: CanvasContext | null = null;
37
37
  let isPreviewEnabled =
38
- localStorage.getItem("gitmaps:previewEnabled") !== "false"; // enabled by default
38
+ localStorage.getItem("gitmaps:previewEnabled") === "true"; // disabled by default
39
39
  let _isHoveringPopup = false;
40
40
 
41
41
  // ─── Popup container ─────────────────────────────────────
@@ -52,6 +52,24 @@ export function saveLayers(ctx: CanvasContext) {
52
52
  localStorage.setItem(`gitcanvas:layers:${ctx.snap().context.repoPath}`, JSON.stringify(layerState.layers));
53
53
  }
54
54
 
55
+ function removeFileFromCurrentCanvas(ctx: CanvasContext, path: string) {
56
+ const card = ctx.fileCards.get(path);
57
+ if (card) {
58
+ card.remove();
59
+ ctx.fileCards.delete(path);
60
+ }
61
+ const pill = ctx.canvas?.querySelector(`.file-pill[data-path="${CSS.escape(path)}"]`) as HTMLElement | null;
62
+ if (pill) pill.remove();
63
+ if (ctx.deferredCards.has(path)) {
64
+ ctx.deferredCards.delete(path);
65
+ }
66
+ const selected = ctx.snap().context.selectedCards || [];
67
+ if (selected.includes(path)) {
68
+ ctx.actor.send({ type: 'SELECT_CARD', path, shift: true });
69
+ }
70
+ import('./canvas').then(({ forceMinimapRebuild }) => forceMinimapRebuild(ctx)).catch(() => {});
71
+ }
72
+
55
73
  export function createLayer(ctx: CanvasContext, name: string) {
56
74
  const newLayer: LayerData = {
57
75
  id: `layer_${Date.now()}`,
@@ -124,9 +142,8 @@ export function moveFileToLayer(ctx: CanvasContext, layerId: string, path: strin
124
142
 
125
143
  saveLayers(ctx);
126
144
  renderLayersUI(ctx);
127
- // Re-render current layer to hide the moved file
128
145
  if (layerState.activeLayerId === 'default') {
129
- applyLayer(ctx);
146
+ removeFileFromCurrentCanvas(ctx, path);
130
147
  }
131
148
  }
132
149
 
@@ -150,6 +167,10 @@ export function removeFileFromLayer(ctx: CanvasContext, layerId: string, path: s
150
167
  saveLayers(ctx);
151
168
  renderLayersUI(ctx);
152
169
  if (layer.id === layerState.activeLayerId) applyLayer(ctx);
170
+ else if (layerState.activeLayerId === 'default') {
171
+ // File becomes visible in Main again without a full rerender on the active custom layer path.
172
+ applyLayer(ctx);
173
+ }
153
174
  }
154
175
  }
155
176
 
@@ -33,11 +33,11 @@ describe('low zoom preview helpers', () => {
33
33
  expect(lines.some((line) => line.tone === 'added' && line.text === 'two')).toBe(true);
34
34
  });
35
35
 
36
- test('keeps zoomed-out on-screen text smaller than zoomed-in text', () => {
36
+ test('keeps preview text screen size stable across zoom levels', () => {
37
37
  const far = getLowZoomScale(0.1);
38
38
  const near = getLowZoomScale(1);
39
- expect(far.titleFont * 0.1).toBeLessThan(near.titleFont * 1);
40
- expect(far.bodyFont * 0.1).toBeLessThan(near.bodyFont * 1);
39
+ expect(Math.round(far.titleFont * 0.1)).toBe(Math.round(near.titleFont * 1));
40
+ expect(Math.round(far.bodyFont * 0.1)).toBe(Math.round(near.bodyFont * 1));
41
41
  });
42
42
 
43
43
  test('wraps preview text into bounded lines with ellipsis', () => {
@@ -6,13 +6,12 @@ const PREVIEWABLE_EXTS = new Set([
6
6
 
7
7
  export function getLowZoomScale(zoom: number) {
8
8
  const clampedZoom = Math.max(0.08, Math.min(1, zoom));
9
- const progress = (clampedZoom - 0.08) / (1 - 0.08);
10
9
  const settings = getSettings();
11
10
 
12
- const desiredScreenTitle = settings.previewFarTitlePx + progress * (settings.previewNearTitlePx - settings.previewFarTitlePx);
13
- const desiredScreenBody = 5.5 + progress * 6.5;
14
- const desiredScreenPadding = 6 + progress * 8;
15
- const desiredScreenGap = 4 + progress * 4;
11
+ const desiredScreenBody = settings.previewFontPx;
12
+ const desiredScreenTitle = settings.previewFontPx + 2;
13
+ const desiredScreenPadding = 10;
14
+ const desiredScreenGap = 6;
16
15
 
17
16
  return {
18
17
  titleFont: desiredScreenTitle / clampedZoom,
package/app/lib/repo.tsx CHANGED
@@ -991,80 +991,16 @@ export function renderAllFilesOnCanvas(ctx: CanvasContext, files: any[]) {
991
991
 
992
992
  renderConnections(ctx);
993
993
  buildConnectionMarkers(ctx);
994
- renderDirectoryLabels(ctx);
995
994
  forceMinimapRebuild(ctx);
996
995
  // Cull off-screen cards after browser layout (needs rAF for valid dimensions)
997
996
  requestAnimationFrame(() => performViewportCulling(ctx));
998
997
  });
999
998
  }
1000
999
 
1001
- // ─── Directory labels on canvas ──────────────────────────
1002
- // Groups visible file cards by parent directory and renders
1003
- // a world-space label above each directory cluster.
1000
+ // Directory labels disabled the auto-generated shared-prefix / directory containers
1001
+ // were adding noise and confusion without helping navigation.
1004
1002
  function renderDirectoryLabels(ctx: CanvasContext) {
1005
- // Remove existing labels
1006
- ctx.canvas?.querySelectorAll(".dir-label").forEach((el) => el.remove());
1007
-
1008
- // Group cards by parent directory
1009
- const groups = new Map<
1010
- string,
1011
- { minX: number; minY: number; maxX: number; count: number }
1012
- >();
1013
-
1014
- const processCard = (path: string, x: number, y: number, w: number) => {
1015
- const dir = path.includes("/")
1016
- ? path.substring(0, path.lastIndexOf("/"))
1017
- : ".";
1018
- const g = groups.get(dir);
1019
- if (g) {
1020
- g.minX = Math.min(g.minX, x);
1021
- g.minY = Math.min(g.minY, y);
1022
- g.maxX = Math.max(g.maxX, x + w);
1023
- g.count++;
1024
- } else {
1025
- groups.set(dir, { minX: x, minY: y, maxX: x + w, count: 1 });
1026
- }
1027
- };
1028
-
1029
- // Created cards (in DOM)
1030
- ctx.fileCards.forEach((card, path) => {
1031
- const x = parseFloat(card.style.left) || 0;
1032
- const y = parseFloat(card.style.top) || 0;
1033
- const w = card.offsetWidth || 580;
1034
- processCard(path, x, y, w);
1035
- });
1036
-
1037
- // Deferred cards (not yet in DOM)
1038
- ctx.deferredCards.forEach((info, path) => {
1039
- const w = info.size?.width || 580;
1040
- processCard(path, info.x, info.y, w);
1041
- });
1042
-
1043
- // Only show labels if we have multiple directories
1044
- if (groups.size <= 1) return;
1045
-
1046
- const frag = document.createDocumentFragment();
1047
- for (const [dir, g] of groups) {
1048
- const label = document.createElement("div");
1049
- label.className = "dir-label";
1050
- label.dataset.dir = dir;
1051
- const centerX = (g.minX + g.maxX) / 2;
1052
- label.style.left = `${centerX}px`;
1053
- label.style.top = `${g.minY - 36}px`;
1054
- label.style.transform = "translateX(-50%)";
1055
- label.innerHTML = `<span class="dir-label-icon">📁</span> ${dir}<span class="dir-label-count">${g.count}</span>`;
1056
-
1057
- // Click to collapse directory into a group card
1058
- label.addEventListener("click", (e) => {
1059
- e.stopPropagation();
1060
- import("./card-groups").then(({ toggleDirectoryCollapse }) => {
1061
- toggleDirectoryCollapse(ctx, dir);
1062
- });
1063
- });
1064
-
1065
- frag.appendChild(label);
1066
- }
1067
- ctx.canvas?.appendChild(frag);
1003
+ ctx.canvas?.querySelectorAll('.dir-label').forEach((el) => el.remove());
1068
1004
  }
1069
1005
 
1070
1006
  // ─── Highlight changed files without re-rendering ────────
@@ -98,14 +98,10 @@ function SettingsPanel({ settings }: { settings: GitCanvasSettings }) {
98
98
  <ToggleGroup id="settingRenderMode" value={settings.renderMode}
99
99
  options={[{ value: 'canvas', label: 'Canvas' }, { value: 'dom', label: 'DOM' }]} />
100
100
  </SettingsRow>
101
- <SettingsRow label="Font Size" desc="Code font size in pixels">
101
+ <SettingsRow label="Font Size" desc="Code font size in full cards and editor views">
102
102
  <Slider id="settingFontSize" valueId="fontSizeValue"
103
103
  min={10} max={18} step={1} value={settings.fontSize} suffix="px" />
104
104
  </SettingsRow>
105
- <SettingsRow label="Popup Font Size" desc="Font size for hover popup previews">
106
- <Slider id="settingPopupFontSize" valueId="popupFontSizeValue"
107
- min={10} max={24} step={1} value={settings.popupFontSize} suffix="px" />
108
- </SettingsRow>
109
105
  <SettingsRow label="Card Width" desc="Character columns per card (like editors)">
110
106
  <Slider id="settingCardWidth" valueId="cardWidthValue"
111
107
  min={40} max={120} step={5} value={cardCols} suffix=" cols" />
@@ -146,19 +142,15 @@ function SettingsPanel({ settings }: { settings: GitCanvasSettings }) {
146
142
 
147
143
  {/* Preview Mode Section */}
148
144
  <SettingsSection title="Preview Mode">
149
- <SettingsRow label="Far Zoom Title" desc="Filename size when zoomed far out">
150
- <Slider id="settingPreviewFarTitlePx" valueId="previewFarTitlePxValue"
151
- min={6} max={14} step={1} value={settings.previewFarTitlePx} suffix="px" />
152
- </SettingsRow>
153
- <SettingsRow label="Near Zoom Title" desc="Filename size when zoomed in within preview mode">
154
- <Slider id="settingPreviewNearTitlePx" valueId="previewNearTitlePxValue"
155
- min={10} max={24} step={1} value={settings.previewNearTitlePx} suffix="px" />
145
+ <SettingsRow label="Preview text size" desc="Fixed text size used by preview cards">
146
+ <Slider id="settingPreviewFontPx" valueId="previewFontPxValue"
147
+ min={7} max={16} step={1} value={settings.previewFontPx} suffix="px" />
156
148
  </SettingsRow>
157
- <SettingsRow label="Far Zoom Lines" desc="Minimum content lines shown when zoomed far out">
149
+ <SettingsRow label="Zoomed-out lines" desc="Minimum content lines shown at the farthest preview zoom">
158
150
  <Slider id="settingPreviewFarLines" valueId="previewFarLinesValue"
159
151
  min={1} max={8} step={1} value={settings.previewFarLines} suffix="" />
160
152
  </SettingsRow>
161
- <SettingsRow label="Near Zoom Lines" desc="Target content lines shown when zoomed in">
153
+ <SettingsRow label="Zoomed-in lines" desc="Target content lines shown at the closest preview zoom">
162
154
  <Slider id="settingPreviewNearLines" valueId="previewNearLinesValue"
163
155
  min={8} max={40} step={1} value={settings.previewNearLines} suffix="" />
164
156
  </SettingsRow>
@@ -257,13 +249,6 @@ export function openSettingsModal(ctx?: any) {
257
249
  applyFontSize(parseInt(fontSlider.value));
258
250
  });
259
251
 
260
- const popupFontSlider = _modal.querySelector('#settingPopupFontSize') as HTMLInputElement;
261
- const popupFontValue = _modal.querySelector('#popupFontSizeValue')!;
262
- popupFontSlider?.addEventListener('input', () => {
263
- popupFontValue.textContent = `${popupFontSlider.value}px`;
264
- updateSettings({ popupFontSize: parseInt(popupFontSlider.value) });
265
- });
266
-
267
252
  const cardWidthSlider = _modal.querySelector('#settingCardWidth') as HTMLInputElement;
268
253
  const cardWidthValue = _modal.querySelector('#cardWidthValue')!;
269
254
  cardWidthSlider?.addEventListener('input', () => {
@@ -281,19 +266,11 @@ export function openSettingsModal(ctx?: any) {
281
266
  updateSettings({ maxVisibleLines: parseInt(maxLinesSlider.value) });
282
267
  });
283
268
 
284
- const previewFarTitleSlider = _modal.querySelector('#settingPreviewFarTitlePx') as HTMLInputElement;
285
- const previewFarTitleValue = _modal.querySelector('#previewFarTitlePxValue')!;
286
- previewFarTitleSlider?.addEventListener('input', () => {
287
- previewFarTitleValue.textContent = `${previewFarTitleSlider.value}px`;
288
- updateSettings({ previewFarTitlePx: parseInt(previewFarTitleSlider.value) });
289
- window.dispatchEvent(new CustomEvent('gitcanvas:preview-settings-changed'));
290
- });
291
-
292
- const previewNearTitleSlider = _modal.querySelector('#settingPreviewNearTitlePx') as HTMLInputElement;
293
- const previewNearTitleValue = _modal.querySelector('#previewNearTitlePxValue')!;
294
- previewNearTitleSlider?.addEventListener('input', () => {
295
- previewNearTitleValue.textContent = `${previewNearTitleSlider.value}px`;
296
- updateSettings({ previewNearTitlePx: parseInt(previewNearTitleSlider.value) });
269
+ const previewFontSlider = _modal.querySelector('#settingPreviewFontPx') as HTMLInputElement;
270
+ const previewFontValue = _modal.querySelector('#previewFontPxValue')!;
271
+ previewFontSlider?.addEventListener('input', () => {
272
+ previewFontValue.textContent = `${previewFontSlider.value}px`;
273
+ updateSettings({ previewFontPx: parseInt(previewFontSlider.value) });
297
274
  window.dispatchEvent(new CustomEvent('gitcanvas:preview-settings-changed'));
298
275
  });
299
276
 
@@ -31,10 +31,8 @@ export interface GitCanvasSettings {
31
31
  heatmapEnabled: boolean;
32
32
  /** Heatmap time range in days */
33
33
  heatmapDays: number;
34
- /** Preview mode far-zoom title size (screen px) */
35
- previewFarTitlePx: number;
36
- /** Preview mode near-zoom title size (screen px) */
37
- previewNearTitlePx: number;
34
+ /** Preview mode fixed text size (screen px) */
35
+ previewFontPx: number;
38
36
  /** Preview mode far-zoom minimum visible lines */
39
37
  previewFarLines: number;
40
38
  /** Preview mode near-zoom target visible lines */
@@ -54,8 +52,7 @@ const DEFAULTS: GitCanvasSettings = {
54
52
  popupFontSize: 14,
55
53
  heatmapEnabled: false,
56
54
  heatmapDays: 90,
57
- previewFarTitlePx: 8,
58
- previewNearTitlePx: 16,
55
+ previewFontPx: 10,
59
56
  previewFarLines: 3,
60
57
  previewNearLines: 20,
61
58
  };
@@ -69,6 +66,11 @@ export function getSettings(): GitCanvasSettings {
69
66
  const raw = localStorage.getItem(STORAGE_KEY);
70
67
  if (raw) {
71
68
  const parsed = JSON.parse(raw);
69
+ if (parsed.previewFontPx == null) {
70
+ const far = typeof parsed.previewFarTitlePx === 'number' ? parsed.previewFarTitlePx : DEFAULTS.previewFontPx;
71
+ const near = typeof parsed.previewNearTitlePx === 'number' ? parsed.previewNearTitlePx : DEFAULTS.previewFontPx;
72
+ parsed.previewFontPx = Math.round((far + near) / 2);
73
+ }
72
74
  _settings = { ...DEFAULTS, ...parsed };
73
75
  } else {
74
76
  _settings = { ...DEFAULTS };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitmaps",
3
- "version": "1.1.22",
3
+ "version": "1.1.24",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "gitmaps": "cli.ts"