@flight-framework/devtools 0.0.4 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/panel.js CHANGED
@@ -1,103 +1,214 @@
1
1
  /**
2
2
  * @flight-framework/devtools/panel - DevTools Panel UI
3
3
  *
4
- * React component for the DevTools floating panel.
5
- * Automatically injected into pages during development.
4
+ * Enhanced floating panel with collapsible sections, real-time updates,
5
+ * and improved data visualization.
6
6
  */
7
- import { getDevTools } from './index.js';
7
+ import { getDevTools, formatBytes, formatDuration, formatRelativeTime } from './index.js';
8
8
  // ============================================================================
9
- // Panel Component (Plain JS for framework-agnostic use)
9
+ // Panel Component
10
10
  // ============================================================================
11
- /**
12
- * Inject the DevTools panel into the page
13
- * Call this in your app's entry point during development
14
- */
15
11
  export function injectDevToolsPanel() {
16
12
  if (typeof document === 'undefined')
17
13
  return;
18
14
  const devTools = getDevTools();
19
15
  if (!devTools)
20
16
  return;
17
+ // Remove existing panel if any
18
+ const existing = document.getElementById('flight-devtools');
19
+ if (existing)
20
+ existing.remove();
21
21
  // Create container
22
22
  const container = document.createElement('div');
23
23
  container.id = 'flight-devtools';
24
24
  document.body.appendChild(container);
25
25
  // Inject styles
26
26
  const styles = document.createElement('style');
27
+ styles.id = 'flight-devtools-styles';
27
28
  styles.textContent = getDevToolsStyles();
28
29
  document.head.appendChild(styles);
29
- // Initial render
30
- let isOpen = false;
31
- let currentTab = 'routes';
30
+ // Panel state
31
+ const panelState = {
32
+ isOpen: false,
33
+ currentTab: 'routes',
34
+ routeFilter: '',
35
+ consoleFilter: 'all',
36
+ expandedItems: new Set(),
37
+ };
32
38
  function render(state) {
33
39
  const options = devTools.getOptions();
34
40
  container.innerHTML = `
35
- <div class="flight-devtools ${options.position} ${isOpen ? 'open' : ''}">
36
- <button class="flight-devtools-toggle" onclick="window.__flightToggleDevTools()">
37
- ${isOpen ? 'X' : 'Flight'}
41
+ <div class="flight-devtools ${options.position} ${panelState.isOpen ? 'open' : ''}">
42
+ <button class="flight-devtools-toggle" data-action="toggle">
43
+ ${panelState.isOpen ? '' : ''}
38
44
  </button>
39
- ${isOpen ? renderPanel(state, currentTab) : ''}
45
+ ${panelState.isOpen ? renderPanel(state) : renderBadge(state)}
40
46
  </div>
41
47
  `;
48
+ // Attach event listeners
49
+ attachEventListeners(state);
42
50
  }
43
- function renderPanel(state, tab) {
51
+ function renderBadge(state) {
52
+ const errorCount = state.errors.length;
53
+ if (errorCount === 0)
54
+ return '';
55
+ return `<span class="flight-devtools-badge">${errorCount}</span>`;
56
+ }
57
+ function renderPanel(state) {
58
+ const tabs = [
59
+ { id: 'routes', label: 'Routes', count: state.routes.length },
60
+ { id: 'requests', label: 'Requests', count: state.requests.length },
61
+ { id: 'adapters', label: 'Adapters', count: state.adapters.length },
62
+ { id: 'performance', label: 'Perf', count: null },
63
+ { id: 'console', label: 'Console', count: state.console.length + state.errors.length },
64
+ ];
44
65
  return `
45
66
  <div class="flight-devtools-panel">
46
67
  <div class="flight-devtools-header">
47
- <span>Flight DevTools</span>
68
+ <span class="flight-devtools-logo">✈ Flight DevTools</span>
69
+ <span class="flight-devtools-version">v0.0.5</span>
48
70
  </div>
49
71
  <div class="flight-devtools-tabs">
50
- <button class="${tab === 'routes' ? 'active' : ''}" onclick="window.__flightSetTab('routes')">
51
- Routes (${state.routes.length})
52
- </button>
53
- <button class="${tab === 'requests' ? 'active' : ''}" onclick="window.__flightSetTab('requests')">
54
- Requests (${state.requests.length})
55
- </button>
56
- <button class="${tab === 'adapters' ? 'active' : ''}" onclick="window.__flightSetTab('adapters')">
57
- Adapters (${state.adapters.length})
58
- </button>
72
+ ${tabs.map(tab => `
73
+ <button
74
+ class="${panelState.currentTab === tab.id ? 'active' : ''}"
75
+ data-action="tab"
76
+ data-tab="${tab.id}"
77
+ >
78
+ ${tab.label}${tab.count !== null ? ` <span class="count">${tab.count}</span>` : ''}
79
+ </button>
80
+ `).join('')}
59
81
  </div>
60
82
  <div class="flight-devtools-content">
61
- ${tab === 'routes' ? renderRoutes(state.routes) : ''}
62
- ${tab === 'requests' ? renderRequests(state.requests) : ''}
63
- ${tab === 'adapters' ? renderAdapters(state) : ''}
83
+ ${renderTabContent(state)}
64
84
  </div>
65
85
  </div>
66
86
  `;
67
87
  }
88
+ function renderTabContent(state) {
89
+ switch (panelState.currentTab) {
90
+ case 'routes': return renderRoutes(state.routes);
91
+ case 'requests': return renderRequests(state.requests);
92
+ case 'adapters': return renderAdapters(state);
93
+ case 'performance': return renderPerformance(state);
94
+ case 'console': return renderConsole(state);
95
+ default: return '';
96
+ }
97
+ }
68
98
  function renderRoutes(routes) {
69
99
  if (routes.length === 0) {
70
100
  return '<div class="flight-devtools-empty">No routes registered</div>';
71
101
  }
72
- const dots = (level) => '.'.repeat(level);
102
+ const filtered = panelState.routeFilter
103
+ ? routes.filter(r => r.path.includes(panelState.routeFilter))
104
+ : routes;
105
+ const pageRoutes = filtered.filter(r => r.type === 'page');
106
+ const apiRoutes = filtered.filter(r => r.type === 'api');
73
107
  return `
108
+ <div class="flight-devtools-toolbar">
109
+ <input
110
+ type="text"
111
+ placeholder="Filter routes..."
112
+ value="${panelState.routeFilter}"
113
+ data-action="filter-routes"
114
+ class="flight-devtools-search"
115
+ />
116
+ </div>
74
117
  <div class="flight-devtools-list">
75
- ${routes.map(route => `
76
- <div class="flight-devtools-item">
77
- <span class="method ${route.method.toLowerCase()}">${route.method}</span>
78
- <span class="path">${route.path}</span>
79
- <span class="type">${route.type}</span>
80
- ${route.slot ? `<span class="slot">@${route.slot}</span>` : ''}
81
- ${route.interceptInfo ? `<span class="intercept">(${dots(route.interceptInfo.level)})</span>` : ''}
118
+ ${pageRoutes.length > 0 ? `
119
+ <div class="flight-devtools-section">
120
+ <div class="flight-devtools-section-title">Pages (${pageRoutes.length})</div>
121
+ ${pageRoutes.map(route => renderRouteItem(route)).join('')}
82
122
  </div>
83
- `).join('')}
123
+ ` : ''}
124
+ ${apiRoutes.length > 0 ? `
125
+ <div class="flight-devtools-section">
126
+ <div class="flight-devtools-section-title">API Routes (${apiRoutes.length})</div>
127
+ ${apiRoutes.map(route => renderRouteItem(route)).join('')}
128
+ </div>
129
+ ` : ''}
130
+ </div>
131
+ `;
132
+ }
133
+ function renderRouteItem(route) {
134
+ const isExpanded = panelState.expandedItems.has(route.path);
135
+ const hasParams = route.path.includes(':') || route.path.includes('[');
136
+ return `
137
+ <div class="flight-devtools-item ${isExpanded ? 'expanded' : ''}" data-action="toggle-item" data-id="${route.path}">
138
+ <div class="flight-devtools-item-header">
139
+ <span class="method ${route.method.toLowerCase()}">${route.method}</span>
140
+ <span class="path">${route.path}</span>
141
+ ${hasParams ? '<span class="badge dynamic">Dynamic</span>' : ''}
142
+ ${route.slot ? `<span class="badge slot">@${route.slot}</span>` : ''}
143
+ <span class="expand-icon">${isExpanded ? '▼' : '▶'}</span>
144
+ </div>
145
+ ${isExpanded ? `
146
+ <div class="flight-devtools-item-details">
147
+ <div class="detail-row">
148
+ <span class="detail-label">File:</span>
149
+ <span class="detail-value mono">${route.filePath}</span>
150
+ </div>
151
+ ${route.params ? `
152
+ <div class="detail-row">
153
+ <span class="detail-label">Params:</span>
154
+ <span class="detail-value">${route.params.join(', ')}</span>
155
+ </div>
156
+ ` : ''}
157
+ </div>
158
+ ` : ''}
84
159
  </div>
85
160
  `;
86
161
  }
87
162
  function renderRequests(requests) {
88
163
  if (requests.length === 0) {
89
- return '<div class="flight-devtools-empty">No requests yet</div>';
164
+ return '<div class="flight-devtools-empty">No requests captured yet<br/><small>Make some fetch requests to see them here</small></div>';
90
165
  }
91
166
  return `
167
+ <div class="flight-devtools-toolbar">
168
+ <span class="flight-devtools-info">${requests.length} requests</span>
169
+ <button data-action="clear-requests" class="flight-devtools-btn-small">Clear</button>
170
+ </div>
92
171
  <div class="flight-devtools-list">
93
- ${requests.slice(0, 20).map(req => `
94
- <div class="flight-devtools-item">
95
- <span class="method ${req.method.toLowerCase()}">${req.method}</span>
96
- <span class="path">${req.path}</span>
97
- <span class="status status-${Math.floor(req.status / 100)}xx">${req.status}</span>
98
- <span class="duration">${req.duration}ms</span>
172
+ ${requests.slice(0, 50).map(req => renderRequestItem(req)).join('')}
173
+ </div>
174
+ `;
175
+ }
176
+ function renderRequestItem(req) {
177
+ const isExpanded = panelState.expandedItems.has(req.id);
178
+ const statusClass = `status-${Math.floor(req.status / 100)}xx`;
179
+ return `
180
+ <div class="flight-devtools-item ${isExpanded ? 'expanded' : ''}" data-action="toggle-item" data-id="${req.id}">
181
+ <div class="flight-devtools-item-header">
182
+ <span class="method ${req.method.toLowerCase()}">${req.method}</span>
183
+ <span class="path">${req.path}</span>
184
+ <span class="status ${statusClass}">${req.status}</span>
185
+ <span class="duration">${formatDuration(req.duration)}</span>
186
+ <span class="time">${formatRelativeTime(req.timestamp)}</span>
187
+ </div>
188
+ ${isExpanded ? `
189
+ <div class="flight-devtools-item-details">
190
+ <div class="detail-row">
191
+ <span class="detail-label">Duration:</span>
192
+ <span class="detail-value">${formatDuration(req.duration)}</span>
193
+ </div>
194
+ ${req.size !== undefined ? `
195
+ <div class="detail-row">
196
+ <span class="detail-label">Size:</span>
197
+ <span class="detail-value">${formatBytes(req.size)}</span>
198
+ </div>
199
+ ` : ''}
200
+ ${req.type ? `
201
+ <div class="detail-row">
202
+ <span class="detail-label">Type:</span>
203
+ <span class="detail-value">${req.type}</span>
204
+ </div>
205
+ ` : ''}
206
+ <div class="detail-row">
207
+ <span class="detail-label">Time:</span>
208
+ <span class="detail-value">${new Date(req.timestamp).toLocaleTimeString()}</span>
209
+ </div>
99
210
  </div>
100
- `).join('')}
211
+ ` : ''}
101
212
  </div>
102
213
  `;
103
214
  }
@@ -109,23 +220,143 @@ export function injectDevToolsPanel() {
109
220
  <div class="flight-devtools-list">
110
221
  ${state.adapters.map(adapter => `
111
222
  <div class="flight-devtools-item">
112
- <span class="adapter-type">${adapter.type}</span>
113
- <span class="adapter-name">${adapter.name}</span>
114
- <span class="adapter-status status-${adapter.status}">${adapter.status}</span>
223
+ <div class="flight-devtools-item-header">
224
+ <span class="adapter-icon">${getAdapterIcon(adapter.type)}</span>
225
+ <span class="adapter-name">${adapter.name}</span>
226
+ <span class="adapter-type">${adapter.type}</span>
227
+ <span class="adapter-status status-${adapter.status}">${adapter.status}</span>
228
+ </div>
115
229
  </div>
116
230
  `).join('')}
117
231
  </div>
118
232
  `;
119
233
  }
120
- // Global functions for onclick handlers
121
- window.__flightToggleDevTools = () => {
122
- isOpen = !isOpen;
123
- render(devTools.getState());
124
- };
125
- window.__flightSetTab = (tab) => {
126
- currentTab = tab;
127
- render(devTools.getState());
128
- };
234
+ function getAdapterIcon(type) {
235
+ const icons = {
236
+ db: '🗄️',
237
+ auth: '🔐',
238
+ storage: '📦',
239
+ email: '✉️',
240
+ payments: '💳',
241
+ realtime: '⚡',
242
+ other: '🔌',
243
+ };
244
+ return icons[type] || '🔌';
245
+ }
246
+ function renderPerformance(state) {
247
+ const perf = state.performance;
248
+ const hasMetrics = Object.keys(perf).some(k => perf[k] !== undefined);
249
+ if (!hasMetrics) {
250
+ return '<div class="flight-devtools-empty">Performance metrics will appear after page load</div>';
251
+ }
252
+ const metrics = [
253
+ { label: 'LCP', value: perf.lcp, unit: 'ms', good: 2500, desc: 'Largest Contentful Paint' },
254
+ { label: 'FID', value: perf.fid, unit: 'ms', good: 100, desc: 'First Input Delay' },
255
+ { label: 'CLS', value: perf.cls, unit: '', good: 0.1, desc: 'Cumulative Layout Shift' },
256
+ { label: 'FCP', value: perf.fcp, unit: 'ms', good: 1800, desc: 'First Contentful Paint' },
257
+ { label: 'TTFB', value: perf.ttfb, unit: 'ms', good: 800, desc: 'Time to First Byte' },
258
+ ].filter(m => m.value !== undefined);
259
+ return `
260
+ <div class="flight-devtools-list">
261
+ <div class="flight-devtools-section-title">Core Web Vitals</div>
262
+ <div class="flight-devtools-metrics">
263
+ ${metrics.map(m => {
264
+ const status = m.value <= m.good ? 'good' : m.value <= m.good * 2 ? 'needs-improvement' : 'poor';
265
+ return `
266
+ <div class="flight-devtools-metric ${status}">
267
+ <div class="metric-label">${m.label}</div>
268
+ <div class="metric-value">${m.unit ? formatDuration(m.value) : m.value.toFixed(3)}</div>
269
+ <div class="metric-desc">${m.desc}</div>
270
+ </div>
271
+ `;
272
+ }).join('')}
273
+ </div>
274
+ </div>
275
+ `;
276
+ }
277
+ function renderConsole(state) {
278
+ const entries = [
279
+ ...state.errors.map(e => ({ id: e.id, level: 'error', message: e.message, timestamp: e.timestamp, stack: e.stack })),
280
+ ...state.console.map(c => ({ id: c.id, level: c.level, message: c.message, timestamp: c.timestamp, stack: c.stack })),
281
+ ].sort((a, b) => b.timestamp - a.timestamp);
282
+ const filtered = panelState.consoleFilter === 'all'
283
+ ? entries
284
+ : entries.filter(e => e.level === panelState.consoleFilter);
285
+ return `
286
+ <div class="flight-devtools-toolbar">
287
+ <div class="flight-devtools-filter-group">
288
+ ${['all', 'log', 'warn', 'error'].map(f => `
289
+ <button
290
+ class="flight-devtools-filter-btn ${panelState.consoleFilter === f ? 'active' : ''}"
291
+ data-action="filter-console"
292
+ data-filter="${f}"
293
+ >${f}</button>
294
+ `).join('')}
295
+ </div>
296
+ <button data-action="clear-console" class="flight-devtools-btn-small">Clear</button>
297
+ </div>
298
+ <div class="flight-devtools-list console-list">
299
+ ${filtered.length === 0
300
+ ? '<div class="flight-devtools-empty">No console entries</div>'
301
+ : filtered.slice(0, 100).map(entry => `
302
+ <div class="flight-devtools-console-item level-${entry.level}">
303
+ <span class="console-time">${formatRelativeTime(entry.timestamp)}</span>
304
+ <span class="console-level">${entry.level}</span>
305
+ <span class="console-message">${escapeHtml(entry.message)}</span>
306
+ </div>
307
+ `).join('')}
308
+ </div>
309
+ `;
310
+ }
311
+ function escapeHtml(text) {
312
+ const div = document.createElement('div');
313
+ div.textContent = text;
314
+ return div.innerHTML;
315
+ }
316
+ function attachEventListeners(state) {
317
+ container.querySelectorAll('[data-action]').forEach(el => {
318
+ const action = el.getAttribute('data-action');
319
+ el.addEventListener('click', (e) => {
320
+ e.stopPropagation();
321
+ switch (action) {
322
+ case 'toggle':
323
+ panelState.isOpen = !panelState.isOpen;
324
+ break;
325
+ case 'tab':
326
+ panelState.currentTab = el.getAttribute('data-tab');
327
+ break;
328
+ case 'toggle-item':
329
+ const id = el.getAttribute('data-id');
330
+ if (panelState.expandedItems.has(id)) {
331
+ panelState.expandedItems.delete(id);
332
+ }
333
+ else {
334
+ panelState.expandedItems.add(id);
335
+ }
336
+ break;
337
+ case 'clear-requests':
338
+ devTools.clearRequests();
339
+ break;
340
+ case 'clear-console':
341
+ devTools.clearConsole();
342
+ devTools.clearErrors();
343
+ break;
344
+ case 'filter-console':
345
+ panelState.consoleFilter = el.getAttribute('data-filter');
346
+ break;
347
+ }
348
+ render(devTools.getState());
349
+ });
350
+ });
351
+ // Route filter input
352
+ const filterInput = container.querySelector('[data-action="filter-routes"]');
353
+ if (filterInput) {
354
+ filterInput.addEventListener('input', (e) => {
355
+ panelState.routeFilter = e.target.value;
356
+ render(devTools.getState());
357
+ });
358
+ }
359
+ }
129
360
  // Subscribe to state changes
130
361
  devTools.subscribe(render);
131
362
  }
@@ -135,134 +366,450 @@ export function injectDevToolsPanel() {
135
366
  function getDevToolsStyles() {
136
367
  return `
137
368
  #flight-devtools {
138
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
369
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
139
370
  font-size: 12px;
371
+ line-height: 1.4;
372
+ --green: #22c55e;
373
+ --green-dark: #16a34a;
374
+ --bg: #0f0f0f;
375
+ --bg-2: #1a1a1a;
376
+ --bg-3: #252525;
377
+ --border: #333;
378
+ --text: #e5e5e5;
379
+ --text-muted: #888;
140
380
  }
141
381
 
142
382
  .flight-devtools {
143
383
  position: fixed;
144
- z-index: 99999;
145
- }
146
-
147
- .flight-devtools.bottom-right {
148
- bottom: 16px;
149
- right: 16px;
384
+ z-index: 999999;
150
385
  }
151
386
 
152
- .flight-devtools.bottom-left {
153
- bottom: 16px;
154
- left: 16px;
155
- }
387
+ .flight-devtools.bottom-right { bottom: 16px; right: 16px; }
388
+ .flight-devtools.bottom-left { bottom: 16px; left: 16px; }
389
+ .flight-devtools.top-right { top: 16px; right: 16px; }
390
+ .flight-devtools.top-left { top: 16px; left: 16px; }
156
391
 
157
392
  .flight-devtools-toggle {
158
393
  width: 48px;
159
394
  height: 48px;
160
395
  border-radius: 50%;
161
396
  border: none;
162
- background: #32CD32;
397
+ background: linear-gradient(135deg, var(--green) 0%, var(--green-dark) 100%);
163
398
  color: white;
164
- font-weight: bold;
399
+ font-size: 20px;
165
400
  cursor: pointer;
166
- box-shadow: 0 4px 12px rgba(0,0,0,0.3);
401
+ box-shadow: 0 4px 20px rgba(34, 197, 94, 0.4);
402
+ transition: transform 0.2s, box-shadow 0.2s;
403
+ }
404
+
405
+ .flight-devtools-toggle:hover {
406
+ transform: scale(1.05);
407
+ box-shadow: 0 6px 24px rgba(34, 197, 94, 0.5);
167
408
  }
168
409
 
169
410
  .flight-devtools.open .flight-devtools-toggle {
170
- border-radius: 50%;
171
411
  position: absolute;
172
- top: 8px;
173
- right: 8px;
174
- width: 32px;
175
- height: 32px;
412
+ top: 12px;
413
+ right: 12px;
414
+ width: 28px;
415
+ height: 28px;
176
416
  font-size: 14px;
417
+ background: var(--bg-3);
418
+ box-shadow: none;
419
+ }
420
+
421
+ .flight-devtools-badge {
422
+ position: absolute;
423
+ top: -4px;
424
+ right: -4px;
425
+ width: 18px;
426
+ height: 18px;
427
+ border-radius: 50%;
428
+ background: #ef4444;
429
+ color: white;
430
+ font-size: 10px;
431
+ font-weight: 600;
432
+ display: flex;
433
+ align-items: center;
434
+ justify-content: center;
177
435
  }
178
436
 
179
437
  .flight-devtools-panel {
180
438
  position: absolute;
181
439
  bottom: 60px;
182
440
  right: 0;
183
- width: 400px;
184
- max-height: 500px;
185
- background: #1a1a1a;
441
+ width: 480px;
442
+ max-height: 600px;
443
+ background: var(--bg);
444
+ border: 1px solid var(--border);
186
445
  border-radius: 12px;
187
446
  overflow: hidden;
188
- box-shadow: 0 8px 32px rgba(0,0,0,0.4);
447
+ box-shadow: 0 8px 40px rgba(0,0,0,0.5);
448
+ display: flex;
449
+ flex-direction: column;
189
450
  }
190
451
 
191
452
  .flight-devtools-header {
192
- padding: 12px 16px;
193
- background: #32CD32;
453
+ padding: 12px 48px 12px 16px;
454
+ background: linear-gradient(135deg, var(--green) 0%, var(--green-dark) 100%);
194
455
  color: white;
456
+ display: flex;
457
+ align-items: center;
458
+ justify-content: space-between;
459
+ }
460
+
461
+ .flight-devtools-logo {
195
462
  font-weight: 600;
463
+ font-size: 14px;
464
+ }
465
+
466
+ .flight-devtools-version {
467
+ font-size: 10px;
468
+ opacity: 0.8;
196
469
  }
197
470
 
198
471
  .flight-devtools-tabs {
199
472
  display: flex;
200
- border-bottom: 1px solid #333;
473
+ background: var(--bg-2);
474
+ border-bottom: 1px solid var(--border);
201
475
  }
202
476
 
203
477
  .flight-devtools-tabs button {
204
478
  flex: 1;
205
- padding: 8px;
479
+ padding: 10px 8px;
206
480
  border: none;
207
481
  background: transparent;
208
- color: #888;
482
+ color: var(--text-muted);
483
+ font-size: 11px;
209
484
  cursor: pointer;
485
+ border-bottom: 2px solid transparent;
486
+ transition: all 0.2s;
487
+ }
488
+
489
+ .flight-devtools-tabs button:hover {
490
+ color: var(--text);
491
+ background: var(--bg-3);
210
492
  }
211
493
 
212
494
  .flight-devtools-tabs button.active {
213
- color: #32CD32;
214
- border-bottom: 2px solid #32CD32;
495
+ color: var(--green);
496
+ border-bottom-color: var(--green);
497
+ }
498
+
499
+ .flight-devtools-tabs .count {
500
+ display: inline-block;
501
+ min-width: 18px;
502
+ padding: 1px 5px;
503
+ margin-left: 4px;
504
+ background: var(--bg-3);
505
+ border-radius: 10px;
506
+ font-size: 10px;
215
507
  }
216
508
 
217
509
  .flight-devtools-content {
218
- max-height: 350px;
510
+ flex: 1;
219
511
  overflow-y: auto;
512
+ max-height: 480px;
513
+ }
514
+
515
+ .flight-devtools-toolbar {
516
+ display: flex;
517
+ align-items: center;
518
+ justify-content: space-between;
519
+ padding: 8px 12px;
520
+ background: var(--bg-2);
521
+ border-bottom: 1px solid var(--border);
522
+ }
523
+
524
+ .flight-devtools-search {
525
+ flex: 1;
526
+ padding: 6px 10px;
527
+ background: var(--bg-3);
528
+ border: 1px solid var(--border);
529
+ border-radius: 6px;
530
+ color: var(--text);
531
+ font-size: 12px;
532
+ }
533
+
534
+ .flight-devtools-search:focus {
535
+ outline: none;
536
+ border-color: var(--green);
537
+ }
538
+
539
+ .flight-devtools-btn-small {
540
+ padding: 4px 10px;
541
+ background: var(--bg-3);
542
+ border: 1px solid var(--border);
543
+ border-radius: 4px;
544
+ color: var(--text-muted);
545
+ font-size: 11px;
546
+ cursor: pointer;
547
+ }
548
+
549
+ .flight-devtools-btn-small:hover {
550
+ background: var(--border);
551
+ color: var(--text);
552
+ }
553
+
554
+ .flight-devtools-info {
555
+ color: var(--text-muted);
556
+ font-size: 11px;
220
557
  }
221
558
 
222
559
  .flight-devtools-list {
223
560
  padding: 8px;
224
561
  }
225
562
 
563
+ .flight-devtools-section {
564
+ margin-bottom: 16px;
565
+ }
566
+
567
+ .flight-devtools-section-title {
568
+ padding: 8px 8px 4px;
569
+ color: var(--text-muted);
570
+ font-size: 10px;
571
+ font-weight: 600;
572
+ text-transform: uppercase;
573
+ letter-spacing: 0.5px;
574
+ }
575
+
226
576
  .flight-devtools-item {
577
+ padding: 8px 10px;
578
+ border-radius: 6px;
579
+ cursor: pointer;
580
+ transition: background 0.15s;
581
+ }
582
+
583
+ .flight-devtools-item:hover {
584
+ background: var(--bg-2);
585
+ }
586
+
587
+ .flight-devtools-item.expanded {
588
+ background: var(--bg-2);
589
+ }
590
+
591
+ .flight-devtools-item-header {
227
592
  display: flex;
593
+ align-items: center;
228
594
  gap: 8px;
595
+ }
596
+
597
+ .flight-devtools-item-details {
598
+ margin-top: 8px;
229
599
  padding: 8px;
600
+ background: var(--bg-3);
230
601
  border-radius: 4px;
231
- color: #ccc;
232
602
  }
233
603
 
234
- .flight-devtools-item:hover {
235
- background: #252525;
604
+ .detail-row {
605
+ display: flex;
606
+ padding: 3px 0;
607
+ }
608
+
609
+ .detail-label {
610
+ width: 80px;
611
+ color: var(--text-muted);
612
+ }
613
+
614
+ .detail-value {
615
+ flex: 1;
616
+ color: var(--text);
617
+ }
618
+
619
+ .detail-value.mono {
620
+ font-family: 'SF Mono', Monaco, monospace;
621
+ font-size: 11px;
236
622
  }
237
623
 
238
- .flight-devtools-item .method {
239
- width: 50px;
624
+ .method {
240
625
  font-weight: 600;
626
+ font-size: 10px;
627
+ padding: 2px 6px;
628
+ border-radius: 3px;
629
+ background: var(--bg-3);
241
630
  }
242
631
 
243
- .flight-devtools-item .method.get { color: #4CAF50; }
244
- .flight-devtools-item .method.post { color: #2196F3; }
245
- .flight-devtools-item .method.put { color: #FF9800; }
246
- .flight-devtools-item .method.delete { color: #f44336; }
632
+ .method.get { color: #22c55e; }
633
+ .method.post { color: #3b82f6; }
634
+ .method.put { color: #f59e0b; }
635
+ .method.patch { color: #f59e0b; }
636
+ .method.delete { color: #ef4444; }
247
637
 
248
- .flight-devtools-item .path {
638
+ .path {
249
639
  flex: 1;
250
- font-family: monospace;
640
+ font-family: 'SF Mono', Monaco, monospace;
641
+ font-size: 11px;
642
+ color: var(--text);
643
+ overflow: hidden;
644
+ text-overflow: ellipsis;
645
+ white-space: nowrap;
646
+ }
647
+
648
+ .badge {
649
+ font-size: 9px;
650
+ padding: 2px 5px;
651
+ border-radius: 3px;
652
+ background: var(--bg-3);
653
+ color: var(--text-muted);
654
+ }
655
+
656
+ .badge.dynamic { color: #a78bfa; }
657
+ .badge.slot { color: #60a5fa; }
658
+
659
+ .expand-icon {
660
+ color: var(--text-muted);
661
+ font-size: 8px;
662
+ }
663
+
664
+ .status { font-weight: 600; }
665
+ .status.status-2xx { color: #22c55e; }
666
+ .status.status-3xx { color: #f59e0b; }
667
+ .status.status-4xx { color: #ef4444; }
668
+ .status.status-5xx { color: #a855f7; }
669
+
670
+ .duration, .time {
671
+ color: var(--text-muted);
672
+ font-size: 11px;
673
+ }
674
+
675
+ .adapter-icon {
676
+ font-size: 16px;
251
677
  }
252
678
 
253
- .flight-devtools-item .status-2xx { color: #4CAF50; }
254
- .flight-devtools-item .status-3xx { color: #FF9800; }
255
- .flight-devtools-item .status-4xx { color: #f44336; }
256
- .flight-devtools-item .status-5xx { color: #9C27B0; }
679
+ .adapter-name {
680
+ flex: 1;
681
+ font-weight: 500;
682
+ }
683
+
684
+ .adapter-type {
685
+ color: var(--text-muted);
686
+ font-size: 10px;
687
+ }
257
688
 
258
- .flight-devtools-item .duration {
259
- color: #888;
689
+ .adapter-status {
690
+ font-size: 10px;
691
+ padding: 2px 6px;
692
+ border-radius: 3px;
693
+ }
694
+
695
+ .adapter-status.status-connected { background: rgba(34, 197, 94, 0.2); color: #22c55e; }
696
+ .adapter-status.status-disconnected { background: rgba(239, 68, 68, 0.2); color: #ef4444; }
697
+ .adapter-status.status-error { background: rgba(239, 68, 68, 0.2); color: #ef4444; }
698
+
699
+ .flight-devtools-metrics {
700
+ display: grid;
701
+ grid-template-columns: repeat(3, 1fr);
702
+ gap: 8px;
703
+ padding: 8px;
704
+ }
705
+
706
+ .flight-devtools-metric {
707
+ padding: 12px;
708
+ background: var(--bg-2);
709
+ border-radius: 8px;
710
+ text-align: center;
711
+ }
712
+
713
+ .flight-devtools-metric.good { border-left: 3px solid #22c55e; }
714
+ .flight-devtools-metric.needs-improvement { border-left: 3px solid #f59e0b; }
715
+ .flight-devtools-metric.poor { border-left: 3px solid #ef4444; }
716
+
717
+ .metric-label {
718
+ font-weight: 600;
719
+ font-size: 11px;
720
+ color: var(--text);
721
+ }
722
+
723
+ .metric-value {
724
+ font-size: 18px;
725
+ font-weight: 600;
726
+ margin: 4px 0;
727
+ }
728
+
729
+ .flight-devtools-metric.good .metric-value { color: #22c55e; }
730
+ .flight-devtools-metric.needs-improvement .metric-value { color: #f59e0b; }
731
+ .flight-devtools-metric.poor .metric-value { color: #ef4444; }
732
+
733
+ .metric-desc {
734
+ font-size: 9px;
735
+ color: var(--text-muted);
736
+ }
737
+
738
+ .flight-devtools-filter-group {
739
+ display: flex;
740
+ gap: 4px;
741
+ }
742
+
743
+ .flight-devtools-filter-btn {
744
+ padding: 4px 8px;
745
+ background: transparent;
746
+ border: 1px solid var(--border);
747
+ border-radius: 4px;
748
+ color: var(--text-muted);
749
+ font-size: 10px;
750
+ cursor: pointer;
751
+ text-transform: capitalize;
752
+ }
753
+
754
+ .flight-devtools-filter-btn.active {
755
+ background: var(--green);
756
+ border-color: var(--green);
757
+ color: white;
758
+ }
759
+
760
+ .console-list {
761
+ font-family: 'SF Mono', Monaco, monospace;
762
+ font-size: 11px;
763
+ }
764
+
765
+ .flight-devtools-console-item {
766
+ display: flex;
767
+ gap: 8px;
768
+ padding: 6px 8px;
769
+ border-bottom: 1px solid var(--border);
770
+ }
771
+
772
+ .flight-devtools-console-item.level-error {
773
+ background: rgba(239, 68, 68, 0.1);
774
+ }
775
+
776
+ .flight-devtools-console-item.level-warn {
777
+ background: rgba(245, 158, 11, 0.1);
778
+ }
779
+
780
+ .console-time {
781
+ color: var(--text-muted);
782
+ flex-shrink: 0;
783
+ }
784
+
785
+ .console-level {
786
+ flex-shrink: 0;
787
+ width: 40px;
788
+ font-weight: 600;
789
+ }
790
+
791
+ .level-log .console-level { color: #888; }
792
+ .level-info .console-level { color: #3b82f6; }
793
+ .level-warn .console-level { color: #f59e0b; }
794
+ .level-error .console-level { color: #ef4444; }
795
+
796
+ .console-message {
797
+ flex: 1;
798
+ word-break: break-word;
799
+ color: var(--text);
260
800
  }
261
801
 
262
802
  .flight-devtools-empty {
263
- padding: 32px;
803
+ padding: 40px 20px;
264
804
  text-align: center;
265
- color: #666;
805
+ color: var(--text-muted);
806
+ }
807
+
808
+ .flight-devtools-empty small {
809
+ display: block;
810
+ margin-top: 8px;
811
+ font-size: 11px;
812
+ opacity: 0.7;
266
813
  }
267
814
  `;
268
815
  }