@ytspar/devbar 1.0.0-canary.cdf7fa2 → 1.0.0-canary.db7454c

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.
@@ -46,6 +46,8 @@ export declare class GlobalDevBar {
46
46
  private apiKeyStatus;
47
47
  private lastOutline;
48
48
  private lastSchema;
49
+ private savingOutline;
50
+ private savingSchema;
49
51
  private consoleFilter;
50
52
  private showOutlineModal;
51
53
  private showSchemaModal;
@@ -55,6 +57,11 @@ export declare class GlobalDevBar {
55
57
  private clsValue;
56
58
  private inpValue;
57
59
  private reconnectAttempts;
60
+ private readonly currentAppPort;
61
+ private readonly baseWsPort;
62
+ private wsVerified;
63
+ private serverProjectDir;
64
+ private verificationTimeout;
58
65
  private lastDotPosition;
59
66
  private reconnectTimeout;
60
67
  private screenshotTimeout;
@@ -8,7 +8,7 @@
8
8
  * to avoid React dependency conflicts in host applications.
9
9
  */
10
10
  import * as html2canvasModule from 'html2canvas-pro';
11
- import { BASE_RECONNECT_DELAY_MS, BUTTON_COLORS, CATEGORY_COLORS, CLIPBOARD_NOTIFICATION_MS, COLORS, DESIGN_REVIEW_NOTIFICATION_MS, DEVBAR_SCREENSHOT_QUALITY, FONT_MONO, getEffectiveTheme, getThemeColors, MAX_CONSOLE_LOGS, MAX_RECONNECT_ATTEMPTS, MAX_RECONNECT_DELAY_MS, SCREENSHOT_BLUR_DELAY_MS, SCREENSHOT_NOTIFICATION_MS, SCREENSHOT_SCALE, TAILWIND_BREAKPOINTS, TOOLTIP_STYLES, WS_PORT, } from './constants.js';
11
+ import { BASE_RECONNECT_DELAY_MS, BUTTON_COLORS, CATEGORY_COLORS, CLIPBOARD_NOTIFICATION_MS, COLORS, DESIGN_REVIEW_NOTIFICATION_MS, DEVBAR_SCREENSHOT_QUALITY, FONT_MONO, getEffectiveTheme, getTheme, getThemeColors, injectThemeCSS, MAX_CONSOLE_LOGS, MAX_PORT_RETRIES, MAX_RECONNECT_ATTEMPTS, MAX_RECONNECT_DELAY_MS, PORT_RETRY_DELAY_MS, PORT_SCAN_RESTART_DELAY_MS, SCREENSHOT_BLUR_DELAY_MS, SCREENSHOT_NOTIFICATION_MS, SCREENSHOT_SCALE, TAILWIND_BREAKPOINTS, TOOLTIP_STYLES, VERIFICATION_TIMEOUT_MS, WS_PORT, WS_PORT_OFFSET, } from './constants.js';
12
12
  import { DebugLogger, normalizeDebugConfig } from './debug.js';
13
13
  import { extractDocumentOutline, outlineToMarkdown } from './outline.js';
14
14
  import { extractPageSchema, schemaToMarkdown } from './schema.js';
@@ -91,6 +91,8 @@ export class GlobalDevBar {
91
91
  this.apiKeyStatus = null;
92
92
  this.lastOutline = null;
93
93
  this.lastSchema = null;
94
+ this.savingOutline = false;
95
+ this.savingSchema = false;
94
96
  this.consoleFilter = null;
95
97
  // Modal states
96
98
  this.showOutlineModal = false;
@@ -101,6 +103,9 @@ export class GlobalDevBar {
101
103
  this.clsValue = 0;
102
104
  this.inpValue = 0;
103
105
  this.reconnectAttempts = 0;
106
+ this.wsVerified = false;
107
+ this.serverProjectDir = null;
108
+ this.verificationTimeout = null;
104
109
  // Track the position of the connection indicator dot for smooth collapse
105
110
  this.lastDotPosition = null;
106
111
  this.reconnectTimeout = null;
@@ -132,6 +137,17 @@ export class GlobalDevBar {
132
137
  this.debug = new DebugLogger(this.debugConfig);
133
138
  // Initialize settings manager
134
139
  this.settingsManager = getSettingsManager();
140
+ // Calculate app port from URL for multi-instance support
141
+ if (typeof window !== 'undefined') {
142
+ this.currentAppPort =
143
+ parseInt(window.location.port, 10) || (window.location.protocol === 'https:' ? 443 : 80);
144
+ // Calculate expected WS port (appPort + port offset) like SweetlinkBridge does
145
+ this.baseWsPort = this.currentAppPort > 0 ? this.currentAppPort + WS_PORT_OFFSET : WS_PORT;
146
+ }
147
+ else {
148
+ this.currentAppPort = 0;
149
+ this.baseWsPort = WS_PORT;
150
+ }
135
151
  this.options = {
136
152
  position: options.position ?? 'bottom-left',
137
153
  accentColor: options.accentColor ?? COLORS.primary,
@@ -287,6 +303,8 @@ export class GlobalDevBar {
287
303
  this.reconnectAttempts = MAX_RECONNECT_ATTEMPTS; // Prevent reconnection
288
304
  if (this.reconnectTimeout)
289
305
  clearTimeout(this.reconnectTimeout);
306
+ if (this.verificationTimeout)
307
+ clearTimeout(this.verificationTimeout);
290
308
  if (this.ws)
291
309
  this.ws.close();
292
310
  // Clear timeouts
@@ -345,27 +363,82 @@ export class GlobalDevBar {
345
363
  document.head.appendChild(style);
346
364
  }
347
365
  }
348
- connectWebSocket() {
366
+ connectWebSocket(port) {
349
367
  if (this.destroyed)
350
368
  return;
351
- this.debug.ws('Connecting to WebSocket', { port: WS_PORT });
352
- const ws = new WebSocket(`ws://localhost:${WS_PORT}`);
369
+ const targetPort = port ?? this.baseWsPort;
370
+ this.debug.ws('Connecting to WebSocket', { port: targetPort, appPort: this.currentAppPort });
371
+ const ws = new WebSocket(`ws://localhost:${targetPort}`);
353
372
  this.ws = ws;
373
+ this.wsVerified = false;
374
+ // Timeout for server-info verification
375
+ this.verificationTimeout = setTimeout(() => {
376
+ if (!this.wsVerified && ws.readyState === WebSocket.OPEN) {
377
+ // Server didn't send server-info (old version) - accept for backwards compatibility
378
+ this.debug.ws('Server is old version (no server-info), accepting for backwards compat');
379
+ this.wsVerified = true;
380
+ this.sweetlinkConnected = true;
381
+ this.reconnectAttempts = 0;
382
+ this.settingsManager.setWebSocket(ws);
383
+ this.settingsManager.setConnected(true);
384
+ ws.send(JSON.stringify({ type: 'load-settings' }));
385
+ this.render();
386
+ }
387
+ }, VERIFICATION_TIMEOUT_MS);
354
388
  ws.onopen = () => {
355
- this.sweetlinkConnected = true;
356
- this.reconnectAttempts = 0;
357
- this.debug.ws('WebSocket connected');
358
- // Update settings manager with WebSocket connection
359
- this.settingsManager.setWebSocket(ws);
360
- this.settingsManager.setConnected(true);
389
+ this.debug.ws('WebSocket socket opened, awaiting server-info');
361
390
  ws.send(JSON.stringify({ type: 'browser-client-ready' }));
362
- // Request settings from server
363
- ws.send(JSON.stringify({ type: 'load-settings' }));
364
- this.render();
365
391
  };
366
392
  ws.onmessage = async (event) => {
367
393
  try {
368
- const command = JSON.parse(event.data);
394
+ const message = JSON.parse(event.data);
395
+ // Handle server-info for port matching
396
+ if (message.type === 'server-info') {
397
+ if (this.verificationTimeout) {
398
+ clearTimeout(this.verificationTimeout);
399
+ this.verificationTimeout = null;
400
+ }
401
+ const serverAppPort = message.appPort;
402
+ const serverMatchesApp = serverAppPort === null || serverAppPort === this.currentAppPort;
403
+ if (!serverMatchesApp) {
404
+ this.debug.ws('Server mismatch', {
405
+ serverAppPort,
406
+ currentAppPort: this.currentAppPort,
407
+ tryingNextPort: targetPort + 1,
408
+ });
409
+ ws.close();
410
+ // Try next port
411
+ const nextPort = targetPort + 1;
412
+ if (nextPort < this.baseWsPort + MAX_PORT_RETRIES) {
413
+ setTimeout(() => this.connectWebSocket(nextPort), PORT_RETRY_DELAY_MS);
414
+ }
415
+ else {
416
+ this.debug.ws('No matching server found, will retry from base port');
417
+ setTimeout(() => this.connectWebSocket(this.baseWsPort), PORT_SCAN_RESTART_DELAY_MS);
418
+ }
419
+ return;
420
+ }
421
+ // Server matches - mark as verified and connected
422
+ this.wsVerified = true;
423
+ this.sweetlinkConnected = true;
424
+ this.reconnectAttempts = 0;
425
+ this.serverProjectDir = message.projectDir ?? null;
426
+ this.debug.ws('Server verified', {
427
+ appPort: serverAppPort ?? 'any',
428
+ projectDir: this.serverProjectDir,
429
+ });
430
+ this.settingsManager.setWebSocket(ws);
431
+ this.settingsManager.setConnected(true);
432
+ ws.send(JSON.stringify({ type: 'load-settings' }));
433
+ this.render();
434
+ return;
435
+ }
436
+ // Ignore other commands until verified
437
+ if (!this.wsVerified) {
438
+ this.debug.ws('Ignoring command before verification', { type: message.type });
439
+ return;
440
+ }
441
+ const command = message;
369
442
  this.debug.ws('Received command', { type: command.type });
370
443
  await this.handleSweetlinkCommand(command);
371
444
  }
@@ -374,16 +447,25 @@ export class GlobalDevBar {
374
447
  }
375
448
  };
376
449
  ws.onclose = () => {
377
- this.sweetlinkConnected = false;
378
- this.settingsManager.setConnected(false);
379
- this.debug.ws('WebSocket disconnected');
380
- this.render();
381
- // Auto-reconnect with exponential backoff
382
- if (!this.destroyed && this.reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
383
- const delayMs = BASE_RECONNECT_DELAY_MS * 2 ** this.reconnectAttempts;
384
- this.reconnectAttempts++;
385
- this.debug.ws('Scheduling reconnect', { attempt: this.reconnectAttempts, delayMs });
386
- this.reconnectTimeout = setTimeout(() => this.connectWebSocket(), Math.min(delayMs, MAX_RECONNECT_DELAY_MS));
450
+ if (this.verificationTimeout) {
451
+ clearTimeout(this.verificationTimeout);
452
+ this.verificationTimeout = null;
453
+ }
454
+ // Only reset connection state if we were actually verified/connected
455
+ if (this.wsVerified) {
456
+ this.sweetlinkConnected = false;
457
+ this.wsVerified = false;
458
+ this.serverProjectDir = null;
459
+ this.settingsManager.setConnected(false);
460
+ this.debug.ws('WebSocket disconnected');
461
+ this.render();
462
+ // Auto-reconnect with exponential backoff (start from base port)
463
+ if (!this.destroyed && this.reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
464
+ const delayMs = BASE_RECONNECT_DELAY_MS * 2 ** this.reconnectAttempts;
465
+ this.reconnectAttempts++;
466
+ this.debug.ws('Scheduling reconnect', { attempt: this.reconnectAttempts, delayMs });
467
+ this.reconnectTimeout = setTimeout(() => this.connectWebSocket(this.baseWsPort), Math.min(delayMs, MAX_RECONNECT_DELAY_MS));
468
+ }
387
469
  }
388
470
  };
389
471
  ws.onerror = () => {
@@ -579,6 +661,7 @@ export class GlobalDevBar {
579
661
  }, durationMs);
580
662
  break;
581
663
  case 'outline':
664
+ this.savingOutline = false;
582
665
  this.lastOutline = path;
583
666
  if (this.outlineTimeout)
584
667
  clearTimeout(this.outlineTimeout);
@@ -588,6 +671,7 @@ export class GlobalDevBar {
588
671
  }, durationMs);
589
672
  break;
590
673
  case 'schema':
674
+ this.savingSchema = false;
591
675
  this.lastSchema = path;
592
676
  if (this.schemaTimeout)
593
677
  clearTimeout(this.schemaTimeout);
@@ -787,6 +871,8 @@ export class GlobalDevBar {
787
871
  this.themeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
788
872
  this.themeMediaHandler = () => {
789
873
  if (this.themeMode === 'system') {
874
+ // Re-inject theme CSS when system preference changes
875
+ injectThemeCSS(getTheme(this.themeMode));
790
876
  this.debug.state('System theme changed', {
791
877
  effectiveTheme: getEffectiveTheme(this.themeMode),
792
878
  });
@@ -813,6 +899,8 @@ export class GlobalDevBar {
813
899
  setThemeMode(mode) {
814
900
  this.themeMode = mode;
815
901
  this.settingsManager.saveSettings({ themeMode: mode });
902
+ // Inject the appropriate theme CSS variables
903
+ injectThemeCSS(getTheme(mode));
816
904
  this.debug.state('Theme mode changed', { mode, effectiveTheme: getEffectiveTheme(mode) });
817
905
  this.render();
818
906
  }
@@ -1066,9 +1154,13 @@ export class GlobalDevBar {
1066
1154
  this.render();
1067
1155
  }
1068
1156
  handleSaveOutline() {
1157
+ if (this.savingOutline)
1158
+ return; // Prevent repeated clicks
1069
1159
  const outline = extractDocumentOutline();
1070
1160
  const markdown = outlineToMarkdown(outline);
1071
1161
  if (this.ws?.readyState === WebSocket.OPEN) {
1162
+ this.savingOutline = true;
1163
+ this.render();
1072
1164
  this.ws.send(JSON.stringify({
1073
1165
  type: 'save-outline',
1074
1166
  data: {
@@ -1082,9 +1174,13 @@ export class GlobalDevBar {
1082
1174
  }
1083
1175
  }
1084
1176
  handleSaveSchema() {
1177
+ if (this.savingSchema)
1178
+ return; // Prevent repeated clicks
1085
1179
  const schema = extractPageSchema();
1086
1180
  const markdown = schemaToMarkdown(schema);
1087
1181
  if (this.ws?.readyState === WebSocket.OPEN) {
1182
+ this.savingSchema = true;
1183
+ this.render();
1088
1184
  this.ws.send(JSON.stringify({
1089
1185
  type: 'save-schema',
1090
1186
  data: {
@@ -1462,6 +1558,8 @@ export class GlobalDevBar {
1462
1558
  },
1463
1559
  onSave: () => this.handleSaveOutline(),
1464
1560
  sweetlinkConnected: this.sweetlinkConnected,
1561
+ isSaving: this.savingOutline,
1562
+ savedPath: this.lastOutline,
1465
1563
  });
1466
1564
  modal.appendChild(header);
1467
1565
  const content = createModalContent();
@@ -1548,6 +1646,8 @@ export class GlobalDevBar {
1548
1646
  },
1549
1647
  onSave: () => this.handleSaveSchema(),
1550
1648
  sweetlinkConnected: this.sweetlinkConnected,
1649
+ isSaving: this.savingSchema,
1650
+ savedPath: this.lastSchema,
1551
1651
  });
1552
1652
  modal.appendChild(header);
1553
1653
  const content = createModalContent();
@@ -1915,7 +2015,8 @@ export class GlobalDevBar {
1915
2015
  const isCompact = this.compactMode;
1916
2016
  const tooltip = isCompact ? 'Expand (Cmd+Shift+M)' : 'Compact (Cmd+Shift+M)';
1917
2017
  btn.setAttribute('data-tooltip', tooltip);
1918
- const color = COLORS.textSecondary;
2018
+ const { accentColor } = this.options;
2019
+ const iconColor = COLORS.textSecondary;
1919
2020
  Object.assign(btn.style, {
1920
2021
  display: 'flex',
1921
2022
  alignItems: 'center',
@@ -1926,21 +2027,21 @@ export class GlobalDevBar {
1926
2027
  minHeight: '22px',
1927
2028
  flexShrink: '0',
1928
2029
  borderRadius: '50%',
1929
- border: `1px solid ${color}60`,
2030
+ border: `1px solid ${accentColor}60`,
1930
2031
  backgroundColor: 'transparent',
1931
- color: `${color}99`,
2032
+ color: `${iconColor}99`,
1932
2033
  cursor: 'pointer',
1933
2034
  transition: 'all 150ms',
1934
2035
  });
1935
2036
  btn.onmouseenter = () => {
1936
- btn.style.borderColor = color;
1937
- btn.style.backgroundColor = `${color}20`;
1938
- btn.style.color = color;
2037
+ btn.style.borderColor = accentColor;
2038
+ btn.style.backgroundColor = `${accentColor}20`;
2039
+ btn.style.color = iconColor;
1939
2040
  };
1940
2041
  btn.onmouseleave = () => {
1941
- btn.style.borderColor = `${color}60`;
2042
+ btn.style.borderColor = `${accentColor}60`;
1942
2043
  btn.style.backgroundColor = 'transparent';
1943
- btn.style.color = `${color}99`;
2044
+ btn.style.color = `${iconColor}99`;
1944
2045
  };
1945
2046
  btn.onclick = () => {
1946
2047
  this.toggleCompactMode();
@@ -1956,8 +2057,8 @@ export class GlobalDevBar {
1956
2057
  svg.setAttribute('stroke-linecap', 'round');
1957
2058
  svg.setAttribute('stroke-linejoin', 'round');
1958
2059
  const path = document.createElementNS('http://www.w3.org/2000/svg', 'polyline');
1959
- // Right chevron (>) when not compact, left chevron (<) when compact
1960
- path.setAttribute('points', isCompact ? '15 18 9 12 15 6' : '9 18 15 12 9 6');
2060
+ // Left chevron (<) when expanded to shrink, right chevron (>) when compact to expand
2061
+ path.setAttribute('points', isCompact ? '9 18 15 12 9 6' : '15 18 9 12 15 6');
1961
2062
  svg.appendChild(path);
1962
2063
  btn.appendChild(svg);
1963
2064
  return btn;
@@ -11,8 +11,18 @@ export declare const MAX_RECONNECT_ATTEMPTS = 10;
11
11
  export declare const BASE_RECONNECT_DELAY_MS = 1000;
12
12
  /** Maximum delay between reconnection attempts (ms) */
13
13
  export declare const MAX_RECONNECT_DELAY_MS = 30000;
14
- /** Default WebSocket port for Sweetlink connection */
14
+ /** Default WebSocket port for Sweetlink connection (legacy fallback) */
15
15
  export declare const WS_PORT = 9223;
16
+ /** Port offset from app port to calculate WebSocket port (matches SweetlinkBridge) */
17
+ export declare const WS_PORT_OFFSET = 6223;
18
+ /** Maximum ports to try when scanning for matching server */
19
+ export declare const MAX_PORT_RETRIES = 10;
20
+ /** Delay between port scan attempts (ms) */
21
+ export declare const PORT_RETRY_DELAY_MS = 100;
22
+ /** Timeout for server-info verification (ms) */
23
+ export declare const VERIFICATION_TIMEOUT_MS = 1000;
24
+ /** Delay before restarting port scan from base after all ports fail (ms) */
25
+ export declare const PORT_SCAN_RESTART_DELAY_MS = 3000;
16
26
  /** Duration to show screenshot notification (ms) */
17
27
  export declare const SCREENSHOT_NOTIFICATION_MS = 3000;
18
28
  /** Duration to show clipboard notification (ms) */
package/dist/constants.js CHANGED
@@ -18,8 +18,18 @@ export const MAX_RECONNECT_DELAY_MS = 30000;
18
18
  // ============================================================================
19
19
  // WebSocket Settings
20
20
  // ============================================================================
21
- /** Default WebSocket port for Sweetlink connection */
21
+ /** Default WebSocket port for Sweetlink connection (legacy fallback) */
22
22
  export const WS_PORT = 9223;
23
+ /** Port offset from app port to calculate WebSocket port (matches SweetlinkBridge) */
24
+ export const WS_PORT_OFFSET = 6223;
25
+ /** Maximum ports to try when scanning for matching server */
26
+ export const MAX_PORT_RETRIES = 10;
27
+ /** Delay between port scan attempts (ms) */
28
+ export const PORT_RETRY_DELAY_MS = 100;
29
+ /** Timeout for server-info verification (ms) */
30
+ export const VERIFICATION_TIMEOUT_MS = 1000;
31
+ /** Delay before restarting port scan from base after all ports fail (ms) */
32
+ export const PORT_SCAN_RESTART_DELAY_MS = 3000;
23
33
  // ============================================================================
24
34
  // Notification Durations
25
35
  // ============================================================================
@@ -13,6 +13,10 @@ export interface ModalConfig {
13
13
  onCopyMd: () => Promise<void>;
14
14
  onSave?: () => void;
15
15
  sweetlinkConnected: boolean;
16
+ /** Whether a save operation is in progress */
17
+ isSaving?: boolean;
18
+ /** Path where data was saved (shows confirmation) */
19
+ savedPath?: string | null;
16
20
  }
17
21
  /**
18
22
  * Create modal overlay with click-outside-to-close behavior
package/dist/ui/modals.js CHANGED
@@ -34,7 +34,7 @@ export function createModalBox(color) {
34
34
  * Create modal header with title, copy/save/close buttons
35
35
  */
36
36
  export function createModalHeader(config) {
37
- const { color, title, onClose, onCopyMd, onSave, sweetlinkConnected } = config;
37
+ const { color, title, onClose, onCopyMd, onSave, sweetlinkConnected, isSaving, savedPath } = config;
38
38
  const header = document.createElement('div');
39
39
  Object.assign(header.style, {
40
40
  display: 'flex',
@@ -42,6 +42,8 @@ export function createModalHeader(config) {
42
42
  justifyContent: 'space-between',
43
43
  padding: '16px 20px',
44
44
  borderBottom: `1px solid ${color}40`,
45
+ flexWrap: 'wrap',
46
+ gap: '8px',
45
47
  });
46
48
  const titleEl = document.createElement('h2');
47
49
  Object.assign(titleEl.style, {
@@ -53,7 +55,7 @@ export function createModalHeader(config) {
53
55
  titleEl.textContent = title;
54
56
  header.appendChild(titleEl);
55
57
  const headerButtons = document.createElement('div');
56
- Object.assign(headerButtons.style, { display: 'flex', gap: '10px' });
58
+ Object.assign(headerButtons.style, { display: 'flex', gap: '10px', alignItems: 'center' });
57
59
  // Copy MD button
58
60
  const copyBtn = createStyledButton({ color, text: 'Copy MD' });
59
61
  copyBtn.onclick = async () => {
@@ -71,8 +73,17 @@ export function createModalHeader(config) {
71
73
  headerButtons.appendChild(copyBtn);
72
74
  // Save button (if Sweetlink connected)
73
75
  if (sweetlinkConnected && onSave) {
74
- const saveBtn = createStyledButton({ color, text: 'Save' });
75
- saveBtn.onclick = onSave;
76
+ const saveBtn = createStyledButton({
77
+ color,
78
+ text: isSaving ? 'Saving...' : 'Save',
79
+ });
80
+ if (isSaving) {
81
+ saveBtn.style.opacity = '0.6';
82
+ saveBtn.style.cursor = 'not-allowed';
83
+ }
84
+ else {
85
+ saveBtn.onclick = onSave;
86
+ }
76
87
  headerButtons.appendChild(saveBtn);
77
88
  }
78
89
  // Close button - use same padding as other buttons for consistent height
@@ -85,6 +96,39 @@ export function createModalHeader(config) {
85
96
  closeBtn.onclick = onClose;
86
97
  headerButtons.appendChild(closeBtn);
87
98
  header.appendChild(headerButtons);
99
+ // Show saved path confirmation below buttons
100
+ if (savedPath) {
101
+ const savedConfirm = document.createElement('div');
102
+ Object.assign(savedConfirm.style, {
103
+ width: '100%',
104
+ marginTop: '4px',
105
+ padding: '8px 12px',
106
+ backgroundColor: `${color}15`,
107
+ border: `1px solid ${color}30`,
108
+ borderRadius: '6px',
109
+ fontSize: '0.75rem',
110
+ color: color,
111
+ display: 'flex',
112
+ alignItems: 'center',
113
+ gap: '6px',
114
+ });
115
+ // Checkmark icon
116
+ const checkmark = document.createElement('span');
117
+ checkmark.textContent = '✓';
118
+ Object.assign(checkmark.style, { fontWeight: '600' });
119
+ savedConfirm.appendChild(checkmark);
120
+ // Path text
121
+ const pathText = document.createElement('span');
122
+ Object.assign(pathText.style, {
123
+ color: '#9ca3af',
124
+ fontFamily: 'monospace',
125
+ fontSize: '0.6875rem',
126
+ wordBreak: 'break-all',
127
+ });
128
+ pathText.textContent = `Saved to ${savedPath}`;
129
+ savedConfirm.appendChild(pathText);
130
+ header.appendChild(savedConfirm);
131
+ }
88
132
  return header;
89
133
  }
90
134
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ytspar/devbar",
3
- "version": "1.0.0-canary.cdf7fa2",
3
+ "version": "1.0.0-canary.db7454c",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "description": "Development toolbar and utilities with Sweetlink integration - pure vanilla JS, no framework dependencies",