@ytspar/devbar 1.0.0-canary.3007fc6 → 1.0.0-canary.92db425

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.
@@ -7,8 +7,8 @@
7
7
  * This is a vanilla JS replacement for the React-based GlobalDevBar component
8
8
  * to avoid React dependency conflicts in host applications.
9
9
  */
10
- import type { ConsoleLog, DevBarControl, GlobalDevBarOptions, OutlineNode, PageSchema, SweetlinkCommand } from './types.js';
11
- export type { ConsoleLog, SweetlinkCommand, OutlineNode, PageSchema, GlobalDevBarOptions, DevBarControl, };
10
+ import type { ConsoleLog, SweetlinkCommand, OutlineNode, PageSchema, GlobalDevBarOptions, DevBarControl } from './types.js';
11
+ export type { ConsoleLog, SweetlinkCommand, OutlineNode, PageSchema, GlobalDevBarOptions, DevBarControl };
12
12
  interface EarlyConsoleCapture {
13
13
  errorCount: number;
14
14
  warningCount: number;
@@ -48,7 +48,6 @@ export declare class GlobalDevBar {
48
48
  private perfStats;
49
49
  private lcpValue;
50
50
  private reconnectAttempts;
51
- private lastDotPosition;
52
51
  private reconnectTimeout;
53
52
  private screenshotTimeout;
54
53
  private copiedPathTimeout;
@@ -95,10 +94,6 @@ export declare class GlobalDevBar {
95
94
  * Initialize and mount the devbar
96
95
  */
97
96
  init(): void;
98
- /**
99
- * Get the current position
100
- */
101
- getPosition(): string;
102
97
  /**
103
98
  * Destroy the devbar and cleanup
104
99
  */
@@ -184,14 +179,6 @@ export declare class GlobalDevBar {
184
179
  */
185
180
  private createConsoleBadge;
186
181
  private createScreenshotButton;
187
- /**
188
- * Get the tooltip text for the screenshot button based on current state
189
- */
190
- private getScreenshotTooltip;
191
- /**
192
- * Get the tooltip text for the AI review button based on current state
193
- */
194
- private getAIReviewTooltip;
195
182
  private createAIReviewButton;
196
183
  private createOutlineButton;
197
184
  private createSchemaButton;
@@ -8,20 +8,19 @@
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, 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 { MAX_CONSOLE_LOGS, DEVBAR_SCREENSHOT_QUALITY, MAX_RECONNECT_ATTEMPTS, BASE_RECONNECT_DELAY_MS, MAX_RECONNECT_DELAY_MS, WS_PORT, SCREENSHOT_NOTIFICATION_MS, CLIPBOARD_NOTIFICATION_MS, DESIGN_REVIEW_NOTIFICATION_MS, SCREENSHOT_BLUR_DELAY_MS, SCREENSHOT_SCALE, TAILWIND_BREAKPOINTS, BUTTON_COLORS, CATEGORY_COLORS, TOOLTIP_STYLES, COLORS, FONT_MONO, } from './constants.js';
12
+ import { formatArgs, canvasToDataUrl, prepareForCapture, delay, copyCanvasToClipboard, } from './utils.js';
12
13
  import { extractDocumentOutline, outlineToMarkdown } from './outline.js';
13
14
  import { extractPageSchema, schemaToMarkdown } from './schema.js';
14
- import { createEmptyMessage, createInfoBox, createModalBox, createModalContent, createModalHeader, createModalOverlay, createStyledButton, createSvgIcon, getButtonStyles, } from './ui/index.js';
15
- import { canvasToDataUrl, copyCanvasToClipboard, delay, formatArgs, prepareForCapture, } from './utils.js';
16
- const html2canvas = (html2canvasModule.default ??
17
- html2canvasModule);
15
+ import { createSvgIcon, getButtonStyles, createStyledButton, createModalOverlay, createModalBox, createModalHeader, createModalContent, createEmptyMessage, createInfoBox, } from './ui/index.js';
16
+ const html2canvas = (html2canvasModule.default ?? html2canvasModule);
18
17
  const earlyConsoleCapture = (() => {
19
18
  const ssrFallback = {
20
19
  errorCount: 0,
21
20
  warningCount: 0,
22
21
  logs: [],
23
22
  originalConsole: null,
24
- isPatched: false,
23
+ isPatched: false
25
24
  };
26
25
  // Skip on server-side rendering
27
26
  if (typeof window === 'undefined')
@@ -34,9 +33,9 @@ const earlyConsoleCapture = (() => {
34
33
  log: console.log,
35
34
  error: console.error,
36
35
  warn: console.warn,
37
- info: console.info,
36
+ info: console.info
38
37
  },
39
- isPatched: false,
38
+ isPatched: false
40
39
  };
41
40
  const captureLog = (level, args) => {
42
41
  capture.logs.push({ level, message: formatArgs(args), timestamp: Date.now() });
@@ -45,24 +44,10 @@ const earlyConsoleCapture = (() => {
45
44
  };
46
45
  // Patch console immediately
47
46
  if (!capture.isPatched && capture.originalConsole) {
48
- console.log = (...args) => {
49
- captureLog('log', args);
50
- capture.originalConsole.log(...args);
51
- };
52
- console.error = (...args) => {
53
- captureLog('error', args);
54
- capture.errorCount++;
55
- capture.originalConsole.error(...args);
56
- };
57
- console.warn = (...args) => {
58
- captureLog('warn', args);
59
- capture.warningCount++;
60
- capture.originalConsole.warn(...args);
61
- };
62
- console.info = (...args) => {
63
- captureLog('info', args);
64
- capture.originalConsole.info(...args);
65
- };
47
+ console.log = (...args) => { captureLog('log', args); capture.originalConsole.log(...args); };
48
+ console.error = (...args) => { captureLog('error', args); capture.errorCount++; capture.originalConsole.error(...args); };
49
+ console.warn = (...args) => { captureLog('warn', args); capture.warningCount++; capture.originalConsole.warn(...args); };
50
+ console.info = (...args) => { captureLog('info', args); capture.originalConsole.info(...args); };
66
51
  capture.isPatched = true;
67
52
  }
68
53
  return capture;
@@ -96,8 +81,6 @@ export class GlobalDevBar {
96
81
  this.perfStats = null;
97
82
  this.lcpValue = null;
98
83
  this.reconnectAttempts = 0;
99
- // Track the position of the connection indicator dot for smooth collapse
100
- this.lastDotPosition = null;
101
84
  this.reconnectTimeout = null;
102
85
  this.screenshotTimeout = null;
103
86
  this.copiedPathTimeout = null;
@@ -170,7 +153,7 @@ export class GlobalDevBar {
170
153
  fontWeight: '600',
171
154
  display: 'flex',
172
155
  alignItems: 'center',
173
- justifyContent: 'center',
156
+ justifyContent: 'center'
174
157
  });
175
158
  badge.textContent = count > 99 ? '!' : String(count);
176
159
  return badge;
@@ -183,7 +166,7 @@ export class GlobalDevBar {
183
166
  */
184
167
  static registerControl(control) {
185
168
  // Remove existing control with same ID
186
- GlobalDevBar.customControls = GlobalDevBar.customControls.filter((c) => c.id !== control.id);
169
+ GlobalDevBar.customControls = GlobalDevBar.customControls.filter(c => c.id !== control.id);
187
170
  GlobalDevBar.customControls.push(control);
188
171
  // Trigger re-render of all instances
189
172
  const instance = getGlobalInstance();
@@ -195,7 +178,7 @@ export class GlobalDevBar {
195
178
  * Unregister a custom control by ID
196
179
  */
197
180
  static unregisterControl(id) {
198
- GlobalDevBar.customControls = GlobalDevBar.customControls.filter((c) => c.id !== id);
181
+ GlobalDevBar.customControls = GlobalDevBar.customControls.filter(c => c.id !== id);
199
182
  // Trigger re-render of all instances
200
183
  const instance = getGlobalInstance();
201
184
  if (instance) {
@@ -241,12 +224,6 @@ export class GlobalDevBar {
241
224
  // Initial render
242
225
  this.render();
243
226
  }
244
- /**
245
- * Get the current position
246
- */
247
- getPosition() {
248
- return this.options.position;
249
- }
250
227
  /**
251
228
  * Destroy the devbar and cleanup
252
229
  */
@@ -330,7 +307,7 @@ export class GlobalDevBar {
330
307
  this.render();
331
308
  // Auto-reconnect with exponential backoff
332
309
  if (!this.destroyed && this.reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
333
- const delayMs = BASE_RECONNECT_DELAY_MS * 2 ** this.reconnectAttempts;
310
+ const delayMs = BASE_RECONNECT_DELAY_MS * Math.pow(2, this.reconnectAttempts);
334
311
  this.reconnectAttempts++;
335
312
  this.reconnectTimeout = setTimeout(() => this.connectWebSocket(), Math.min(delayMs, MAX_RECONNECT_DELAY_MS));
336
313
  }
@@ -348,20 +325,11 @@ export class GlobalDevBar {
348
325
  const targetElement = command.selector
349
326
  ? document.querySelector(command.selector) || document.body
350
327
  : document.body;
351
- const canvas = await html2canvas(targetElement, {
352
- logging: false,
353
- useCORS: true,
354
- allowTaint: true,
355
- });
328
+ const canvas = await html2canvas(targetElement, { logging: false, useCORS: true, allowTaint: true });
356
329
  ws.send(JSON.stringify({
357
330
  success: true,
358
- data: {
359
- screenshot: canvas.toDataURL('image/png'),
360
- width: canvas.width,
361
- height: canvas.height,
362
- selector: command.selector || 'body',
363
- },
364
- timestamp: Date.now(),
331
+ data: { screenshot: canvas.toDataURL('image/png'), width: canvas.width, height: canvas.height, selector: command.selector || 'body' },
332
+ timestamp: Date.now()
365
333
  }));
366
334
  break;
367
335
  }
@@ -369,7 +337,7 @@ export class GlobalDevBar {
369
337
  let logs = this.consoleLogs;
370
338
  if (command.filter) {
371
339
  const filter = command.filter.toLowerCase();
372
- logs = logs.filter((log) => log.level.includes(filter) || log.message.toLowerCase().includes(filter));
340
+ logs = logs.filter(log => log.level.includes(filter) || log.message.toLowerCase().includes(filter));
373
341
  }
374
342
  ws.send(JSON.stringify({ success: true, data: logs, timestamp: Date.now() }));
375
343
  break;
@@ -380,18 +348,9 @@ export class GlobalDevBar {
380
348
  const results = elements.map((el) => {
381
349
  if (command.property)
382
350
  return el[command.property] ?? null;
383
- return {
384
- tagName: el.tagName,
385
- className: el.className,
386
- id: el.id,
387
- textContent: el.textContent?.trim().slice(0, 100),
388
- };
351
+ return { tagName: el.tagName, className: el.className, id: el.id, textContent: el.textContent?.trim().slice(0, 100) };
389
352
  });
390
- ws.send(JSON.stringify({
391
- success: true,
392
- data: { count: results.length, results },
393
- timestamp: Date.now(),
394
- }));
353
+ ws.send(JSON.stringify({ success: true, data: { count: results.length, results }, timestamp: Date.now() }));
395
354
  }
396
355
  break;
397
356
  }
@@ -404,11 +363,7 @@ export class GlobalDevBar {
404
363
  ws.send(JSON.stringify({ success: true, data: result, timestamp: Date.now() }));
405
364
  }
406
365
  catch (e) {
407
- ws.send(JSON.stringify({
408
- success: false,
409
- error: e instanceof Error ? e.message : 'Execution failed',
410
- timestamp: Date.now(),
411
- }));
366
+ ws.send(JSON.stringify({ success: false, error: e instanceof Error ? e.message : 'Execution failed', timestamp: Date.now() }));
412
367
  }
413
368
  }
414
369
  break;
@@ -472,37 +427,25 @@ export class GlobalDevBar {
472
427
  this.lastScreenshot = path;
473
428
  if (this.screenshotTimeout)
474
429
  clearTimeout(this.screenshotTimeout);
475
- this.screenshotTimeout = setTimeout(() => {
476
- this.lastScreenshot = null;
477
- this.render();
478
- }, durationMs);
430
+ this.screenshotTimeout = setTimeout(() => { this.lastScreenshot = null; this.render(); }, durationMs);
479
431
  break;
480
432
  case 'designReview':
481
433
  this.lastDesignReview = path;
482
434
  if (this.designReviewTimeout)
483
435
  clearTimeout(this.designReviewTimeout);
484
- this.designReviewTimeout = setTimeout(() => {
485
- this.lastDesignReview = null;
486
- this.render();
487
- }, durationMs);
436
+ this.designReviewTimeout = setTimeout(() => { this.lastDesignReview = null; this.render(); }, durationMs);
488
437
  break;
489
438
  case 'outline':
490
439
  this.lastOutline = path;
491
440
  if (this.outlineTimeout)
492
441
  clearTimeout(this.outlineTimeout);
493
- this.outlineTimeout = setTimeout(() => {
494
- this.lastOutline = null;
495
- this.render();
496
- }, durationMs);
442
+ this.outlineTimeout = setTimeout(() => { this.lastOutline = null; this.render(); }, durationMs);
497
443
  break;
498
444
  case 'schema':
499
445
  this.lastSchema = path;
500
446
  if (this.schemaTimeout)
501
447
  clearTimeout(this.schemaTimeout);
502
- this.schemaTimeout = setTimeout(() => {
503
- this.lastSchema = null;
504
- this.render();
505
- }, durationMs);
448
+ this.schemaTimeout = setTimeout(() => { this.lastSchema = null; this.render(); }, durationMs);
506
449
  break;
507
450
  }
508
451
  this.render();
@@ -512,17 +455,20 @@ export class GlobalDevBar {
512
455
  const width = window.innerWidth;
513
456
  const height = window.innerHeight;
514
457
  // Determine breakpoint by checking thresholds in descending order
515
- const breakpointOrder = [
516
- '2xl',
517
- 'xl',
518
- 'lg',
519
- 'md',
520
- 'sm',
521
- ];
522
- const tailwindBreakpoint = breakpointOrder.find((bp) => width >= TAILWIND_BREAKPOINTS[bp].min) ?? 'base';
458
+ let tailwindBreakpoint = 'base';
459
+ if (width >= TAILWIND_BREAKPOINTS['2xl'].min)
460
+ tailwindBreakpoint = '2xl';
461
+ else if (width >= TAILWIND_BREAKPOINTS.xl.min)
462
+ tailwindBreakpoint = 'xl';
463
+ else if (width >= TAILWIND_BREAKPOINTS.lg.min)
464
+ tailwindBreakpoint = 'lg';
465
+ else if (width >= TAILWIND_BREAKPOINTS.md.min)
466
+ tailwindBreakpoint = 'md';
467
+ else if (width >= TAILWIND_BREAKPOINTS.sm.min)
468
+ tailwindBreakpoint = 'sm';
523
469
  this.breakpointInfo = {
524
470
  tailwindBreakpoint,
525
- dimensions: `${width}x${height}`,
471
+ dimensions: `${width}x${height}`
526
472
  };
527
473
  this.render();
528
474
  };
@@ -534,7 +480,7 @@ export class GlobalDevBar {
534
480
  const updatePerfStats = () => {
535
481
  // FCP
536
482
  const paintEntries = performance.getEntriesByType('paint');
537
- const fcpEntry = paintEntries.find((entry) => entry.name === 'first-contentful-paint');
483
+ const fcpEntry = paintEntries.find(entry => entry.name === 'first-contentful-paint');
538
484
  const fcp = fcpEntry ? `${Math.round(fcpEntry.startTime)}ms` : '-';
539
485
  // LCP (from cached value, updated by observer)
540
486
  const lcp = this.lcpValue !== null ? `${Math.round(this.lcpValue)}ms` : '-';
@@ -596,10 +542,7 @@ export class GlobalDevBar {
596
542
  this.keydownHandler = (e) => {
597
543
  // Close modals on Escape
598
544
  if (e.key === 'Escape') {
599
- if (this.consoleFilter ||
600
- this.showOutlineModal ||
601
- this.showSchemaModal ||
602
- this.showDesignReviewConfirm) {
545
+ if (this.consoleFilter || this.showOutlineModal || this.showSchemaModal || this.showDesignReviewConfirm) {
603
546
  this.consoleFilter = null;
604
547
  this.showOutlineModal = false;
605
548
  this.showSchemaModal = false;
@@ -661,7 +604,7 @@ export class GlobalDevBar {
661
604
  allowTaint: true,
662
605
  scale: SCREENSHOT_SCALE,
663
606
  width: window.innerWidth,
664
- windowWidth: window.innerWidth,
607
+ windowWidth: window.innerWidth
665
608
  });
666
609
  // Restore page state
667
610
  cleanup();
@@ -683,10 +626,7 @@ export class GlobalDevBar {
683
626
  }
684
627
  }
685
628
  else {
686
- const dataUrl = canvasToDataUrl(canvas, {
687
- format: 'jpeg',
688
- quality: DEVBAR_SCREENSHOT_QUALITY,
689
- });
629
+ const dataUrl = canvasToDataUrl(canvas, { format: 'jpeg', quality: DEVBAR_SCREENSHOT_QUALITY });
690
630
  if (this.ws?.readyState === WebSocket.OPEN) {
691
631
  this.ws.send(JSON.stringify({
692
632
  type: 'save-screenshot',
@@ -696,8 +636,8 @@ export class GlobalDevBar {
696
636
  height: canvas.height,
697
637
  logs: this.consoleLogs,
698
638
  url: window.location.href,
699
- timestamp: Date.now(),
700
- },
639
+ timestamp: Date.now()
640
+ }
701
641
  }));
702
642
  }
703
643
  }
@@ -732,7 +672,7 @@ export class GlobalDevBar {
732
672
  allowTaint: true,
733
673
  scale: 1, // Full quality for design review
734
674
  width: window.innerWidth,
735
- windowWidth: window.innerWidth,
675
+ windowWidth: window.innerWidth
736
676
  });
737
677
  // Restore page state
738
678
  cleanup();
@@ -747,8 +687,8 @@ export class GlobalDevBar {
747
687
  height: canvas.height,
748
688
  logs: this.consoleLogs,
749
689
  url: window.location.href,
750
- timestamp: Date.now(),
751
- },
690
+ timestamp: Date.now()
691
+ }
752
692
  }));
753
693
  }
754
694
  }
@@ -843,8 +783,8 @@ export class GlobalDevBar {
843
783
  markdown,
844
784
  url: window.location.href,
845
785
  title: document.title,
846
- timestamp: Date.now(),
847
- },
786
+ timestamp: Date.now()
787
+ }
848
788
  }));
849
789
  }
850
790
  }
@@ -859,8 +799,8 @@ export class GlobalDevBar {
859
799
  markdown,
860
800
  url: window.location.href,
861
801
  title: document.title,
862
- timestamp: Date.now(),
863
- },
802
+ timestamp: Date.now()
803
+ }
864
804
  }));
865
805
  }
866
806
  }
@@ -943,12 +883,7 @@ export class GlobalDevBar {
943
883
  Object.assign(title.style, { color, fontSize: '0.875rem', fontWeight: '600' });
944
884
  title.textContent = 'AI Design Review';
945
885
  header.appendChild(title);
946
- const closeBtn = createStyledButton({
947
- color: COLORS.textMuted,
948
- text: '×',
949
- padding: '0',
950
- fontSize: '1.25rem',
951
- });
886
+ const closeBtn = createStyledButton({ color: COLORS.textMuted, text: '×', padding: '0', fontSize: '1.25rem' });
952
887
  closeBtn.style.border = 'none';
953
888
  closeBtn.onclick = closeModal;
954
889
  header.appendChild(closeBtn);
@@ -980,11 +915,7 @@ export class GlobalDevBar {
980
915
  padding: '14px 18px',
981
916
  borderTop: `1px solid ${COLORS.border}`,
982
917
  });
983
- const cancelBtn = createStyledButton({
984
- color: COLORS.textMuted,
985
- text: 'Cancel',
986
- padding: '8px 16px',
987
- });
918
+ const cancelBtn = createStyledButton({ color: COLORS.textMuted, text: 'Cancel', padding: '8px 16px' });
988
919
  cancelBtn.onclick = closeModal;
989
920
  footer.appendChild(cancelBtn);
990
921
  if (this.apiKeyStatus?.configured) {
@@ -1007,11 +938,7 @@ export class GlobalDevBar {
1007
938
  const instructions = document.createElement('div');
1008
939
  Object.assign(instructions.style, { marginBottom: '12px' });
1009
940
  const instructTitle = document.createElement('div');
1010
- Object.assign(instructTitle.style, {
1011
- color: COLORS.textSecondary,
1012
- fontWeight: '600',
1013
- marginBottom: '8px',
1014
- });
941
+ Object.assign(instructTitle.style, { color: COLORS.textSecondary, fontWeight: '600', marginBottom: '8px' });
1015
942
  instructTitle.textContent = 'To configure:';
1016
943
  instructions.appendChild(instructTitle);
1017
944
  const steps = [
@@ -1071,11 +998,7 @@ export class GlobalDevBar {
1071
998
  // Model info
1072
999
  if (this.apiKeyStatus?.model) {
1073
1000
  const modelDiv = document.createElement('div');
1074
- Object.assign(modelDiv.style, {
1075
- color: COLORS.textMuted,
1076
- fontSize: '0.6875rem',
1077
- marginTop: '12px',
1078
- });
1001
+ Object.assign(modelDiv.style, { color: COLORS.textMuted, fontSize: '0.6875rem', marginTop: '12px' });
1079
1002
  modelDiv.textContent = `Model: ${this.apiKeyStatus.model}`;
1080
1003
  if (this.apiKeyStatus.maskedKey) {
1081
1004
  modelDiv.textContent += ` | Key: ${this.apiKeyStatus.maskedKey}`;
@@ -1088,7 +1011,7 @@ export class GlobalDevBar {
1088
1011
  const filterType = this.consoleFilter;
1089
1012
  if (!filterType)
1090
1013
  return;
1091
- const logs = earlyConsoleCapture.logs.filter((log) => log.level === filterType);
1014
+ const logs = earlyConsoleCapture.logs.filter(log => log.level === filterType);
1092
1015
  const color = filterType === 'error' ? BUTTON_COLORS.error : BUTTON_COLORS.warning;
1093
1016
  const label = filterType === 'error' ? 'Errors' : 'Warnings';
1094
1017
  const popup = document.createElement('div');
@@ -1197,8 +1120,7 @@ export class GlobalDevBar {
1197
1120
  wordBreak: 'break-word',
1198
1121
  whiteSpace: 'pre-wrap',
1199
1122
  });
1200
- message.textContent =
1201
- log.message.length > 500 ? `${log.message.slice(0, 500)}...` : log.message;
1123
+ message.textContent = log.message.length > 500 ? log.message.slice(0, 500) + '...' : log.message;
1202
1124
  logItem.appendChild(message);
1203
1125
  container.appendChild(logItem);
1204
1126
  });
@@ -1270,7 +1192,7 @@ export class GlobalDevBar {
1270
1192
  fontSize: '0.6875rem',
1271
1193
  marginLeft: '8px',
1272
1194
  });
1273
- const truncatedText = node.text.length > 60 ? `${node.text.slice(0, 60)}...` : node.text;
1195
+ const truncatedText = node.text.length > 60 ? node.text.slice(0, 60) + '...' : node.text;
1274
1196
  textSpan.textContent = truncatedText;
1275
1197
  nodeEl.appendChild(textSpan);
1276
1198
  if (node.id) {
@@ -1403,7 +1325,7 @@ export class GlobalDevBar {
1403
1325
  punct: COLORS.textMuted, // gray
1404
1326
  };
1405
1327
  // Simple tokenizer for JSON using matchAll for safety
1406
- const tokenPattern = /("(?:\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*")(\s*:)?|(\btrue\b|\bfalse\b)|(\bnull\b)|(-?\d+(?:\.\d*)?(?:[eE][+-]?\d+)?)|([{}[\],])|(\s+)/g;
1328
+ const tokenPattern = /("(?:\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*")(\s*:)?|(\btrue\b|\bfalse\b)|(\bnull\b)|(-?\d+(?:\.\d*)?(?:[eE][+-]?\d+)?)|([{}\[\],])|(\s+)/g;
1407
1329
  for (const match of json.matchAll(tokenPattern)) {
1408
1330
  const [, str, colon, bool, nullToken, num, punct, whitespace] = match;
1409
1331
  if (whitespace) {
@@ -1495,27 +1417,18 @@ export class GlobalDevBar {
1495
1417
  return;
1496
1418
  const { position, accentColor } = this.options;
1497
1419
  const { errorCount, warningCount } = this.getLogCounts();
1498
- // Use captured dot position if available, otherwise fall back to preset positions
1499
- // The 13px offset accounts for half the collapsed circle diameter (26px / 2)
1500
- let posStyle;
1501
- if (this.lastDotPosition) {
1502
- // Position based on where the dot actually was
1503
- const isTop = position.startsWith('top');
1504
- posStyle = isTop
1505
- ? { top: `${this.lastDotPosition.top - 13}px`, left: `${this.lastDotPosition.left - 13}px` }
1506
- : { bottom: `${this.lastDotPosition.bottom - 13}px`, left: `${this.lastDotPosition.left - 13}px` };
1507
- }
1508
- else {
1509
- // Fallback preset positions for when no dot position was captured
1510
- const collapsedPositions = {
1511
- 'bottom-left': { bottom: '27px', left: '86px' },
1512
- 'bottom-right': { bottom: '27px', right: '29px' },
1513
- 'top-left': { top: '27px', left: '86px' },
1514
- 'top-right': { top: '27px', right: '29px' },
1515
- 'bottom-center': { bottom: '19px', left: '50%', transform: 'translateX(-50%)' },
1516
- };
1517
- posStyle = collapsedPositions[position] ?? collapsedPositions['bottom-left'];
1518
- }
1420
+ // Calculate position so the collapsed dot aligns with where it appears in expanded state
1421
+ // Expanded: left:80 + border:1 + padding:12 + half-indicator:6 = 99px horizontal center
1422
+ // Expanded: bottom:20 + border:1 + padding:8 + half-row-height:11 = 40px vertical center (approx)
1423
+ // Collapsed circle diameter: 26px, so offset by 13px from center
1424
+ const collapsedPositions = {
1425
+ 'bottom-left': { bottom: '27px', left: '86px' },
1426
+ 'bottom-right': { bottom: '27px', right: '29px' },
1427
+ 'top-left': { top: '27px', left: '86px' },
1428
+ 'top-right': { top: '27px', right: '29px' },
1429
+ 'bottom-center': { bottom: '19px', left: '50%', transform: 'translateX(-50%)' },
1430
+ };
1431
+ const posStyle = collapsedPositions[position] ?? collapsedPositions['bottom-left'];
1519
1432
  const wrapper = this.container;
1520
1433
  wrapper.className = this.tooltipClass('left', 'devbar-collapse');
1521
1434
  wrapper.setAttribute('data-tooltip', `Click to expand DevBar${this.sweetlinkConnected ? ' (Sweetlink connected)' : ' (Sweetlink not connected)'}${errorCount > 0 ? `\n${errorCount} console error${errorCount === 1 ? '' : 's'}` : ''}`);
@@ -1543,7 +1456,7 @@ export class GlobalDevBar {
1543
1456
  width: '26px',
1544
1457
  height: '26px',
1545
1458
  boxSizing: 'border-box',
1546
- animation: 'devbar-collapse 150ms ease-out',
1459
+ animation: 'devbar-collapse 150ms ease-out'
1547
1460
  });
1548
1461
  wrapper.onclick = () => {
1549
1462
  this.collapsed = false;
@@ -1556,7 +1469,7 @@ export class GlobalDevBar {
1556
1469
  height: '6px',
1557
1470
  borderRadius: '50%',
1558
1471
  backgroundColor: this.sweetlinkConnected ? COLORS.primary : COLORS.textMuted,
1559
- boxShadow: this.sweetlinkConnected ? `0 0 6px ${COLORS.primary}` : 'none',
1472
+ boxShadow: this.sweetlinkConnected ? `0 0 6px ${COLORS.primary}` : 'none'
1560
1473
  });
1561
1474
  wrapper.appendChild(dot);
1562
1475
  // Error badge (absolute, top-right of circle, shifted left if warning badge exists)
@@ -1611,19 +1524,9 @@ export class GlobalDevBar {
1611
1524
  width: sizeOverrides?.width ?? defaultWidth,
1612
1525
  maxWidth: sizeOverrides?.maxWidth ?? defaultMaxWidth,
1613
1526
  minWidth: sizeOverrides?.minWidth ?? defaultMinWidth,
1614
- cursor: 'default',
1527
+ cursor: 'default'
1615
1528
  });
1616
1529
  wrapper.ondblclick = () => {
1617
- // Capture dot position before collapsing
1618
- const dotEl = wrapper.querySelector('.devbar-status span span');
1619
- if (dotEl) {
1620
- const rect = dotEl.getBoundingClientRect();
1621
- this.lastDotPosition = {
1622
- left: rect.left + rect.width / 2,
1623
- top: rect.top + rect.height / 2,
1624
- bottom: window.innerHeight - (rect.top + rect.height / 2),
1625
- };
1626
- }
1627
1530
  this.collapsed = true;
1628
1531
  this.render();
1629
1532
  };
@@ -1641,14 +1544,12 @@ export class GlobalDevBar {
1641
1544
  boxSizing: 'border-box',
1642
1545
  fontFamily: FONT_MONO,
1643
1546
  fontSize: '0.6875rem',
1644
- lineHeight: '1rem',
1547
+ lineHeight: '1rem'
1645
1548
  });
1646
1549
  // Connection indicator (click to collapse)
1647
1550
  const connIndicator = document.createElement('span');
1648
1551
  connIndicator.className = this.tooltipClass('left', 'devbar-clickable');
1649
- connIndicator.setAttribute('data-tooltip', this.sweetlinkConnected
1650
- ? 'Sweetlink connected (click to minimize)'
1651
- : 'Sweetlink disconnected (click to minimize)');
1552
+ connIndicator.setAttribute('data-tooltip', this.sweetlinkConnected ? 'Sweetlink connected (click to minimize)' : 'Sweetlink disconnected (click to minimize)');
1652
1553
  Object.assign(connIndicator.style, {
1653
1554
  width: '12px',
1654
1555
  height: '12px',
@@ -1658,17 +1559,10 @@ export class GlobalDevBar {
1658
1559
  alignItems: 'center',
1659
1560
  justifyContent: 'center',
1660
1561
  cursor: 'pointer',
1661
- flexShrink: '0',
1562
+ flexShrink: '0'
1662
1563
  });
1663
1564
  connIndicator.onclick = (e) => {
1664
1565
  e.stopPropagation();
1665
- // Capture dot position before collapsing (connDot is the inner 6px dot)
1666
- const rect = connIndicator.getBoundingClientRect();
1667
- this.lastDotPosition = {
1668
- left: rect.left + rect.width / 2,
1669
- top: rect.top + rect.height / 2,
1670
- bottom: window.innerHeight - (rect.top + rect.height / 2),
1671
- };
1672
1566
  this.collapsed = true;
1673
1567
  this.render();
1674
1568
  };
@@ -1679,7 +1573,7 @@ export class GlobalDevBar {
1679
1573
  borderRadius: '50%',
1680
1574
  backgroundColor: this.sweetlinkConnected ? COLORS.primary : COLORS.textMuted,
1681
1575
  boxShadow: this.sweetlinkConnected ? `0 0 6px ${COLORS.primary}` : 'none',
1682
- transition: 'all 300ms',
1576
+ transition: 'all 300ms'
1683
1577
  });
1684
1578
  connIndicator.appendChild(connDot);
1685
1579
  // Status row wrapper - keeps connection dot, info, and badges together
@@ -1690,7 +1584,7 @@ export class GlobalDevBar {
1690
1584
  alignItems: 'center',
1691
1585
  gap: '0.5rem',
1692
1586
  flexWrap: 'nowrap',
1693
- flexShrink: '0',
1587
+ flexShrink: '0'
1694
1588
  });
1695
1589
  statusRow.appendChild(connIndicator);
1696
1590
  // Info section
@@ -1704,7 +1598,7 @@ export class GlobalDevBar {
1704
1598
  letterSpacing: '0.05em',
1705
1599
  flexShrink: '1',
1706
1600
  minWidth: '0',
1707
- overflow: 'visible',
1601
+ overflow: 'visible'
1708
1602
  });
1709
1603
  // Breakpoint info
1710
1604
  if (showMetrics.breakpoint && this.breakpointInfo) {
@@ -1716,10 +1610,9 @@ export class GlobalDevBar {
1716
1610
  bpSpan.setAttribute('data-tooltip', `Tailwind Breakpoint: ${bp}\n${breakpointData?.label || ''}\n\nViewport: ${this.breakpointInfo.dimensions}\n\nBreakpoints:\nbase: <640px | sm: >=640px\nmd: >=768px | lg: >=1024px\nxl: >=1280px | 2xl: >=1536px`);
1717
1611
  let bpText = bp;
1718
1612
  if (bp !== 'base') {
1719
- bpText =
1720
- bp === 'sm'
1721
- ? `${bp} - ${this.breakpointInfo.dimensions.split('x')[0]}`
1722
- : `${bp} - ${this.breakpointInfo.dimensions}`;
1613
+ bpText = bp === 'sm'
1614
+ ? `${bp} - ${this.breakpointInfo.dimensions.split('x')[0]}`
1615
+ : `${bp} - ${this.breakpointInfo.dimensions}`;
1723
1616
  }
1724
1617
  bpSpan.textContent = bpText;
1725
1618
  infoSection.appendChild(bpSpan);
@@ -1797,7 +1690,7 @@ export class GlobalDevBar {
1797
1690
  fontFamily: FONT_MONO,
1798
1691
  fontSize: '0.6875rem',
1799
1692
  });
1800
- GlobalDevBar.customControls.forEach((control) => {
1693
+ GlobalDevBar.customControls.forEach(control => {
1801
1694
  const btn = document.createElement('button');
1802
1695
  btn.type = 'button';
1803
1696
  const color = control.variant === 'warning' ? BUTTON_COLORS.warning : accentColor;
@@ -1872,7 +1765,13 @@ export class GlobalDevBar {
1872
1765
  btn.type = 'button';
1873
1766
  btn.className = this.tooltipClass('right');
1874
1767
  const hasSuccessState = this.copiedToClipboard || this.copiedPath || this.lastScreenshot;
1875
- const tooltip = this.getScreenshotTooltip();
1768
+ const tooltip = this.copiedToClipboard
1769
+ ? 'Copied to clipboard!'
1770
+ : this.copiedPath
1771
+ ? 'Path copied to clipboard!'
1772
+ : this.lastScreenshot
1773
+ ? `Screenshot saved!\n${this.lastScreenshot}\n\nClick to copy path`
1774
+ : `Screenshot\n\nClick: Save to file\nShift+Click: Copy to clipboard\n\nKeyboard:\nCmd/Ctrl+Shift+S: Save\nCmd/Ctrl+Shift+C: Copy${!this.sweetlinkConnected ? '\n\nWarning: Sweetlink not connected' : ''}`;
1876
1775
  btn.setAttribute('data-tooltip', tooltip);
1877
1776
  Object.assign(btn.style, {
1878
1777
  display: 'flex',
@@ -1890,7 +1789,7 @@ export class GlobalDevBar {
1890
1789
  color: hasSuccessState ? accentColor : `${accentColor}99`,
1891
1790
  cursor: !this.capturing ? 'pointer' : 'not-allowed',
1892
1791
  opacity: '1',
1893
- transition: 'all 150ms',
1792
+ transition: 'all 150ms'
1894
1793
  });
1895
1794
  btn.disabled = this.capturing;
1896
1795
  btn.onclick = (e) => {
@@ -1936,47 +1835,17 @@ export class GlobalDevBar {
1936
1835
  }
1937
1836
  return btn;
1938
1837
  }
1939
- /**
1940
- * Get the tooltip text for the screenshot button based on current state
1941
- */
1942
- getScreenshotTooltip() {
1943
- if (this.copiedToClipboard) {
1944
- return 'Copied to clipboard!';
1945
- }
1946
- if (this.copiedPath) {
1947
- return 'Path copied to clipboard!';
1948
- }
1949
- if (this.lastScreenshot) {
1950
- return `Screenshot saved!\n${this.lastScreenshot}\n\nClick to copy path`;
1951
- }
1952
- const baseTooltip = `Screenshot\n\nClick: Save to file\nShift+Click: Copy to clipboard\n\nKeyboard:\nCmd/Ctrl+Shift+S: Save\nCmd/Ctrl+Shift+C: Copy`;
1953
- return this.sweetlinkConnected
1954
- ? baseTooltip
1955
- : `${baseTooltip}\n\nWarning: Sweetlink not connected`;
1956
- }
1957
- /**
1958
- * Get the tooltip text for the AI review button based on current state
1959
- */
1960
- getAIReviewTooltip() {
1961
- if (this.designReviewInProgress) {
1962
- return 'AI Design Review in progress...';
1963
- }
1964
- if (this.designReviewError) {
1965
- return `Design review failed:\n${this.designReviewError}`;
1966
- }
1967
- if (this.lastDesignReview) {
1968
- return `Design review saved to:\n${this.lastDesignReview}`;
1969
- }
1970
- const baseTooltip = `AI Design Review\n\nCaptures screenshot and sends to\nClaude for design analysis.\n\nRequires ANTHROPIC_API_KEY.`;
1971
- return this.sweetlinkConnected
1972
- ? baseTooltip
1973
- : `${baseTooltip}\n\nWarning: Sweetlink not connected`;
1974
- }
1975
1838
  createAIReviewButton() {
1976
1839
  const btn = document.createElement('button');
1977
1840
  btn.type = 'button';
1978
1841
  btn.className = this.tooltipClass('right');
1979
- const tooltip = this.getAIReviewTooltip();
1842
+ const tooltip = this.designReviewInProgress
1843
+ ? 'AI Design Review in progress...'
1844
+ : this.designReviewError
1845
+ ? `Design review failed:\n${this.designReviewError}`
1846
+ : this.lastDesignReview
1847
+ ? `Design review saved to:\n${this.lastDesignReview}`
1848
+ : `AI Design Review\n\nCaptures screenshot and sends to\nClaude for design analysis.\n\nRequires ANTHROPIC_API_KEY.${!this.sweetlinkConnected ? '\n\nWarning: Sweetlink not connected' : ''}`;
1980
1849
  btn.setAttribute('data-tooltip', tooltip);
1981
1850
  const hasError = !!this.designReviewError;
1982
1851
  const isActive = this.designReviewInProgress || !!this.lastDesignReview || hasError;
@@ -2076,7 +1945,7 @@ export function initGlobalDevBar(options) {
2076
1945
  const existing = getGlobalInstance();
2077
1946
  if (existing) {
2078
1947
  // Check if already initialized with same position - skip re-init during HMR
2079
- const existingPosition = existing.getPosition();
1948
+ const existingPosition = existing['options']?.position ?? 'bottom-left';
2080
1949
  const newPosition = options?.position ?? 'bottom-left';
2081
1950
  if (existingPosition === newPosition) {
2082
1951
  return existing;
package/dist/constants.js CHANGED
@@ -41,11 +41,11 @@ export const SCREENSHOT_SCALE = 0.75;
41
41
  // ============================================================================
42
42
  /** Tailwind CSS breakpoint definitions */
43
43
  export const TAILWIND_BREAKPOINTS = {
44
- base: { min: 0, label: 'Tailwind base: <640px' },
45
- sm: { min: 640, label: 'Tailwind sm: >=640px' },
46
- md: { min: 768, label: 'Tailwind md: >=768px' },
47
- lg: { min: 1024, label: 'Tailwind lg: >=1024px' },
48
- xl: { min: 1280, label: 'Tailwind xl: >=1280px' },
44
+ 'base': { min: 0, label: 'Tailwind base: <640px' },
45
+ 'sm': { min: 640, label: 'Tailwind sm: >=640px' },
46
+ 'md': { min: 768, label: 'Tailwind md: >=768px' },
47
+ 'lg': { min: 1024, label: 'Tailwind lg: >=1024px' },
48
+ 'xl': { min: 1280, label: 'Tailwind xl: >=1280px' },
49
49
  '2xl': { min: 1536, label: 'Tailwind 2xl: >=1536px' },
50
50
  };
51
51
  // ============================================================================
@@ -489,8 +489,7 @@ export const TOOLTIP_STYLES = `
489
489
  }
490
490
  /* BASE only (< 640px): fit content, centered horizontally */
491
491
  @media (max-width: 639px) {
492
- /* Expanded state: center and constrain width */
493
- [data-devbar]:not(.devbar-collapse) {
492
+ [data-devbar] {
494
493
  width: auto !important;
495
494
  min-width: auto !important;
496
495
  max-width: calc(100vw - 32px) !important;
@@ -498,7 +497,6 @@ export const TOOLTIP_STYLES = `
498
497
  right: auto !important;
499
498
  transform: translateX(-50%) !important;
500
499
  }
501
- /* Collapsed state: JS handles positioning based on captured dot location */
502
500
  .devbar-main {
503
501
  flex-wrap: wrap;
504
502
  justify-content: center;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ytspar/devbar",
3
- "version": "1.0.0-canary.3007fc6",
3
+ "version": "1.0.0-canary.92db425",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "description": "Development toolbar and utilities with Sweetlink integration - pure vanilla JS, no framework dependencies",