@flight-framework/devtools 0.0.4 → 0.0.6

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 ? '' : getFlightLogo()}
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,160 +220,619 @@ 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
  }
132
363
  // ============================================================================
364
+ // Logo
365
+ // ============================================================================
366
+ function getFlightLogo() {
367
+ // Simplified Flight logo SVG for the button
368
+ return `<svg viewBox="50 100 500 500" width="28" height="28" fill="currentColor">
369
+ <g transform="translate(0,800) scale(0.1,-0.1)">
370
+ <path d="M 4056 6148 c31 -40 81 -114 110 -165 53 -89 119 -231 112 -239 -2 -2 -23
371
+ 6 -45 17 -133 65 -342 65 -475 0 -26 -13 -38 -15 -38 -6 0 18 94 206 136 272
372
+ 34 55 135 193 140 193 2 0 28 -33 60 -72z m90 -449 c34 -11 90 -39 125 -62
373
+ l64 -43 24 -99 c13 -55 25 -106 27 -112 3 -10 107 -13 480 -13 l476 0 -5 -22
374
+ c-3 -13 -18 -88 -32 -167 -14 -80 -28 -148 -31 -153 -3 -4 -182 -8 -399 -8
375
+ l-393 0 -27 -82 c-86 -263 -277 -536 -531 -758 -131 -114 -245 -201 -484 -370
376
+ -96 -67 -222 -159 -280 -204 -143 -110 -364 -332 -428 -429 -62 -94 -67 -84
377
+ -11 25 120 240 288 420 669 719 268 209 377 305 511 444 180 188 290 363 352
378
+ 561 41 134 39 143 -11 44 -98 -195 -226 -366 -406 -545 -112 -111 -204 -191
379
+ -210 -184 -13 15 -39 379 -43 601 -6 283 3 419 37 588 33 162 33 160 102 207
380
+ 134 91 274 111 424 62z"/>
381
+ <path d="M3938 5509 c-57 -16 -114 -71 -133 -130 -29 -86 -4 -168 67 -222 136
382
+ -103 328 -10 328 160 0 132 -131 228 -262 192z"/>
383
+ </g>
384
+ </svg>`;
385
+ }
386
+ // ============================================================================
133
387
  // Styles
134
388
  // ============================================================================
135
389
  function getDevToolsStyles() {
136
390
  return `
137
391
  #flight-devtools {
138
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
392
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
139
393
  font-size: 12px;
394
+ line-height: 1.4;
395
+ --green: #22c55e;
396
+ --green-dark: #16a34a;
397
+ --bg: #0f0f0f;
398
+ --bg-2: #1a1a1a;
399
+ --bg-3: #252525;
400
+ --border: #333;
401
+ --text: #e5e5e5;
402
+ --text-muted: #888;
140
403
  }
141
404
 
142
405
  .flight-devtools {
143
406
  position: fixed;
144
- z-index: 99999;
145
- }
146
-
147
- .flight-devtools.bottom-right {
148
- bottom: 16px;
149
- right: 16px;
407
+ z-index: 999999;
150
408
  }
151
409
 
152
- .flight-devtools.bottom-left {
153
- bottom: 16px;
154
- left: 16px;
155
- }
410
+ .flight-devtools.bottom-right { bottom: 16px; right: 16px; }
411
+ .flight-devtools.bottom-left { bottom: 16px; left: 16px; }
412
+ .flight-devtools.top-right { top: 16px; right: 16px; }
413
+ .flight-devtools.top-left { top: 16px; left: 16px; }
156
414
 
157
415
  .flight-devtools-toggle {
158
416
  width: 48px;
159
417
  height: 48px;
160
418
  border-radius: 50%;
161
419
  border: none;
162
- background: #32CD32;
420
+ background: linear-gradient(135deg, var(--green) 0%, var(--green-dark) 100%);
163
421
  color: white;
164
- font-weight: bold;
422
+ font-size: 20px;
165
423
  cursor: pointer;
166
- box-shadow: 0 4px 12px rgba(0,0,0,0.3);
424
+ box-shadow: 0 4px 20px rgba(34, 197, 94, 0.4);
425
+ transition: transform 0.2s, box-shadow 0.2s;
426
+ }
427
+
428
+ .flight-devtools-toggle:hover {
429
+ transform: scale(1.05);
430
+ box-shadow: 0 6px 24px rgba(34, 197, 94, 0.5);
167
431
  }
168
432
 
169
433
  .flight-devtools.open .flight-devtools-toggle {
170
- border-radius: 50%;
171
434
  position: absolute;
172
- top: 8px;
173
- right: 8px;
174
- width: 32px;
175
- height: 32px;
435
+ top: 12px;
436
+ right: 12px;
437
+ width: 28px;
438
+ height: 28px;
176
439
  font-size: 14px;
440
+ background: var(--bg-3);
441
+ box-shadow: none;
442
+ }
443
+
444
+ .flight-devtools-badge {
445
+ position: absolute;
446
+ top: -4px;
447
+ right: -4px;
448
+ width: 18px;
449
+ height: 18px;
450
+ border-radius: 50%;
451
+ background: #ef4444;
452
+ color: white;
453
+ font-size: 10px;
454
+ font-weight: 600;
455
+ display: flex;
456
+ align-items: center;
457
+ justify-content: center;
177
458
  }
178
459
 
179
460
  .flight-devtools-panel {
180
461
  position: absolute;
181
462
  bottom: 60px;
182
463
  right: 0;
183
- width: 400px;
184
- max-height: 500px;
185
- background: #1a1a1a;
464
+ width: 480px;
465
+ max-height: 600px;
466
+ background: var(--bg);
467
+ border: 1px solid var(--border);
186
468
  border-radius: 12px;
187
469
  overflow: hidden;
188
- box-shadow: 0 8px 32px rgba(0,0,0,0.4);
470
+ box-shadow: 0 8px 40px rgba(0,0,0,0.5);
471
+ display: flex;
472
+ flex-direction: column;
189
473
  }
190
474
 
191
475
  .flight-devtools-header {
192
- padding: 12px 16px;
193
- background: #32CD32;
476
+ padding: 12px 48px 12px 16px;
477
+ background: linear-gradient(135deg, var(--green) 0%, var(--green-dark) 100%);
194
478
  color: white;
479
+ display: flex;
480
+ align-items: center;
481
+ justify-content: space-between;
482
+ }
483
+
484
+ .flight-devtools-logo {
195
485
  font-weight: 600;
486
+ font-size: 14px;
487
+ }
488
+
489
+ .flight-devtools-version {
490
+ font-size: 10px;
491
+ opacity: 0.8;
196
492
  }
197
493
 
198
494
  .flight-devtools-tabs {
199
495
  display: flex;
200
- border-bottom: 1px solid #333;
496
+ background: var(--bg-2);
497
+ border-bottom: 1px solid var(--border);
201
498
  }
202
499
 
203
500
  .flight-devtools-tabs button {
204
501
  flex: 1;
205
- padding: 8px;
502
+ padding: 10px 8px;
206
503
  border: none;
207
504
  background: transparent;
208
- color: #888;
505
+ color: var(--text-muted);
506
+ font-size: 11px;
209
507
  cursor: pointer;
508
+ border-bottom: 2px solid transparent;
509
+ transition: all 0.2s;
510
+ }
511
+
512
+ .flight-devtools-tabs button:hover {
513
+ color: var(--text);
514
+ background: var(--bg-3);
210
515
  }
211
516
 
212
517
  .flight-devtools-tabs button.active {
213
- color: #32CD32;
214
- border-bottom: 2px solid #32CD32;
518
+ color: var(--green);
519
+ border-bottom-color: var(--green);
520
+ }
521
+
522
+ .flight-devtools-tabs .count {
523
+ display: inline-block;
524
+ min-width: 18px;
525
+ padding: 1px 5px;
526
+ margin-left: 4px;
527
+ background: var(--bg-3);
528
+ border-radius: 10px;
529
+ font-size: 10px;
215
530
  }
216
531
 
217
532
  .flight-devtools-content {
218
- max-height: 350px;
533
+ flex: 1;
219
534
  overflow-y: auto;
535
+ max-height: 480px;
536
+ }
537
+
538
+ .flight-devtools-toolbar {
539
+ display: flex;
540
+ align-items: center;
541
+ justify-content: space-between;
542
+ padding: 8px 12px;
543
+ background: var(--bg-2);
544
+ border-bottom: 1px solid var(--border);
545
+ }
546
+
547
+ .flight-devtools-search {
548
+ flex: 1;
549
+ padding: 6px 10px;
550
+ background: var(--bg-3);
551
+ border: 1px solid var(--border);
552
+ border-radius: 6px;
553
+ color: var(--text);
554
+ font-size: 12px;
555
+ }
556
+
557
+ .flight-devtools-search:focus {
558
+ outline: none;
559
+ border-color: var(--green);
560
+ }
561
+
562
+ .flight-devtools-btn-small {
563
+ padding: 4px 10px;
564
+ background: var(--bg-3);
565
+ border: 1px solid var(--border);
566
+ border-radius: 4px;
567
+ color: var(--text-muted);
568
+ font-size: 11px;
569
+ cursor: pointer;
570
+ }
571
+
572
+ .flight-devtools-btn-small:hover {
573
+ background: var(--border);
574
+ color: var(--text);
575
+ }
576
+
577
+ .flight-devtools-info {
578
+ color: var(--text-muted);
579
+ font-size: 11px;
220
580
  }
221
581
 
222
582
  .flight-devtools-list {
223
583
  padding: 8px;
224
584
  }
225
585
 
586
+ .flight-devtools-section {
587
+ margin-bottom: 16px;
588
+ }
589
+
590
+ .flight-devtools-section-title {
591
+ padding: 8px 8px 4px;
592
+ color: var(--text-muted);
593
+ font-size: 10px;
594
+ font-weight: 600;
595
+ text-transform: uppercase;
596
+ letter-spacing: 0.5px;
597
+ }
598
+
226
599
  .flight-devtools-item {
600
+ padding: 8px 10px;
601
+ border-radius: 6px;
602
+ cursor: pointer;
603
+ transition: background 0.15s;
604
+ }
605
+
606
+ .flight-devtools-item:hover {
607
+ background: var(--bg-2);
608
+ }
609
+
610
+ .flight-devtools-item.expanded {
611
+ background: var(--bg-2);
612
+ }
613
+
614
+ .flight-devtools-item-header {
227
615
  display: flex;
616
+ align-items: center;
228
617
  gap: 8px;
618
+ }
619
+
620
+ .flight-devtools-item-details {
621
+ margin-top: 8px;
229
622
  padding: 8px;
623
+ background: var(--bg-3);
230
624
  border-radius: 4px;
231
- color: #ccc;
232
625
  }
233
626
 
234
- .flight-devtools-item:hover {
235
- background: #252525;
627
+ .detail-row {
628
+ display: flex;
629
+ padding: 3px 0;
236
630
  }
237
631
 
238
- .flight-devtools-item .method {
239
- width: 50px;
632
+ .detail-label {
633
+ width: 80px;
634
+ color: var(--text-muted);
635
+ }
636
+
637
+ .detail-value {
638
+ flex: 1;
639
+ color: var(--text);
640
+ }
641
+
642
+ .detail-value.mono {
643
+ font-family: 'SF Mono', Monaco, monospace;
644
+ font-size: 11px;
645
+ }
646
+
647
+ .method {
240
648
  font-weight: 600;
649
+ font-size: 10px;
650
+ padding: 2px 6px;
651
+ border-radius: 3px;
652
+ background: var(--bg-3);
653
+ }
654
+
655
+ .method.get { color: #22c55e; }
656
+ .method.post { color: #3b82f6; }
657
+ .method.put { color: #f59e0b; }
658
+ .method.patch { color: #f59e0b; }
659
+ .method.delete { color: #ef4444; }
660
+
661
+ .path {
662
+ flex: 1;
663
+ font-family: 'SF Mono', Monaco, monospace;
664
+ font-size: 11px;
665
+ color: var(--text);
666
+ overflow: hidden;
667
+ text-overflow: ellipsis;
668
+ white-space: nowrap;
669
+ }
670
+
671
+ .badge {
672
+ font-size: 9px;
673
+ padding: 2px 5px;
674
+ border-radius: 3px;
675
+ background: var(--bg-3);
676
+ color: var(--text-muted);
677
+ }
678
+
679
+ .badge.dynamic { color: #a78bfa; }
680
+ .badge.slot { color: #60a5fa; }
681
+
682
+ .expand-icon {
683
+ color: var(--text-muted);
684
+ font-size: 8px;
241
685
  }
242
686
 
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; }
687
+ .status { font-weight: 600; }
688
+ .status.status-2xx { color: #22c55e; }
689
+ .status.status-3xx { color: #f59e0b; }
690
+ .status.status-4xx { color: #ef4444; }
691
+ .status.status-5xx { color: #a855f7; }
247
692
 
248
- .flight-devtools-item .path {
693
+ .duration, .time {
694
+ color: var(--text-muted);
695
+ font-size: 11px;
696
+ }
697
+
698
+ .adapter-icon {
699
+ font-size: 16px;
700
+ }
701
+
702
+ .adapter-name {
249
703
  flex: 1;
250
- font-family: monospace;
704
+ font-weight: 500;
251
705
  }
252
706
 
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; }
707
+ .adapter-type {
708
+ color: var(--text-muted);
709
+ font-size: 10px;
710
+ }
257
711
 
258
- .flight-devtools-item .duration {
259
- color: #888;
712
+ .adapter-status {
713
+ font-size: 10px;
714
+ padding: 2px 6px;
715
+ border-radius: 3px;
716
+ }
717
+
718
+ .adapter-status.status-connected { background: rgba(34, 197, 94, 0.2); color: #22c55e; }
719
+ .adapter-status.status-disconnected { background: rgba(239, 68, 68, 0.2); color: #ef4444; }
720
+ .adapter-status.status-error { background: rgba(239, 68, 68, 0.2); color: #ef4444; }
721
+
722
+ .flight-devtools-metrics {
723
+ display: grid;
724
+ grid-template-columns: repeat(3, 1fr);
725
+ gap: 8px;
726
+ padding: 8px;
727
+ }
728
+
729
+ .flight-devtools-metric {
730
+ padding: 12px;
731
+ background: var(--bg-2);
732
+ border-radius: 8px;
733
+ text-align: center;
734
+ }
735
+
736
+ .flight-devtools-metric.good { border-left: 3px solid #22c55e; }
737
+ .flight-devtools-metric.needs-improvement { border-left: 3px solid #f59e0b; }
738
+ .flight-devtools-metric.poor { border-left: 3px solid #ef4444; }
739
+
740
+ .metric-label {
741
+ font-weight: 600;
742
+ font-size: 11px;
743
+ color: var(--text);
744
+ }
745
+
746
+ .metric-value {
747
+ font-size: 18px;
748
+ font-weight: 600;
749
+ margin: 4px 0;
750
+ }
751
+
752
+ .flight-devtools-metric.good .metric-value { color: #22c55e; }
753
+ .flight-devtools-metric.needs-improvement .metric-value { color: #f59e0b; }
754
+ .flight-devtools-metric.poor .metric-value { color: #ef4444; }
755
+
756
+ .metric-desc {
757
+ font-size: 9px;
758
+ color: var(--text-muted);
759
+ }
760
+
761
+ .flight-devtools-filter-group {
762
+ display: flex;
763
+ gap: 4px;
764
+ }
765
+
766
+ .flight-devtools-filter-btn {
767
+ padding: 4px 8px;
768
+ background: transparent;
769
+ border: 1px solid var(--border);
770
+ border-radius: 4px;
771
+ color: var(--text-muted);
772
+ font-size: 10px;
773
+ cursor: pointer;
774
+ text-transform: capitalize;
775
+ }
776
+
777
+ .flight-devtools-filter-btn.active {
778
+ background: var(--green);
779
+ border-color: var(--green);
780
+ color: white;
781
+ }
782
+
783
+ .console-list {
784
+ font-family: 'SF Mono', Monaco, monospace;
785
+ font-size: 11px;
786
+ }
787
+
788
+ .flight-devtools-console-item {
789
+ display: flex;
790
+ gap: 8px;
791
+ padding: 6px 8px;
792
+ border-bottom: 1px solid var(--border);
793
+ }
794
+
795
+ .flight-devtools-console-item.level-error {
796
+ background: rgba(239, 68, 68, 0.1);
797
+ }
798
+
799
+ .flight-devtools-console-item.level-warn {
800
+ background: rgba(245, 158, 11, 0.1);
801
+ }
802
+
803
+ .console-time {
804
+ color: var(--text-muted);
805
+ flex-shrink: 0;
806
+ }
807
+
808
+ .console-level {
809
+ flex-shrink: 0;
810
+ width: 40px;
811
+ font-weight: 600;
812
+ }
813
+
814
+ .level-log .console-level { color: #888; }
815
+ .level-info .console-level { color: #3b82f6; }
816
+ .level-warn .console-level { color: #f59e0b; }
817
+ .level-error .console-level { color: #ef4444; }
818
+
819
+ .console-message {
820
+ flex: 1;
821
+ word-break: break-word;
822
+ color: var(--text);
260
823
  }
261
824
 
262
825
  .flight-devtools-empty {
263
- padding: 32px;
826
+ padding: 40px 20px;
264
827
  text-align: center;
265
- color: #666;
828
+ color: var(--text-muted);
829
+ }
830
+
831
+ .flight-devtools-empty small {
832
+ display: block;
833
+ margin-top: 8px;
834
+ font-size: 11px;
835
+ opacity: 0.7;
266
836
  }
267
837
  `;
268
838
  }