binja 0.5.3 → 0.6.1

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/dist/index.js CHANGED
@@ -5081,6 +5081,14 @@ class DebugCollector {
5081
5081
  testsUsed: new Map,
5082
5082
  cacheHits: 0,
5083
5083
  cacheMisses: 0,
5084
+ queries: [],
5085
+ queryStats: {
5086
+ count: 0,
5087
+ totalDuration: 0,
5088
+ slowCount: 0,
5089
+ n1Count: 0,
5090
+ queryCounts: new Map
5091
+ },
5084
5092
  warnings: []
5085
5093
  };
5086
5094
  }
@@ -5221,6 +5229,33 @@ class DebugCollector {
5221
5229
  addWarning(message) {
5222
5230
  this.data.warnings.push(message);
5223
5231
  }
5232
+ recordQuery(query) {
5233
+ const normalizedSql = this.normalizeQuery(query.sql);
5234
+ const currentCount = this.data.queryStats.queryCounts.get(normalizedSql) || 0;
5235
+ this.data.queryStats.queryCounts.set(normalizedSql, currentCount + 1);
5236
+ const isN1 = currentCount >= 2;
5237
+ const queryInfo = {
5238
+ ...query,
5239
+ timestamp: performance.now(),
5240
+ isN1
5241
+ };
5242
+ this.data.queries.push(queryInfo);
5243
+ this.data.queryStats.count++;
5244
+ this.data.queryStats.totalDuration += query.duration;
5245
+ if (query.duration > 100) {
5246
+ this.data.queryStats.slowCount++;
5247
+ }
5248
+ if (isN1 && currentCount === 2) {
5249
+ this.data.queryStats.n1Count++;
5250
+ this.addWarning(`N+1 query detected: ${normalizedSql.slice(0, 50)}...`);
5251
+ }
5252
+ }
5253
+ normalizeQuery(sql) {
5254
+ return sql.replace(/\s+/g, " ").replace(/= \?/g, "= ?").replace(/= \$\d+/g, "= ?").replace(/= '\w+'/g, "= '?'").replace(/= \d+/g, "= ?").replace(/IN \([^)]+\)/gi, "IN (?)").trim();
5255
+ }
5256
+ getQueryStats() {
5257
+ return this.data.queryStats;
5258
+ }
5224
5259
  getData() {
5225
5260
  return this.data;
5226
5261
  }
@@ -5249,388 +5284,516 @@ function endDebugCollection() {
5249
5284
 
5250
5285
  // src/debug/panel.ts
5251
5286
  var DEFAULT_OPTIONS = {
5252
- position: "bottom-right",
5253
- collapsed: true,
5254
- dark: true,
5255
- width: 420
5287
+ position: "bottom",
5288
+ height: 300,
5289
+ width: 400,
5290
+ open: false,
5291
+ dark: true
5256
5292
  };
5257
5293
  function generateDebugPanel(data, options = {}) {
5258
5294
  const opts = { ...DEFAULT_OPTIONS, ...options };
5259
- const id = `binja-debug-${Date.now()}`;
5260
- const colors2 = opts.dark ? darkTheme : lightTheme;
5295
+ const id = `binja-dbg-${Date.now()}`;
5296
+ const c2 = opts.dark ? darkTheme : lightTheme;
5261
5297
  return `
5262
5298
  <!-- Binja Debug Panel -->
5263
- <div id="${id}" class="binja-debugger" data-theme="${opts.dark ? "dark" : "light"}">
5264
- <style>${generateStyles(id, colors2, opts)}</style>
5265
- ${generateToggle(id, data, colors2)}
5266
- ${generatePanel(id, data, colors2, opts)}
5267
- <script>${generateScript(id)}</script>
5299
+ <div id="${id}" class="binja-devtools" data-position="${opts.position}" data-open="${opts.open}">
5300
+ <style>${generateStyles(id, c2, opts)}</style>
5301
+ ${generateHTML(id, data, c2, opts)}
5302
+ <script>${generateScript(id, data, opts)}</script>
5268
5303
  </div>
5269
5304
  <!-- /Binja Debug Panel -->
5270
5305
  `;
5271
5306
  }
5272
5307
  var darkTheme = {
5273
- bg: "#0f0f0f",
5274
- bgSecondary: "#1a1a1a",
5275
- bgTertiary: "#242424",
5276
- border: "#2a2a2a",
5277
- borderLight: "#333",
5278
- text: "#e5e5e5",
5279
- textSecondary: "#a0a0a0",
5280
- textMuted: "#666",
5281
- accent: "#3b82f6",
5282
- accentHover: "#2563eb",
5283
- success: "#22c55e",
5284
- warning: "#eab308",
5285
- error: "#ef4444",
5286
- info: "#06b6d4"
5308
+ bg: "#1e1e1e",
5309
+ bgPanel: "#252526",
5310
+ bgHover: "#2a2d2e",
5311
+ bgActive: "#37373d",
5312
+ border: "#3c3c3c",
5313
+ text: "#cccccc",
5314
+ textSecondary: "#969696",
5315
+ textMuted: "#6e6e6e",
5316
+ accent: "#0078d4",
5317
+ accentHover: "#1c86d8",
5318
+ success: "#4ec9b0",
5319
+ warning: "#dcdcaa",
5320
+ error: "#f14c4c",
5321
+ info: "#75beff",
5322
+ string: "#ce9178",
5323
+ number: "#b5cea8",
5324
+ keyword: "#569cd6"
5287
5325
  };
5288
5326
  var lightTheme = {
5289
- bg: "#ffffff",
5290
- bgSecondary: "#f8f9fa",
5291
- bgTertiary: "#f1f3f4",
5292
- border: "#e5e7eb",
5293
- borderLight: "#d1d5db",
5294
- text: "#111827",
5295
- textSecondary: "#4b5563",
5296
- textMuted: "#9ca3af",
5297
- accent: "#2563eb",
5298
- accentHover: "#1d4ed8",
5299
- success: "#16a34a",
5300
- warning: "#ca8a04",
5301
- error: "#dc2626",
5302
- info: "#0891b2"
5327
+ bg: "#f3f3f3",
5328
+ bgPanel: "#ffffff",
5329
+ bgHover: "#e8e8e8",
5330
+ bgActive: "#d4d4d4",
5331
+ border: "#d4d4d4",
5332
+ text: "#1e1e1e",
5333
+ textSecondary: "#616161",
5334
+ textMuted: "#a0a0a0",
5335
+ accent: "#0078d4",
5336
+ accentHover: "#106ebe",
5337
+ success: "#16825d",
5338
+ warning: "#bf8803",
5339
+ error: "#cd3131",
5340
+ info: "#0078d4",
5341
+ string: "#a31515",
5342
+ number: "#098658",
5343
+ keyword: "#0000ff"
5303
5344
  };
5304
5345
  function generateStyles(id, c2, opts) {
5305
- const pos = getPosition(opts.position);
5306
5346
  return `
5307
- #${id} { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif; font-size: 13px; line-height: 1.5; position: fixed; ${pos} z-index: 2147483647; }
5347
+ #${id} { --bg: ${c2.bg}; --bg-panel: ${c2.bgPanel}; --bg-hover: ${c2.bgHover}; --bg-active: ${c2.bgActive}; --border: ${c2.border}; --text: ${c2.text}; --text-secondary: ${c2.textSecondary}; --text-muted: ${c2.textMuted}; --accent: ${c2.accent}; --success: ${c2.success}; --warning: ${c2.warning}; --error: ${c2.error}; --string: ${c2.string}; --number: ${c2.number}; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; font-size: 12px; line-height: 1.4; color: var(--text); }
5308
5348
  #${id} * { box-sizing: border-box; margin: 0; padding: 0; }
5309
- #${id} .dbg-toggle { display: inline-flex; align-items: center; gap: 8px; padding: 8px 14px; background: ${c2.bg}; border: 1px solid ${c2.border}; border-radius: 8px; color: ${c2.text}; cursor: pointer; font-size: 12px; font-weight: 500; box-shadow: 0 4px 12px rgba(0,0,0,0.15); transition: all 0.2s ease; }
5310
- #${id} .dbg-toggle:hover { border-color: ${c2.accent}; box-shadow: 0 4px 16px rgba(0,0,0,0.2); }
5311
- #${id} .dbg-toggle svg { width: 16px; height: 16px; }
5312
- #${id} .dbg-toggle .dbg-time { font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; font-size: 11px; padding: 2px 8px; background: ${c2.bgTertiary}; border-radius: 4px; color: ${c2.success}; }
5313
- #${id} .dbg-panel { display: none; width: ${opts.width}px; max-height: 85vh; background: ${c2.bg}; border: 1px solid ${c2.border}; border-radius: 10px; box-shadow: 0 8px 32px rgba(0,0,0,0.24); overflow: hidden; margin-top: 8px; }
5314
- #${id} .dbg-panel.open { display: block; }
5315
- #${id} .dbg-header { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; background: ${c2.bgSecondary}; border-bottom: 1px solid ${c2.border}; }
5316
- #${id} .dbg-logo { display: flex; align-items: center; gap: 10px; font-weight: 600; color: ${c2.text}; }
5317
- #${id} .dbg-logo svg { width: 20px; height: 20px; color: ${c2.accent}; }
5318
- #${id} .dbg-meta { display: flex; align-items: center; gap: 12px; }
5319
- #${id} .dbg-badge { font-family: 'SF Mono', Monaco, monospace; font-size: 11px; padding: 3px 10px; border-radius: 4px; font-weight: 500; }
5320
- #${id} .dbg-badge.time { background: rgba(34,197,94,0.1); color: ${c2.success}; }
5321
- #${id} .dbg-badge.mode { background: rgba(59,130,246,0.1); color: ${c2.accent}; }
5322
- #${id} .dbg-close { background: none; border: none; color: ${c2.textMuted}; cursor: pointer; padding: 4px; border-radius: 4px; display: flex; }
5323
- #${id} .dbg-close:hover { background: ${c2.bgTertiary}; color: ${c2.text}; }
5324
- #${id} .dbg-close svg { width: 18px; height: 18px; }
5325
- #${id} .dbg-body { max-height: calc(85vh - 52px); overflow-y: auto; }
5326
- #${id} .dbg-section { border-bottom: 1px solid ${c2.border}; }
5327
- #${id} .dbg-section:last-child { border-bottom: none; }
5328
- #${id} .dbg-section-header { display: flex; align-items: center; justify-content: space-between; padding: 10px 16px; cursor: pointer; user-select: none; transition: background 0.15s; }
5329
- #${id} .dbg-section-header:hover { background: ${c2.bgSecondary}; }
5330
- #${id} .dbg-section-title { display: flex; align-items: center; gap: 8px; font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; color: ${c2.textSecondary}; }
5331
- #${id} .dbg-section-title svg { width: 14px; height: 14px; opacity: 0.7; }
5332
- #${id} .dbg-section-meta { font-size: 11px; color: ${c2.textMuted}; font-family: 'SF Mono', Monaco, monospace; }
5333
- #${id} .dbg-section-content { display: none; padding: 12px 16px; background: ${c2.bgSecondary}; }
5334
- #${id} .dbg-section.open .dbg-section-content { display: block; }
5335
- #${id} .dbg-section .dbg-chevron { transition: transform 0.2s; color: ${c2.textMuted}; }
5336
- #${id} .dbg-section.open .dbg-chevron { transform: rotate(90deg); }
5337
- #${id} .dbg-row { display: flex; justify-content: space-between; align-items: center; padding: 6px 0; border-bottom: 1px solid ${c2.border}; }
5338
- #${id} .dbg-row:last-child { border-bottom: none; }
5339
- #${id} .dbg-label { color: ${c2.textSecondary}; font-size: 12px; }
5340
- #${id} .dbg-value { color: ${c2.text}; font-family: 'SF Mono', Monaco, monospace; font-size: 12px; text-align: right; max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
5341
- #${id} .dbg-bar { height: 3px; background: ${c2.bgTertiary}; border-radius: 2px; margin-top: 4px; overflow: hidden; }
5342
- #${id} .dbg-bar-fill { height: 100%; border-radius: 2px; transition: width 0.3s ease; }
5343
- #${id} .dbg-bar-fill.lexer { background: ${c2.info}; }
5344
- #${id} .dbg-bar-fill.parser { background: ${c2.warning}; }
5345
- #${id} .dbg-bar-fill.render { background: ${c2.success}; }
5346
- #${id} .dbg-templates { display: flex; flex-direction: column; gap: 6px; }
5347
- #${id} .dbg-template { display: flex; align-items: center; gap: 8px; padding: 8px 10px; background: ${c2.bg}; border-radius: 6px; font-size: 12px; }
5348
- #${id} .dbg-template-icon { width: 16px; height: 16px; color: ${c2.textMuted}; flex-shrink: 0; }
5349
- #${id} .dbg-template-name { color: ${c2.text}; font-family: 'SF Mono', Monaco, monospace; }
5350
- #${id} .dbg-template-tag { font-size: 10px; padding: 2px 6px; border-radius: 3px; font-weight: 500; text-transform: uppercase; }
5351
- #${id} .dbg-template-tag.root { background: rgba(59,130,246,0.15); color: ${c2.accent}; }
5352
- #${id} .dbg-template-tag.extends { background: rgba(168,85,247,0.15); color: #a855f7; }
5353
- #${id} .dbg-template-tag.include { background: rgba(34,197,94,0.15); color: ${c2.success}; }
5354
- #${id} .dbg-ctx-grid { display: flex; flex-direction: column; gap: 4px; }
5355
- #${id} .dbg-ctx-item { background: ${c2.bg}; border-radius: 6px; overflow: hidden; }
5356
- #${id} .dbg-ctx-row { display: flex; align-items: center; justify-content: space-between; padding: 8px 10px; cursor: default; }
5357
- #${id} .dbg-ctx-row.expandable { cursor: pointer; }
5358
- #${id} .dbg-ctx-row.expandable:hover { background: ${c2.bgTertiary}; }
5359
- #${id} .dbg-ctx-key { display: flex; align-items: center; gap: 6px; }
5360
- #${id} .dbg-ctx-arrow { width: 12px; height: 12px; color: ${c2.textMuted}; transition: transform 0.15s; flex-shrink: 0; }
5361
- #${id} .dbg-ctx-item.open > .dbg-ctx-row .dbg-ctx-arrow { transform: rotate(90deg); }
5362
- #${id} .dbg-ctx-name { color: ${c2.text}; font-family: 'SF Mono', Monaco, monospace; font-size: 12px; }
5363
- #${id} .dbg-ctx-type { font-size: 10px; color: ${c2.accent}; background: rgba(59,130,246,0.1); padding: 1px 5px; border-radius: 3px; }
5364
- #${id} .dbg-ctx-preview { color: ${c2.textSecondary}; font-family: 'SF Mono', Monaco, monospace; font-size: 11px; max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
5365
- #${id} .dbg-ctx-children { display: none; padding-left: 16px; border-left: 1px solid ${c2.border}; margin-left: 10px; }
5366
- #${id} .dbg-ctx-item.open > .dbg-ctx-children { display: block; }
5367
- #${id} .dbg-ctx-children .dbg-ctx-item { background: transparent; }
5368
- #${id} .dbg-ctx-children .dbg-ctx-row { padding: 4px 8px; }
5369
- #${id} .dbg-filters { display: flex; flex-wrap: wrap; gap: 6px; }
5370
- #${id} .dbg-filter { display: inline-flex; align-items: center; gap: 4px; padding: 4px 10px; background: ${c2.bg}; border-radius: 5px; font-size: 12px; font-family: 'SF Mono', Monaco, monospace; color: ${c2.text}; }
5371
- #${id} .dbg-filter-count { font-size: 10px; color: ${c2.accent}; font-weight: 600; }
5372
- #${id} .dbg-cache { display: flex; gap: 16px; }
5373
- #${id} .dbg-cache-stat { flex: 1; padding: 12px; background: ${c2.bg}; border-radius: 6px; text-align: center; }
5374
- #${id} .dbg-cache-num { font-size: 24px; font-weight: 600; font-family: 'SF Mono', Monaco, monospace; }
5375
- #${id} .dbg-cache-num.hit { color: ${c2.success}; }
5376
- #${id} .dbg-cache-num.miss { color: ${c2.error}; }
5377
- #${id} .dbg-cache-label { font-size: 11px; color: ${c2.textMuted}; margin-top: 4px; }
5378
- #${id} .dbg-warnings { display: flex; flex-direction: column; gap: 6px; }
5379
- #${id} .dbg-warning { display: flex; align-items: flex-start; gap: 8px; padding: 10px 12px; background: rgba(234,179,8,0.1); border-radius: 6px; border-left: 3px solid ${c2.warning}; }
5380
- #${id} .dbg-warning-icon { color: ${c2.warning}; flex-shrink: 0; margin-top: 1px; }
5381
- #${id} .dbg-warning-text { color: ${c2.text}; font-size: 12px; }
5349
+
5350
+ /* Toggle Button */
5351
+ #${id} .devtools-toggle { position: fixed; bottom: 10px; right: 10px; z-index: 2147483646; display: flex; align-items: center; gap: 6px; padding: 8px 12px; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; color: var(--text); cursor: pointer; font-size: 11px; font-weight: 500; box-shadow: 0 2px 8px rgba(0,0,0,0.2); transition: all 0.15s; }
5352
+ #${id} .devtools-toggle:hover { background: var(--bg-hover); border-color: var(--accent); }
5353
+ #${id} .devtools-toggle svg { width: 14px; height: 14px; color: var(--accent); }
5354
+ #${id} .devtools-toggle .badge { padding: 2px 6px; background: var(--accent); color: #fff; border-radius: 3px; font-size: 10px; }
5355
+ #${id}[data-open="true"] .devtools-toggle { display: none; }
5356
+
5357
+ /* Panel Container */
5358
+ #${id} .devtools-panel { display: none; position: fixed; z-index: 2147483647; background: var(--bg); border: 1px solid var(--border); box-shadow: 0 -2px 12px rgba(0,0,0,0.3); }
5359
+ #${id}[data-open="true"] .devtools-panel { display: flex; flex-direction: column; }
5360
+ #${id}[data-position="bottom"] .devtools-panel { left: 0; right: 0; bottom: 0; height: ${opts.height}px; border-left: none; border-right: none; border-bottom: none; }
5361
+ #${id}[data-position="right"] .devtools-panel { top: 0; right: 0; bottom: 0; width: ${opts.width}px; border-top: none; border-right: none; border-bottom: none; }
5362
+ #${id}[data-position="popup"] .devtools-panel { bottom: 50px; right: 20px; width: 700px; height: 500px; border-radius: 8px; }
5363
+
5364
+ /* Resize Handle */
5365
+ #${id} .devtools-resize { position: absolute; background: transparent; }
5366
+ #${id}[data-position="bottom"] .devtools-resize { top: 0; left: 0; right: 0; height: 4px; cursor: ns-resize; }
5367
+ #${id}[data-position="right"] .devtools-resize { top: 0; left: 0; bottom: 0; width: 4px; cursor: ew-resize; }
5368
+ #${id} .devtools-resize:hover { background: var(--accent); }
5369
+
5370
+ /* Toolbar */
5371
+ #${id} .devtools-toolbar { display: flex; align-items: center; justify-content: space-between; height: 30px; padding: 0 8px; background: var(--bg-panel); border-bottom: 1px solid var(--border); flex-shrink: 0; }
5372
+ #${id} .devtools-tabs { display: flex; height: 100%; }
5373
+ #${id} .devtools-tab { display: flex; align-items: center; gap: 4px; padding: 0 12px; border: none; background: none; color: var(--text-secondary); font-size: 11px; cursor: pointer; border-bottom: 2px solid transparent; transition: all 0.1s; }
5374
+ #${id} .devtools-tab:hover { color: var(--text); background: var(--bg-hover); }
5375
+ #${id} .devtools-tab.active { color: var(--text); border-bottom-color: var(--accent); }
5376
+ #${id} .devtools-tab svg { width: 12px; height: 12px; opacity: 0.7; }
5377
+ #${id} .devtools-tab .count { margin-left: 4px; padding: 1px 5px; background: var(--bg-active); border-radius: 8px; font-size: 10px; }
5378
+ #${id} .devtools-tab .count.warn { background: rgba(241,76,76,0.2); color: var(--error); }
5379
+ #${id} .devtools-actions { display: flex; align-items: center; gap: 4px; }
5380
+ #${id} .devtools-btn { display: flex; align-items: center; justify-content: center; width: 24px; height: 24px; border: none; background: none; color: var(--text-secondary); cursor: pointer; border-radius: 3px; }
5381
+ #${id} .devtools-btn:hover { background: var(--bg-hover); color: var(--text); }
5382
+ #${id} .devtools-btn svg { width: 14px; height: 14px; }
5383
+
5384
+ /* Content Area */
5385
+ #${id} .devtools-content { flex: 1; overflow: hidden; }
5386
+ #${id} .devtools-pane { display: none; height: 100%; overflow: auto; padding: 8px; }
5387
+ #${id} .devtools-pane.active { display: block; }
5388
+
5389
+ /* Performance Tab */
5390
+ #${id} .perf-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 8px; margin-bottom: 12px; }
5391
+ #${id} .perf-card { background: var(--bg-panel); border: 1px solid var(--border); border-radius: 4px; padding: 10px; text-align: center; }
5392
+ #${id} .perf-card-value { font-size: 20px; font-weight: 600; font-family: 'SF Mono', Monaco, monospace; color: var(--success); }
5393
+ #${id} .perf-card-label { font-size: 10px; color: var(--text-muted); margin-top: 4px; text-transform: uppercase; }
5394
+ #${id} .perf-breakdown { background: var(--bg-panel); border: 1px solid var(--border); border-radius: 4px; overflow: hidden; }
5395
+ #${id} .perf-row { display: flex; align-items: center; padding: 8px 12px; border-bottom: 1px solid var(--border); }
5396
+ #${id} .perf-row:last-child { border-bottom: none; }
5397
+ #${id} .perf-row-label { flex: 1; font-size: 11px; color: var(--text-secondary); }
5398
+ #${id} .perf-row-value { font-family: 'SF Mono', Monaco, monospace; font-size: 11px; min-width: 60px; text-align: right; }
5399
+ #${id} .perf-row-bar { flex: 2; height: 4px; background: var(--bg-active); border-radius: 2px; margin: 0 12px; overflow: hidden; }
5400
+ #${id} .perf-row-fill { height: 100%; border-radius: 2px; }
5401
+ #${id} .perf-row-fill.lexer { background: ${c2.info}; }
5402
+ #${id} .perf-row-fill.parser { background: ${c2.warning}; }
5403
+ #${id} .perf-row-fill.render { background: ${c2.success}; }
5404
+
5405
+ /* Context Tab - Tree View */
5406
+ #${id} .tree { font-family: 'SF Mono', Monaco, Consolas, monospace; font-size: 11px; }
5407
+ #${id} .tree-item { }
5408
+ #${id} .tree-row { display: flex; align-items: center; padding: 2px 4px; cursor: default; border-radius: 2px; }
5409
+ #${id} .tree-row:hover { background: var(--bg-hover); }
5410
+ #${id} .tree-row.expandable { cursor: pointer; }
5411
+ #${id} .tree-arrow { width: 12px; height: 12px; color: var(--text-muted); transition: transform 0.1s; flex-shrink: 0; }
5412
+ #${id} .tree-item.open > .tree-row .tree-arrow { transform: rotate(90deg); }
5413
+ #${id} .tree-key { color: var(--keyword); margin-right: 4px; }
5414
+ #${id} .tree-colon { color: var(--text-muted); margin-right: 6px; }
5415
+ #${id} .tree-value { color: var(--text); }
5416
+ #${id} .tree-value.string { color: var(--string); }
5417
+ #${id} .tree-value.number { color: var(--number); }
5418
+ #${id} .tree-value.null { color: var(--text-muted); font-style: italic; }
5419
+ #${id} .tree-type { color: var(--text-muted); margin-left: 6px; font-size: 10px; }
5420
+ #${id} .tree-children { display: none; padding-left: 16px; }
5421
+ #${id} .tree-item.open > .tree-children { display: block; }
5422
+
5423
+ /* Templates Tab */
5424
+ #${id} .template-list { display: flex; flex-direction: column; gap: 4px; }
5425
+ #${id} .template-item { display: flex; align-items: center; gap: 8px; padding: 8px 10px; background: var(--bg-panel); border: 1px solid var(--border); border-radius: 4px; }
5426
+ #${id} .template-icon { width: 14px; height: 14px; color: var(--text-muted); }
5427
+ #${id} .template-name { font-family: 'SF Mono', Monaco, monospace; font-size: 11px; }
5428
+ #${id} .template-badge { font-size: 9px; padding: 2px 6px; border-radius: 3px; text-transform: uppercase; font-weight: 500; }
5429
+ #${id} .template-badge.root { background: rgba(0,120,212,0.15); color: var(--accent); }
5430
+ #${id} .template-badge.extends { background: rgba(156,86,246,0.15); color: #9c56f6; }
5431
+ #${id} .template-badge.include { background: rgba(78,201,176,0.15); color: var(--success); }
5432
+
5433
+ /* Queries Tab */
5434
+ #${id} .queries-stats { display: flex; gap: 16px; padding: 8px 12px; background: var(--bg-panel); border: 1px solid var(--border); border-radius: 4px; margin-bottom: 8px; }
5435
+ #${id} .queries-stat { text-align: center; }
5436
+ #${id} .queries-stat-value { font-size: 16px; font-weight: 600; font-family: 'SF Mono', Monaco, monospace; }
5437
+ #${id} .queries-stat-value.warn { color: var(--warning); }
5438
+ #${id} .queries-stat-value.error { color: var(--error); }
5439
+ #${id} .queries-stat-label { font-size: 9px; color: var(--text-muted); text-transform: uppercase; }
5440
+ #${id} .query-list { display: flex; flex-direction: column; gap: 4px; }
5441
+ #${id} .query-item { background: var(--bg-panel); border: 1px solid var(--border); border-radius: 4px; overflow: hidden; }
5442
+ #${id} .query-item.n1 { border-left: 3px solid var(--error); }
5443
+ #${id} .query-item.slow { border-left: 3px solid var(--warning); }
5444
+ #${id} .query-header { display: flex; align-items: center; gap: 8px; padding: 6px 10px; }
5445
+ #${id} .query-sql { flex: 1; font-family: 'SF Mono', Monaco, monospace; font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--text); }
5446
+ #${id} .query-meta { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }
5447
+ #${id} .query-badge { font-size: 9px; padding: 2px 5px; border-radius: 3px; font-weight: 600; }
5448
+ #${id} .query-badge.n1 { background: rgba(241,76,76,0.15); color: var(--error); }
5449
+ #${id} .query-source { font-size: 9px; padding: 2px 5px; border-radius: 3px; background: var(--bg-active); color: var(--text-muted); text-transform: uppercase; }
5450
+ #${id} .query-time { font-family: 'SF Mono', Monaco, monospace; font-size: 10px; color: var(--text-secondary); }
5451
+ #${id} .query-time.slow { color: var(--warning); }
5452
+ #${id} .query-rows { font-size: 10px; color: var(--text-muted); }
5453
+
5454
+ /* Filters Tab */
5455
+ #${id} .filter-grid { display: flex; flex-wrap: wrap; gap: 6px; }
5456
+ #${id} .filter-chip { display: inline-flex; align-items: center; gap: 4px; padding: 4px 10px; background: var(--bg-panel); border: 1px solid var(--border); border-radius: 4px; font-family: 'SF Mono', Monaco, monospace; font-size: 11px; }
5457
+ #${id} .filter-count { color: var(--accent); font-weight: 600; font-size: 10px; }
5458
+
5459
+ /* Cache Tab */
5460
+ #${id} .cache-stats { display: flex; gap: 20px; }
5461
+ #${id} .cache-stat { flex: 1; background: var(--bg-panel); border: 1px solid var(--border); border-radius: 4px; padding: 16px; text-align: center; }
5462
+ #${id} .cache-value { font-size: 32px; font-weight: 600; font-family: 'SF Mono', Monaco, monospace; }
5463
+ #${id} .cache-value.hit { color: var(--success); }
5464
+ #${id} .cache-value.miss { color: var(--error); }
5465
+ #${id} .cache-label { font-size: 11px; color: var(--text-muted); margin-top: 4px; }
5466
+
5467
+ /* Warnings Tab */
5468
+ #${id} .warning-list { display: flex; flex-direction: column; gap: 6px; }
5469
+ #${id} .warning-item { display: flex; align-items: flex-start; gap: 8px; padding: 10px 12px; background: rgba(241,76,76,0.08); border: 1px solid rgba(241,76,76,0.2); border-radius: 4px; }
5470
+ #${id} .warning-icon { color: var(--error); flex-shrink: 0; width: 14px; height: 14px; }
5471
+ #${id} .warning-text { font-size: 11px; color: var(--text); }
5472
+
5473
+ /* Empty State */
5474
+ #${id} .empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; color: var(--text-muted); font-size: 12px; }
5475
+ #${id} .empty-state svg { width: 32px; height: 32px; margin-bottom: 8px; opacity: 0.5; }
5382
5476
  `;
5383
5477
  }
5384
- function getPosition(pos) {
5385
- switch (pos) {
5386
- case "bottom-left":
5387
- return "bottom: 16px; left: 16px;";
5388
- case "top-right":
5389
- return "top: 16px; right: 16px;";
5390
- case "top-left":
5391
- return "top: 16px; left: 16px;";
5392
- default:
5393
- return "bottom: 16px; right: 16px;";
5394
- }
5395
- }
5396
5478
  var icons = {
5397
5479
  logo: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>`,
5398
5480
  close: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6L6 18M6 6l12 12"/></svg>`,
5399
- chevron: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 18l6-6-6-6"/></svg>`,
5481
+ minimize: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14"/></svg>`,
5482
+ dock: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 15h18"/></svg>`,
5483
+ dockRight: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M15 3v18"/></svg>`,
5484
+ popup: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M9 3v6h6"/></svg>`,
5485
+ arrow: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 18l6-6-6-6"/></svg>`,
5400
5486
  perf: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>`,
5401
- template: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><path d="M14 2v6h6"/></svg>`,
5402
5487
  context: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 16V8a2 2 0 00-1-1.73l-7-4a2 2 0 00-2 0l-7 4A2 2 0 003 8v8a2 2 0 001 1.73l7 4a2 2 0 002 0l7-4A2 2 0 0021 16z"/></svg>`,
5488
+ template: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><path d="M14 2v6h6"/></svg>`,
5403
5489
  filter: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"/></svg>`,
5490
+ database: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/></svg>`,
5404
5491
  cache: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2a10 10 0 1010 10H12V2z"/><path d="M12 2a10 10 0 00-8.66 15"/></svg>`,
5405
- warning: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>`,
5406
- file: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M13 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V9z"/></svg>`
5492
+ warning: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>`
5407
5493
  };
5408
- function generateToggle(id, data, c2) {
5494
+ function generateHTML(id, data, c2, opts) {
5409
5495
  const time2 = (data.totalTime || 0).toFixed(1);
5496
+ const queryCount = data.queries?.length || 0;
5497
+ const hasWarnings = data.warnings.length > 0 || data.queryStats?.n1Count > 0;
5498
+ const tabs = [
5499
+ { id: "perf", icon: icons.perf, label: "Performance", count: `${time2}ms` },
5500
+ { id: "context", icon: icons.context, label: "Context", count: Object.keys(data.contextSnapshot).length || null },
5501
+ { id: "templates", icon: icons.template, label: "Templates", count: data.templateChain.length || null },
5502
+ { id: "filters", icon: icons.filter, label: "Filters", count: data.filtersUsed.size || null },
5503
+ { id: "queries", icon: icons.database, label: "Queries", count: queryCount || null, warn: data.queryStats?.n1Count > 0 },
5504
+ { id: "cache", icon: icons.cache, label: "Cache", count: data.cacheHits + data.cacheMisses || null },
5505
+ { id: "warnings", icon: icons.warning, label: "Warnings", count: data.warnings.length || null, warn: hasWarnings }
5506
+ ];
5507
+ const tabsHtml = tabs.map((t, i) => {
5508
+ const countHtml = t.count !== null ? `<span class="count${t.warn ? " warn" : ""}">${t.count}</span>` : "";
5509
+ return `<button class="devtools-tab${i === 0 ? " active" : ""}" data-tab="${t.id}">${t.icon}${t.label}${countHtml}</button>`;
5510
+ }).join("");
5410
5511
  return `
5411
- <button class="dbg-toggle" onclick="document.querySelector('#${id} .dbg-panel').classList.add('open');this.style.display='none'">
5512
+ <button class="devtools-toggle" onclick="document.getElementById('${id}').dataset.open='true'">
5412
5513
  ${icons.logo}
5413
5514
  <span>Binja</span>
5414
- <span class="dbg-time">${time2}ms</span>
5415
- </button>`;
5416
- }
5417
- function generatePanel(id, data, c2, opts) {
5418
- const time2 = (data.totalTime || 0).toFixed(2);
5419
- const mode = data.mode === "aot" ? "AOT" : "Runtime";
5420
- return `
5421
- <div class="dbg-panel">
5422
- <div class="dbg-header">
5423
- <div class="dbg-logo">${icons.logo} Binja Debugger</div>
5424
- <div class="dbg-meta">
5425
- <span class="dbg-badge mode">${mode}</span>
5426
- <span class="dbg-badge time">${time2}ms</span>
5427
- <button class="dbg-close" onclick="document.querySelector('#${id} .dbg-panel').classList.remove('open');document.querySelector('#${id} .dbg-toggle').style.display='inline-flex'">${icons.close}</button>
5515
+ <span class="badge">${time2}ms</span>
5516
+ </button>
5517
+
5518
+ <div class="devtools-panel">
5519
+ <div class="devtools-resize"></div>
5520
+ <div class="devtools-toolbar">
5521
+ <div class="devtools-tabs">${tabsHtml}</div>
5522
+ <div class="devtools-actions">
5523
+ <button class="devtools-btn" title="Dock to bottom" data-dock="bottom">${icons.dock}</button>
5524
+ <button class="devtools-btn" title="Dock to right" data-dock="right">${icons.dockRight}</button>
5525
+ <button class="devtools-btn" title="Popup" data-dock="popup">${icons.popup}</button>
5526
+ <button class="devtools-btn" title="Close" onclick="document.getElementById('${id}').dataset.open='false'">${icons.close}</button>
5428
5527
  </div>
5429
5528
  </div>
5430
- <div class="dbg-body">
5431
- ${generatePerfSection(data)}
5432
- ${generateTemplatesSection(data)}
5433
- ${generateContextSection(data)}
5434
- ${generateFiltersSection(data)}
5435
- ${generateCacheSection(data)}
5436
- ${generateWarningsSection(data)}
5529
+ <div class="devtools-content">
5530
+ ${generatePerfPane(data)}
5531
+ ${generateContextPane(data)}
5532
+ ${generateTemplatesPane(data)}
5533
+ ${generateFiltersPane(data)}
5534
+ ${generateQueriesPane(data)}
5535
+ ${generateCachePane(data)}
5536
+ ${generateWarningsPane(data)}
5437
5537
  </div>
5438
5538
  </div>`;
5439
5539
  }
5440
- function generatePerfSection(data) {
5540
+ function generatePerfPane(data) {
5441
5541
  const total = data.totalTime || 0.01;
5442
5542
  const lexer = data.lexerTime || 0;
5443
5543
  const parser = data.parserTime || 0;
5444
5544
  const render = data.renderTime || 0;
5545
+ const mode = data.mode === "aot" ? "AOT" : "Runtime";
5445
5546
  return `
5446
- <div class="dbg-section open">
5447
- <div class="dbg-section-header" onclick="this.parentElement.classList.toggle('open')">
5448
- <span class="dbg-section-title">${icons.perf} Performance</span>
5449
- <span class="dbg-chevron">${icons.chevron}</span>
5450
- </div>
5451
- <div class="dbg-section-content">
5452
- <div class="dbg-row">
5453
- <span class="dbg-label">Lexer</span>
5454
- <span class="dbg-value">${lexer.toFixed(2)}ms</span>
5547
+ <div class="devtools-pane active" data-pane="perf">
5548
+ <div class="perf-grid">
5549
+ <div class="perf-card">
5550
+ <div class="perf-card-value">${total.toFixed(2)}ms</div>
5551
+ <div class="perf-card-label">Total Time</div>
5455
5552
  </div>
5456
- <div class="dbg-bar"><div class="dbg-bar-fill lexer" style="width:${lexer / total * 100}%"></div></div>
5457
- <div class="dbg-row">
5458
- <span class="dbg-label">Parser</span>
5459
- <span class="dbg-value">${parser.toFixed(2)}ms</span>
5553
+ <div class="perf-card">
5554
+ <div class="perf-card-value" style="color:var(--accent)">${mode}</div>
5555
+ <div class="perf-card-label">Mode</div>
5460
5556
  </div>
5461
- <div class="dbg-bar"><div class="dbg-bar-fill parser" style="width:${parser / total * 100}%"></div></div>
5462
- <div class="dbg-row">
5463
- <span class="dbg-label">Render</span>
5464
- <span class="dbg-value">${render.toFixed(2)}ms</span>
5557
+ <div class="perf-card">
5558
+ <div class="perf-card-value">${data.templateChain.length}</div>
5559
+ <div class="perf-card-label">Templates</div>
5465
5560
  </div>
5466
- <div class="dbg-bar"><div class="dbg-bar-fill render" style="width:${render / total * 100}%"></div></div>
5467
- <div class="dbg-row" style="margin-top:8px;padding-top:8px;border-top:1px solid rgba(255,255,255,0.1)">
5468
- <span class="dbg-label" style="font-weight:600">Total</span>
5469
- <span class="dbg-value" style="font-weight:600">${total.toFixed(2)}ms</span>
5561
+ <div class="perf-card">
5562
+ <div class="perf-card-value">${data.queries?.length || 0}</div>
5563
+ <div class="perf-card-label">Queries</div>
5470
5564
  </div>
5471
5565
  </div>
5472
- </div>`;
5473
- }
5474
- function generateTemplatesSection(data) {
5475
- if (data.templateChain.length === 0)
5476
- return "";
5477
- const templates = data.templateChain.map((t) => `
5478
- <div class="dbg-template">
5479
- ${icons.file}
5480
- <span class="dbg-template-name">${t.name}</span>
5481
- <span class="dbg-template-tag ${t.type}">${t.type}</span>
5566
+ <div class="perf-breakdown">
5567
+ <div class="perf-row">
5568
+ <span class="perf-row-label">Lexer</span>
5569
+ <div class="perf-row-bar"><div class="perf-row-fill lexer" style="width:${lexer / total * 100}%"></div></div>
5570
+ <span class="perf-row-value">${lexer.toFixed(2)}ms</span>
5571
+ </div>
5572
+ <div class="perf-row">
5573
+ <span class="perf-row-label">Parser</span>
5574
+ <div class="perf-row-bar"><div class="perf-row-fill parser" style="width:${parser / total * 100}%"></div></div>
5575
+ <span class="perf-row-value">${parser.toFixed(2)}ms</span>
5576
+ </div>
5577
+ <div class="perf-row">
5578
+ <span class="perf-row-label">Render</span>
5579
+ <div class="perf-row-bar"><div class="perf-row-fill render" style="width:${render / total * 100}%"></div></div>
5580
+ <span class="perf-row-value">${render.toFixed(2)}ms</span>
5482
5581
  </div>
5483
- `).join("");
5484
- return `
5485
- <div class="dbg-section">
5486
- <div class="dbg-section-header" onclick="this.parentElement.classList.toggle('open')">
5487
- <span class="dbg-section-title">${icons.template} Templates</span>
5488
- <span class="dbg-section-meta">${data.templateChain.length}</span>
5489
- <span class="dbg-chevron">${icons.chevron}</span>
5490
- </div>
5491
- <div class="dbg-section-content">
5492
- <div class="dbg-templates">${templates}</div>
5493
5582
  </div>
5494
5583
  </div>`;
5495
5584
  }
5496
- function generateContextSection(data) {
5585
+ function generateContextPane(data) {
5497
5586
  const keys = Object.keys(data.contextSnapshot);
5498
- if (keys.length === 0)
5499
- return "";
5500
- const items2 = keys.map((key) => renderContextValue(key, data.contextSnapshot[key])).join("");
5587
+ if (keys.length === 0) {
5588
+ return `<div class="devtools-pane" data-pane="context"><div class="empty-state">${icons.context}<span>No context variables</span></div></div>`;
5589
+ }
5590
+ const items2 = keys.map((key) => renderTreeItem(key, data.contextSnapshot[key])).join("");
5591
+ return `<div class="devtools-pane" data-pane="context"><div class="tree">${items2}</div></div>`;
5592
+ }
5593
+ function renderTreeItem(key, ctx) {
5594
+ const hasChildren = ctx.expandable && ctx.children && Object.keys(ctx.children).length > 0;
5595
+ const arrowHtml = hasChildren ? `<span class="tree-arrow">${icons.arrow}</span>` : '<span class="tree-arrow" style="visibility:hidden">${icons.arrow}</span>';
5596
+ const expandableClass = hasChildren ? "expandable" : "";
5597
+ const valueClass = getValueClass(ctx.type);
5598
+ let childrenHtml = "";
5599
+ if (hasChildren && ctx.children) {
5600
+ childrenHtml = `<div class="tree-children">${Object.entries(ctx.children).map(([k, v]) => renderTreeItem(k, v)).join("")}</div>`;
5601
+ }
5501
5602
  return `
5502
- <div class="dbg-section">
5503
- <div class="dbg-section-header" onclick="this.parentElement.classList.toggle('open')">
5504
- <span class="dbg-section-title">${icons.context} Context</span>
5505
- <span class="dbg-section-meta">${keys.length} vars</span>
5506
- <span class="dbg-chevron">${icons.chevron}</span>
5507
- </div>
5508
- <div class="dbg-section-content">
5509
- <div class="dbg-ctx-grid">${items2}</div>
5603
+ <div class="tree-item">
5604
+ <div class="tree-row ${expandableClass}">
5605
+ ${arrowHtml}
5606
+ <span class="tree-key">${escapeHtml(key)}</span>
5607
+ <span class="tree-colon">:</span>
5608
+ <span class="tree-value ${valueClass}">${escapeHtml(ctx.preview)}</span>
5609
+ <span class="tree-type">${ctx.type}</span>
5510
5610
  </div>
5611
+ ${childrenHtml}
5511
5612
  </div>`;
5512
5613
  }
5513
- function renderContextValue(key, ctx) {
5514
- const arrow = ctx.expandable ? `<svg class="dbg-ctx-arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 18l6-6-6-6"/></svg>` : "";
5515
- const expandableClass = ctx.expandable ? "expandable" : "";
5516
- const onClick = ctx.expandable ? `onclick="this.parentElement.classList.toggle('open')"` : "";
5517
- let children = "";
5518
- if (ctx.expandable && ctx.children) {
5519
- const childItems = Object.entries(ctx.children).map(([k, v]) => renderContextValue(k, v)).join("");
5520
- children = `<div class="dbg-ctx-children">${childItems}</div>`;
5521
- }
5522
- return `
5523
- <div class="dbg-ctx-item">
5524
- <div class="dbg-ctx-row ${expandableClass}" ${onClick}>
5525
- <div class="dbg-ctx-key">
5526
- ${arrow}
5527
- <span class="dbg-ctx-name">${escapeHtml(key)}</span>
5528
- <span class="dbg-ctx-type">${ctx.type}</span>
5529
- </div>
5530
- <span class="dbg-ctx-preview">${escapeHtml(ctx.preview)}</span>
5614
+ function getValueClass(type) {
5615
+ if (type === "string")
5616
+ return "string";
5617
+ if (type === "number" || type === "integer" || type === "float")
5618
+ return "number";
5619
+ if (type === "null" || type === "undefined")
5620
+ return "null";
5621
+ return "";
5622
+ }
5623
+ function generateTemplatesPane(data) {
5624
+ if (data.templateChain.length === 0) {
5625
+ return `<div class="devtools-pane" data-pane="templates"><div class="empty-state">${icons.template}<span>No templates loaded</span></div></div>`;
5626
+ }
5627
+ const items2 = data.templateChain.map((t) => `
5628
+ <div class="template-item">
5629
+ <span class="template-icon">${icons.template}</span>
5630
+ <span class="template-name">${escapeHtml(t.name)}</span>
5631
+ <span class="template-badge ${t.type}">${t.type}</span>
5531
5632
  </div>
5532
- ${children}
5533
- </div>`;
5633
+ `).join("");
5634
+ return `<div class="devtools-pane" data-pane="templates"><div class="template-list">${items2}</div></div>`;
5534
5635
  }
5535
- function generateFiltersSection(data) {
5636
+ function generateFiltersPane(data) {
5536
5637
  const filters = Array.from(data.filtersUsed.entries());
5537
- if (filters.length === 0)
5538
- return "";
5539
- const items2 = filters.map(([name, count]) => `<span class="dbg-filter">${name}<span class="dbg-filter-count">\xD7${count}</span></span>`).join("");
5540
- return `
5541
- <div class="dbg-section">
5542
- <div class="dbg-section-header" onclick="this.parentElement.classList.toggle('open')">
5543
- <span class="dbg-section-title">${icons.filter} Filters</span>
5544
- <span class="dbg-section-meta">${filters.length}</span>
5545
- <span class="dbg-chevron">${icons.chevron}</span>
5546
- </div>
5547
- <div class="dbg-section-content">
5548
- <div class="dbg-filters">${items2}</div>
5549
- </div>
5550
- </div>`;
5638
+ if (filters.length === 0) {
5639
+ return `<div class="devtools-pane" data-pane="filters"><div class="empty-state">${icons.filter}<span>No filters used</span></div></div>`;
5640
+ }
5641
+ const items2 = filters.map(([name, count]) => `
5642
+ <span class="filter-chip">${escapeHtml(name)}<span class="filter-count">\xD7${count}</span></span>
5643
+ `).join("");
5644
+ return `<div class="devtools-pane" data-pane="filters"><div class="filter-grid">${items2}</div></div>`;
5551
5645
  }
5552
- function generateCacheSection(data) {
5553
- const total = data.cacheHits + data.cacheMisses;
5554
- if (total === 0)
5555
- return "";
5556
- return `
5557
- <div class="dbg-section">
5558
- <div class="dbg-section-header" onclick="this.parentElement.classList.toggle('open')">
5559
- <span class="dbg-section-title">${icons.cache} Cache</span>
5560
- <span class="dbg-section-meta">${(data.cacheHits / total * 100).toFixed(0)}% hit</span>
5561
- <span class="dbg-chevron">${icons.chevron}</span>
5562
- </div>
5563
- <div class="dbg-section-content">
5564
- <div class="dbg-cache">
5565
- <div class="dbg-cache-stat">
5566
- <div class="dbg-cache-num hit">${data.cacheHits}</div>
5567
- <div class="dbg-cache-label">Cache Hits</div>
5646
+ function generateQueriesPane(data) {
5647
+ if (!data.queries || data.queries.length === 0) {
5648
+ return `<div class="devtools-pane" data-pane="queries"><div class="empty-state">${icons.database}<span>No queries recorded</span></div></div>`;
5649
+ }
5650
+ const stats = data.queryStats;
5651
+ const statsHtml = `
5652
+ <div class="queries-stats">
5653
+ <div class="queries-stat">
5654
+ <div class="queries-stat-value">${stats.count}</div>
5655
+ <div class="queries-stat-label">Queries</div>
5656
+ </div>
5657
+ <div class="queries-stat">
5658
+ <div class="queries-stat-value">${stats.totalDuration.toFixed(1)}ms</div>
5659
+ <div class="queries-stat-label">Total</div>
5660
+ </div>
5661
+ <div class="queries-stat">
5662
+ <div class="queries-stat-value ${stats.slowCount > 0 ? "warn" : ""}">${stats.slowCount}</div>
5663
+ <div class="queries-stat-label">Slow</div>
5568
5664
  </div>
5569
- <div class="dbg-cache-stat">
5570
- <div class="dbg-cache-num miss">${data.cacheMisses}</div>
5571
- <div class="dbg-cache-label">Cache Misses</div>
5665
+ <div class="queries-stat">
5666
+ <div class="queries-stat-value ${stats.n1Count > 0 ? "error" : ""}">${stats.n1Count}</div>
5667
+ <div class="queries-stat-label">N+1</div>
5572
5668
  </div>
5669
+ </div>`;
5670
+ const queries = data.queries.map((q) => {
5671
+ const isSlow = q.duration > 100;
5672
+ const classes = ["query-item", q.isN1 ? "n1" : "", isSlow ? "slow" : ""].filter(Boolean).join(" ");
5673
+ const badge = q.isN1 ? '<span class="query-badge n1">N+1</span>' : "";
5674
+ const source = q.source ? `<span class="query-source">${escapeHtml(q.source)}</span>` : "";
5675
+ const rows = q.rows !== undefined ? `<span class="query-rows">${q.rows} rows</span>` : "";
5676
+ return `
5677
+ <div class="${classes}">
5678
+ <div class="query-header">
5679
+ <span class="query-sql" title="${escapeHtml(q.sql)}">${escapeHtml(q.sql)}</span>
5680
+ <div class="query-meta">
5681
+ ${badge}${source}${rows}
5682
+ <span class="query-time ${isSlow ? "slow" : ""}">${q.duration.toFixed(1)}ms</span>
5683
+ </div>
5684
+ </div>
5685
+ </div>`;
5686
+ }).join("");
5687
+ return `<div class="devtools-pane" data-pane="queries">${statsHtml}<div class="query-list">${queries}</div></div>`;
5688
+ }
5689
+ function generateCachePane(data) {
5690
+ const total = data.cacheHits + data.cacheMisses;
5691
+ if (total === 0) {
5692
+ return `<div class="devtools-pane" data-pane="cache"><div class="empty-state">${icons.cache}<span>No cache activity</span></div></div>`;
5693
+ }
5694
+ const hitRate = (data.cacheHits / total * 100).toFixed(0);
5695
+ return `
5696
+ <div class="devtools-pane" data-pane="cache">
5697
+ <div class="cache-stats">
5698
+ <div class="cache-stat">
5699
+ <div class="cache-value hit">${data.cacheHits}</div>
5700
+ <div class="cache-label">Cache Hits</div>
5701
+ </div>
5702
+ <div class="cache-stat">
5703
+ <div class="cache-value miss">${data.cacheMisses}</div>
5704
+ <div class="cache-label">Cache Misses</div>
5705
+ </div>
5706
+ <div class="cache-stat">
5707
+ <div class="cache-value">${hitRate}%</div>
5708
+ <div class="cache-label">Hit Rate</div>
5573
5709
  </div>
5574
5710
  </div>
5575
5711
  </div>`;
5576
5712
  }
5577
- function generateWarningsSection(data) {
5578
- if (data.warnings.length === 0)
5579
- return "";
5713
+ function generateWarningsPane(data) {
5714
+ if (data.warnings.length === 0) {
5715
+ return `<div class="devtools-pane" data-pane="warnings"><div class="empty-state">${icons.warning}<span>No warnings</span></div></div>`;
5716
+ }
5580
5717
  const items2 = data.warnings.map((w) => `
5581
- <div class="dbg-warning">
5582
- ${icons.warning}
5583
- <span class="dbg-warning-text">${escapeHtml(w)}</span>
5718
+ <div class="warning-item">
5719
+ <span class="warning-icon">${icons.warning}</span>
5720
+ <span class="warning-text">${escapeHtml(w)}</span>
5584
5721
  </div>
5585
5722
  `).join("");
5586
- return `
5587
- <div class="dbg-section open">
5588
- <div class="dbg-section-header" onclick="this.parentElement.classList.toggle('open')">
5589
- <span class="dbg-section-title">${icons.warning} Warnings</span>
5590
- <span class="dbg-section-meta" style="color:#eab308">${data.warnings.length}</span>
5591
- <span class="dbg-chevron">${icons.chevron}</span>
5592
- </div>
5593
- <div class="dbg-section-content">
5594
- <div class="dbg-warnings">${items2}</div>
5595
- </div>
5596
- </div>`;
5723
+ return `<div class="devtools-pane" data-pane="warnings"><div class="warning-list">${items2}</div></div>`;
5597
5724
  }
5598
- function generateScript(id) {
5725
+ function generateScript(id, data, opts) {
5599
5726
  return `
5600
- (function(){
5601
- var panel = document.getElementById('${id}');
5602
- if (!panel) return;
5603
- var header = panel.querySelector('.dbg-header');
5604
- if (!header) return;
5605
- var isDrag = false, startX, startY, startL, startT;
5606
- header.style.cursor = 'grab';
5607
- header.onmousedown = function(e) {
5608
- if (e.target.closest('.dbg-close')) return;
5609
- isDrag = true;
5610
- header.style.cursor = 'grabbing';
5611
- startX = e.clientX;
5612
- startY = e.clientY;
5613
- var r = panel.getBoundingClientRect();
5614
- startL = r.left;
5615
- startT = r.top;
5616
- panel.style.right = 'auto';
5617
- panel.style.bottom = 'auto';
5618
- panel.style.left = startL + 'px';
5619
- panel.style.top = startT + 'px';
5620
- };
5621
- document.onmousemove = function(e) {
5622
- if (!isDrag) return;
5623
- panel.style.left = (startL + e.clientX - startX) + 'px';
5624
- panel.style.top = (startT + e.clientY - startY) + 'px';
5625
- };
5626
- document.onmouseup = function() {
5627
- isDrag = false;
5628
- header.style.cursor = 'grab';
5629
- };
5727
+ (function() {
5728
+ var root = document.getElementById('${id}');
5729
+ if (!root) return;
5730
+
5731
+ // Tab switching
5732
+ root.querySelectorAll('.devtools-tab').forEach(function(tab) {
5733
+ tab.addEventListener('click', function() {
5734
+ root.querySelectorAll('.devtools-tab').forEach(function(t) { t.classList.remove('active'); });
5735
+ root.querySelectorAll('.devtools-pane').forEach(function(p) { p.classList.remove('active'); });
5736
+ tab.classList.add('active');
5737
+ var pane = root.querySelector('[data-pane="' + tab.dataset.tab + '"]');
5738
+ if (pane) pane.classList.add('active');
5739
+ });
5740
+ });
5741
+
5742
+ // Tree expand/collapse
5743
+ root.querySelectorAll('.tree-row.expandable').forEach(function(row) {
5744
+ row.addEventListener('click', function() {
5745
+ row.parentElement.classList.toggle('open');
5746
+ });
5747
+ });
5748
+
5749
+ // Dock position switching
5750
+ root.querySelectorAll('[data-dock]').forEach(function(btn) {
5751
+ btn.addEventListener('click', function() {
5752
+ root.dataset.position = btn.dataset.dock;
5753
+ });
5754
+ });
5755
+
5756
+ // Resize functionality
5757
+ var resize = root.querySelector('.devtools-resize');
5758
+ var panel = root.querySelector('.devtools-panel');
5759
+ if (resize && panel) {
5760
+ var isResizing = false;
5761
+ var startY, startX, startHeight, startWidth;
5762
+
5763
+ resize.addEventListener('mousedown', function(e) {
5764
+ isResizing = true;
5765
+ startY = e.clientY;
5766
+ startX = e.clientX;
5767
+ startHeight = panel.offsetHeight;
5768
+ startWidth = panel.offsetWidth;
5769
+ document.body.style.cursor = root.dataset.position === 'right' ? 'ew-resize' : 'ns-resize';
5770
+ e.preventDefault();
5771
+ });
5772
+
5773
+ document.addEventListener('mousemove', function(e) {
5774
+ if (!isResizing) return;
5775
+ if (root.dataset.position === 'bottom') {
5776
+ var newHeight = startHeight - (e.clientY - startY);
5777
+ if (newHeight > 100 && newHeight < window.innerHeight * 0.8) {
5778
+ panel.style.height = newHeight + 'px';
5779
+ }
5780
+ } else if (root.dataset.position === 'right') {
5781
+ var newWidth = startWidth - (e.clientX - startX);
5782
+ if (newWidth > 200 && newWidth < window.innerWidth * 0.6) {
5783
+ panel.style.width = newWidth + 'px';
5784
+ }
5785
+ }
5786
+ });
5787
+
5788
+ document.addEventListener('mouseup', function() {
5789
+ isResizing = false;
5790
+ document.body.style.cursor = '';
5791
+ });
5792
+ }
5630
5793
  })();`;
5631
5794
  }
5632
5795
  function escapeHtml(str) {
5633
- return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
5796
+ return String(str).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
5634
5797
  }
5635
5798
 
5636
5799
  // src/debug/index.ts