claude-dev-server 1.1.4 → 1.2.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.
@@ -196,87 +196,77 @@ async function handleInspect(msg, ws, projectRoot) {
196
196
  // src/client/injection.js
197
197
  var CLIENT_STYLES = `
198
198
  <style id="claude-dev-server-styles">
199
- .claude-dev-server-toggle {
200
- position: fixed;
201
- bottom: 120px;
202
- right: 20px;
203
- width: 44px;
204
- height: 44px;
205
- background: #1e1e1e;
206
- border: none;
207
- border-radius: 50%;
208
- box-shadow: 0 2px 12px rgba(0,0,0,0.3);
209
- cursor: pointer;
210
- z-index: 2147483647;
211
- display: flex;
212
- align-items: center;
213
- justify-content: center;
214
- font-size: 20px;
215
- transition: transform 0.2s, background 0.2s;
199
+ * {
200
+ margin: 0;
216
201
  padding: 0;
202
+ box-sizing: border-box;
217
203
  }
218
- .claude-dev-server-toggle:hover {
219
- transform: scale(1.1);
220
- background: #2d2d2d;
221
- }
222
- .claude-dev-server-toggle.hidden {
223
- display: none;
204
+ html, body {
205
+ width: 100%;
206
+ height: 100%;
207
+ overflow: hidden;
208
+ background: transparent;
224
209
  }
225
- .claude-dev-server-inspect-btn {
226
- position: fixed;
227
- bottom: 174px;
228
- right: 20px;
229
- width: 44px;
230
- height: 44px;
231
- background: #d97757;
232
- border: none;
233
- border-radius: 50%;
234
- box-shadow: 0 2px 12px rgba(0,0,0,0.3);
235
- cursor: pointer;
236
- z-index: 2147483647;
210
+ .claude-dev-server-container {
237
211
  display: flex;
238
- align-items: center;
239
- justify-content: center;
240
- font-size: 16px;
241
- transition: transform 0.2s, background 0.2s;
242
- }
243
- .claude-dev-server-inspect-btn:hover {
244
- transform: scale(1.1);
245
- background: #c96a4a;
212
+ width: 100vw;
213
+ height: 100vh;
214
+ overflow: hidden;
215
+ background: transparent;
246
216
  }
247
- .claude-dev-server-inspect-btn.active {
248
- background: #b85d3f;
217
+ .claude-dev-server-left {
218
+ width: 40%;
219
+ min-width: 300px;
220
+ max-width: 60%;
221
+ overflow: hidden;
222
+ position: relative;
223
+ background: #fff;
224
+ display: flex;
225
+ flex-direction: column;
249
226
  }
250
- .claude-dev-server-inspect-btn.hidden {
251
- display: none;
227
+ .claude-dev-server-left iframe {
228
+ width: 100%;
229
+ height: 100%;
230
+ border: none;
252
231
  }
253
- .claude-dev-server-toggle svg,
254
- .claude-dev-server-inspect-btn svg {
255
- width: 20px;
256
- height: 20px;
232
+ .claude-dev-server-divider {
233
+ width: 6px;
234
+ background: #3e3e3e;
235
+ position: relative;
236
+ cursor: col-resize;
237
+ transition: background 0.2s;
257
238
  flex-shrink: 0;
258
239
  }
259
- .claude-dev-server-panel {
260
- position: fixed;
261
- top: 0;
262
- right: 0;
263
- width: 650px;
264
- height: 100vh;
265
- background: #1e1e1e;
266
- color: #d4d4d4;
267
- font-family: 'SF Mono', 'Monaco', 'Cascadia Code', 'Consolas', monospace;
268
- font-size: 13px;
269
- box-shadow: -4px 0 20px rgba(0,0,0,0.3);
270
- transform: translateX(100%);
271
- transition: transform 0.2s ease;
272
- z-index: 2147483647;
240
+ .claude-dev-server-divider:hover,
241
+ .claude-dev-server-divider.dragging {
242
+ background: #d97757;
243
+ }
244
+ .claude-dev-server-right {
245
+ flex: 1;
246
+ min-width: 300px;
247
+ overflow: hidden;
248
+ position: relative;
273
249
  display: flex;
274
250
  flex-direction: column;
275
251
  }
276
- .claude-dev-server-panel.open {
277
- transform: translateX(0);
252
+ .claude-dev-server-terminal {
253
+ flex: 1;
254
+ overflow: hidden;
255
+ position: relative;
256
+ background: #000;
278
257
  }
279
- .claude-dev-server-header {
258
+ .claude-dev-server-terminal iframe,
259
+ .claude-dev-server-terminal-iframe {
260
+ width: 100%;
261
+ height: 100%;
262
+ border: none;
263
+ }
264
+ .claude-dev-server-dev-iframe {
265
+ width: 100%;
266
+ height: 100%;
267
+ border: none;
268
+ }
269
+ .claude-dev-server-terminal-header {
280
270
  padding: 8px 12px;
281
271
  background: #2d2d2d;
282
272
  border-bottom: 1px solid #3e3e3e;
@@ -284,6 +274,7 @@ var CLIENT_STYLES = `
284
274
  justify-content: space-between;
285
275
  align-items: center;
286
276
  user-select: none;
277
+ min-height: 40px;
287
278
  }
288
279
  .claude-dev-server-title {
289
280
  font-weight: 600;
@@ -292,6 +283,7 @@ var CLIENT_STYLES = `
292
283
  align-items: center;
293
284
  gap: 8px;
294
285
  font-size: 13px;
286
+ font-family: 'SF Mono', 'Monaco', 'Cascadia Code', 'Consolas', monospace;
295
287
  }
296
288
  .claude-dev-server-title svg {
297
289
  width: 14px;
@@ -323,36 +315,6 @@ var CLIENT_STYLES = `
323
315
  background: #b85d3f;
324
316
  color: #fff;
325
317
  }
326
- .claude-dev-server-close {
327
- background: none;
328
- border: none;
329
- color: #858585;
330
- cursor: pointer;
331
- font-size: 18px;
332
- padding: 0;
333
- width: 20px;
334
- height: 20px;
335
- display: flex;
336
- align-items: center;
337
- justify-content: center;
338
- border-radius: 3px;
339
- transition: background 0.15s, color 0.15s;
340
- }
341
- .claude-dev-server-close:hover {
342
- background: #3e3e3e;
343
- color: #fff;
344
- }
345
- .claude-dev-server-terminal {
346
- flex: 1;
347
- overflow: hidden;
348
- position: relative;
349
- background: #000;
350
- }
351
- .claude-dev-server-terminal iframe {
352
- width: 100%;
353
- height: 100%;
354
- border: none;
355
- }
356
318
  .claude-dev-server-inspect-overlay {
357
319
  position: fixed;
358
320
  top: 0;
@@ -386,39 +348,22 @@ var CLIENT_STYLES = `
386
348
  font-family: monospace;
387
349
  white-space: nowrap;
388
350
  }
389
- /* Responsive layout for portrait mode */
351
+ /* Portrait mode */
390
352
  @media (orientation: portrait) {
391
- .claude-dev-server-toggle {
392
- top: auto;
393
- bottom: 120px;
394
- right: 10px;
395
- width: 36px;
396
- height: 36px;
353
+ .claude-dev-server-container {
354
+ flex-direction: column;
397
355
  }
398
- .claude-dev-server-inspect-btn {
399
- top: auto;
400
- bottom: 166px;
401
- right: 10px;
402
- width: 36px;
403
- height: 36px;
404
- }
405
- .claude-dev-server-panel {
406
- top: auto;
407
- bottom: 0;
408
- right: 0;
409
- left: 0;
356
+ .claude-dev-server-divider {
410
357
  width: 100%;
411
- height: 80vh;
412
- transform: translateY(100%);
413
- box-shadow: 0 -4px 20px rgba(0,0,0,0.3);
414
- }
415
- .claude-dev-server-panel.open {
416
- transform: translateY(0);
358
+ height: 6px;
359
+ cursor: row-resize;
417
360
  }
418
- .claude-dev-server-toggle svg,
419
- .claude-dev-server-inspect-btn svg {
420
- width: 16px;
421
- height: 16px;
361
+ .claude-dev-server-right {
362
+ width: 100%;
363
+ min-width: unset;
364
+ max-width: unset;
365
+ height: 50%;
366
+ min-height: 200px;
422
367
  }
423
368
  }
424
369
  </style>
@@ -426,15 +371,16 @@ var CLIENT_STYLES = `
426
371
  var CLIENT_SCRIPT = `
427
372
  (() => {
428
373
  let ws = null
429
- let panel = null
430
- let toggleBtn = null
431
- let inspectBtn = null // \u60AC\u6D6E inspect \u6309\u94AE
432
- let terminalContainer = null
433
- let terminalIframe = null
374
+ let ttydIframe = null
375
+ let devIframe = null
434
376
  let overlay = null
435
- let isOpen = false
436
377
  let isInspectMode = false
437
378
  let ttydWsUrl = null
379
+ let leftPanel = null
380
+ let divider = null
381
+ let rightPanel = null
382
+ let isDragging = false
383
+ let inspectListenersRegistered = false
438
384
 
439
385
  // Fetch the WebSocket port from the server
440
386
  async function getWsPort() {
@@ -449,75 +395,250 @@ var CLIENT_SCRIPT = `
449
395
  connect(port)
450
396
  } catch (err) {
451
397
  console.error('[Claude Dev Server] Failed to get port:', err)
452
- // Retry after 1 second
453
398
  setTimeout(initWhenReady, 1000)
454
399
  return
455
400
  }
456
- createToggleBtn()
457
- createInspectBtn()
401
+ // Create overlay first before split layout
458
402
  createOverlay()
459
- createPanel()
403
+ createSplitLayout()
460
404
  }
461
405
 
462
- function createToggleBtn() {
463
- if (toggleBtn) return
464
- toggleBtn = document.createElement('button')
465
- toggleBtn.className = 'claude-dev-server-toggle'
466
- toggleBtn.innerHTML = '<svg width="24" height="24" viewBox="0 0 24 24" fill="none"><path d="M11.376 24L10.776 23.544L10.44 22.8L10.776 21.312L11.16 19.392L11.472 17.856L11.76 15.96L11.928 15.336L11.904 15.288L11.784 15.312L10.344 17.28L8.16 20.232L6.432 22.056L6.024 22.224L5.304 21.864L5.376 21.192L5.784 20.616L8.16 17.568L9.6 15.672L10.536 14.592L10.512 14.448H10.464L4.128 18.576L3 18.72L2.496 18.264L2.568 17.52L2.808 17.28L4.704 15.96L9.432 13.32L9.504 13.08L9.432 12.96H9.192L8.4 12.912L5.712 12.84L3.384 12.744L1.104 12.624L0.528 12.504L0 11.784L0.048 11.424L0.528 11.112L1.224 11.16L2.736 11.28L5.016 11.424L6.672 11.52L9.12 11.784H9.504L9.552 11.616L9.432 11.52L9.336 11.424L6.96 9.84L4.416 8.16L3.072 7.176L2.352 6.672L1.992 6.216L1.848 5.208L2.496 4.488L3.384 4.56L3.6 4.608L4.488 5.304L6.384 6.768L8.88 8.616L9.24 8.904L9.408 8.808V8.736L9.24 8.472L7.896 6.024L6.456 3.528L5.808 2.496L5.64 1.872C5.576 1.656 5.544 1.416 5.544 1.152L6.288 0.144001L6.696 0L7.704 0.144001L8.112 0.504001L8.736 1.92L9.72 4.152L11.28 7.176L11.736 8.088L11.976 8.904L12.072 9.168H12.24V9.024L12.36 7.296L12.6 5.208L12.84 2.52L12.912 1.752L13.296 0.840001L14.04 0.360001L14.616 0.624001L15.096 1.32L15.024 1.752L14.76 3.6L14.184 6.504L13.824 8.472H14.04L14.28 8.208L15.264 6.912L16.92 4.848L17.64 4.032L18.504 3.12L19.056 2.688H20.088L20.832 3.816L20.496 4.992L19.44 6.336L18.552 7.464L17.28 9.168L16.512 10.536L16.584 10.632H16.752L19.608 10.008L21.168 9.744L22.992 9.432L23.832 9.816L23.928 10.2L23.592 11.016L21.624 11.496L19.32 11.952L15.888 12.768L15.84 12.792L15.888 12.864L17.424 13.008L18.096 13.056H19.728L22.752 13.272L23.544 13.8L24 14.424L23.928 14.928L22.704 15.528L21.072 15.144L17.232 14.232L15.936 13.92H15.744V14.016L16.848 15.096L18.84 16.896L21.36 19.224L21.48 19.8L21.168 20.28L20.832 20.232L18.624 18.552L17.76 17.808L15.84 16.2H15.72V16.368L16.152 17.016L18.504 20.544L18.624 21.624L18.456 21.96L17.832 22.176L17.184 22.056L15.792 20.136L14.376 17.952L13.224 16.008L13.104 16.104L12.408 23.352L12.096 23.712L11.376 24Z" fill="#d97757"/></svg>'
467
- toggleBtn.title = 'Open Claude Code (Cmd/Ctrl + \`)'
468
- toggleBtn.setAttribute('data-dev-tool', 'true')
469
- toggleBtn.addEventListener('click', () => togglePanel(true))
470
- document.body.appendChild(toggleBtn)
471
- }
406
+ function createSplitLayout() {
407
+ // IMPORTANT: Only create layout in the top-level window, not in iframes
408
+ // This prevents infinite nesting when the dev iframe loads
409
+ if (window.top !== window.self) {
410
+ console.log('[Claude Dev Server] Not in top window, skipping layout creation')
411
+ return
412
+ }
413
+
414
+ // Check if already created
415
+ if (document.querySelector('.claude-dev-server-container')) return
416
+ // Also check if we're already in an injected page
417
+ if (window.__CLAUDE_SPLIT_LAYOUT_CREATED__) return
418
+ window.__CLAUDE_SPLIT_LAYOUT_CREATED__ = true
419
+
420
+ // Get original HTML from window variable (set by server)
421
+ // The HTML is Base64 encoded to avoid any escaping issues
422
+ let originalHtml
423
+ if (window.__CLAUDE_ORIGINAL_HTML_BASE64__) {
424
+ // Decode Base64
425
+ try {
426
+ const decoded = atob(window.__CLAUDE_ORIGINAL_HTML_BASE64__)
427
+ // Handle UTF-8 encoding
428
+ const bytes = new Uint8Array(decoded.length)
429
+ for (let i = 0; i < decoded.length; i++) {
430
+ bytes[i] = decoded.charCodeAt(i)
431
+ }
432
+ originalHtml = new TextDecoder().decode(bytes)
433
+ } catch (e) {
434
+ console.error('[Claude Dev Server] Failed to decode Base64 HTML:', e)
435
+ originalHtml = document.documentElement.outerHTML
436
+ }
437
+ } else {
438
+ originalHtml = document.documentElement.outerHTML
439
+ }
440
+
441
+ // Create container
442
+ const container = document.createElement('div')
443
+ container.className = 'claude-dev-server-container'
444
+
445
+ // Left panel - terminal (Claude)
446
+ leftPanel = document.createElement('div')
447
+ leftPanel.className = 'claude-dev-server-left'
448
+
449
+ // Terminal header
450
+ const header = document.createElement('div')
451
+ header.className = 'claude-dev-server-terminal-header'
452
+ header.innerHTML = \`
453
+ <span class="claude-dev-server-title">
454
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none">
455
+ <path d="M11.376 24L10.776 23.544L10.44 22.8L10.776 21.312L11.16 19.392L11.472 17.856L11.76 15.96L11.928 15.336L11.904 15.288L11.784 15.312L10.344 17.28L8.16 20.232L6.432 22.056L6.024 22.224L5.304 21.864L5.376 21.192L5.784 20.616L8.16 17.568L9.6 15.672L10.536 14.592L10.512 14.448H10.464L4.128 18.576L3 18.72L2.496 18.264L2.568 17.52L2.808 17.28L4.704 15.96L9.432 13.32L9.504 13.08L9.432 12.96H9.192L8.4 12.912L5.712 12.84L3.384 12.744L1.104 12.624L0.528 12.504L0 11.784L0.048 11.424L0.528 11.112L1.224 11.16L2.736 11.28L5.016 11.424L6.672 11.52L9.12 11.784H9.504L9.552 11.616L9.432 11.52L9.336 11.424L6.96 9.84L4.416 8.16L3.072 7.176L2.352 6.672L1.992 6.216L1.848 5.208L2.496 4.488L3.384 4.56L3.6 4.608L4.488 5.304L6.384 6.768L8.88 8.616L9.24 8.904L9.408 8.808V8.736L9.24 8.472L7.896 6.024L6.456 3.528L5.808 2.496L5.64 1.872C5.576 1.656 5.544 1.416 5.544 1.152L6.288 0.144001L6.696 0L7.704 0.144001L8.112 0.504001L8.736 1.92L9.72 4.152L11.28 7.176L11.736 8.088L11.976 8.904L12.072 9.168H12.24V9.024L12.36 7.296L12.6 5.208L12.84 2.52L12.912 1.752L13.296 0.840001L14.04 0.360001L14.616 0.624001L15.096 1.32L15.024 1.752L14.76 3.6L14.184 6.504L13.824 8.472H14.04L14.28 8.208L15.264 6.912L16.92 4.848L17.64 4.032L18.504 3.12L19.056 2.688H20.088L20.832 3.816L20.496 4.992L19.44 6.336L18.552 7.464L17.28 9.168L16.512 10.536L16.584 10.632H16.752L19.608 10.008L21.168 9.744L22.992 9.432L23.832 9.816L23.928 10.2L23.592 11.016L21.624 11.496L19.32 11.952L15.888 12.768L15.84 12.792L15.888 12.864L17.424 13.008L18.096 13.056H19.728L22.752 13.272L23.544 13.8L24 14.424L23.928 14.928L22.704 15.528L21.072 15.144L17.232 14.232L15.936 13.92H15.744V14.016L16.848 15.096L18.84 16.896L21.36 19.224L21.48 19.8L21.168 20.28L20.832 20.232L18.624 18.552L17.76 17.808L15.84 16.2H15.72V16.368L16.152 17.016L18.504 20.544L18.624 21.624L18.456 21.96L17.832 22.176L17.184 22.056L15.792 20.136L14.376 17.952L13.224 16.008L13.104 16.104L12.408 23.352L12.096 23.712L11.376 24Z" fill="#d97757"/>
456
+ </svg>
457
+ Claude Code
458
+ </span>
459
+ <div class="claude-dev-server-actions">
460
+ <button class="claude-dev-server-btn claude-dev-server-btn-inspect" title="Inspect Element">
461
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
462
+ <path d="M19 11V4a2 2 0 00-2-2H4a2 2 0 00-2 2v13a2 2 0 002 2h7"/>
463
+ <path d="M12 12l4.166 10 1.48-4.355L22 16.166 12 12z"/>
464
+ <path d="M18 18l3 3"/>
465
+ </svg>
466
+ Inspect
467
+ </button>
468
+ </div>
469
+ \`
472
470
 
473
- function createInspectBtn() {
474
- if (inspectBtn) return
475
- inspectBtn = document.createElement('button')
476
- inspectBtn.className = 'claude-dev-server-inspect-btn'
477
- inspectBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 11V4a2 2 0 00-2-2H4a2 2 0 00-2 2v13a2 2 0 002 2h7"/><path d="M12 12l4.166 10 1.48-4.355L22 16.166 12 12z"/><path d="M18 18l3 3"/></svg>'
478
- inspectBtn.title = 'Inspect Element (Cmd/Ctrl + Shift + I)'
479
- inspectBtn.setAttribute('data-dev-tool', 'true')
471
+ const inspectBtn = header.querySelector('.claude-dev-server-btn-inspect')
480
472
  inspectBtn.addEventListener('click', () => {
481
473
  if (isInspectMode) {
482
474
  disableInspectMode()
483
475
  } else {
484
- // \u70B9\u51FB Inspect \u65F6\u5148\u6536\u8D77\u9762\u677F
485
- if (isOpen) {
486
- togglePanel(false)
487
- }
488
476
  enableInspectMode()
489
477
  }
490
478
  })
491
- document.body.appendChild(inspectBtn)
492
- }
493
479
 
494
- function createOverlay() {
495
- if (overlay) return
496
- overlay = document.createElement('div')
497
- overlay.className = 'claude-dev-server-inspect-overlay'
480
+ // Terminal container
481
+ const terminal = document.createElement('div')
482
+ terminal.className = 'claude-dev-server-terminal'
483
+
484
+ // Ttyd iframe
485
+ ttydIframe = document.createElement('iframe')
486
+ ttydIframe.className = 'claude-dev-server-terminal-iframe'
487
+ ttydIframe.allow = 'clipboard-read; clipboard-write'
488
+
489
+ terminal.appendChild(ttydIframe)
490
+ leftPanel.appendChild(header)
491
+ leftPanel.appendChild(terminal)
492
+
493
+ // Divider - draggable
494
+ divider = document.createElement('div')
495
+ divider.className = 'claude-dev-server-divider'
496
+ setupDraggable(divider)
497
+
498
+ // Right panel - dev server
499
+ rightPanel = document.createElement('div')
500
+ rightPanel.className = 'claude-dev-server-right'
501
+
502
+ // Create dev server iframe with srcdoc
503
+ devIframe = document.createElement('iframe')
504
+ devIframe.className = 'claude-dev-server-dev-iframe'
505
+ // The HTML already has inspect script injected by server
506
+ devIframe.srcdoc = originalHtml
507
+
508
+ rightPanel.appendChild(devIframe)
509
+
510
+ // Assemble layout: Claude (left) | divider | dev server (right)
511
+ container.appendChild(leftPanel)
512
+ container.appendChild(divider)
513
+ container.appendChild(rightPanel)
514
+
515
+ // Replace body content
516
+ document.body.innerHTML = ''
517
+ document.body.appendChild(container)
498
518
  document.body.appendChild(overlay)
519
+
520
+ // Wait for iframe to load before setting up communication
521
+ devIframe.onload = () => {
522
+ console.log('[Claude Dev Server] Dev iframe loaded')
523
+ // Setup inspect mode communication
524
+ setupInspectCommunication()
525
+ }
526
+ }
527
+
528
+ function setupInspectCommunication() {
529
+ // Inspect mode is now handled entirely in the outer layer
530
+ // We'll access the iframe's document directly
531
+ }
532
+
533
+ function setupIframeInspectListeners() {
534
+ if (!devIframe || !devIframe.contentDocument) return
535
+
536
+ // Prevent duplicate listener registration
537
+ if (inspectListenersRegistered) {
538
+ return
539
+ }
540
+
541
+ const iframeDoc = devIframe.contentDocument
542
+ const iframeWindow = devIframe.contentWindow
543
+
544
+ const inspectHandler = (e) => {
545
+ if (!isInspectMode) return
546
+
547
+ e.preventDefault()
548
+ e.stopPropagation()
549
+
550
+ if (e.type === 'click') {
551
+ const el = e.target
552
+ const rect = el.getBoundingClientRect()
553
+ const className = el.className ? String(el.className) : ''
554
+ // Limit classnames to first 2-3 to avoid overly long selectors
555
+ const classNames = className.split(' ').filter(c => c).slice(0, 3)
556
+ const selector = el.tagName.toLowerCase() +
557
+ (el.id ? '#' + el.id : '') +
558
+ (classNames.length ? '.' + classNames.join('.') : '')
559
+
560
+ // Highlight element
561
+ if (overlay) {
562
+ overlay.innerHTML = ''
563
+
564
+ // Get iframe position on page
565
+ const iframeRect = devIframe.getBoundingClientRect()
566
+
567
+ // Calculate highlight position relative to main document
568
+ // Element rect is relative to iframe viewport, need to add iframe position
569
+ const highlightTop = iframeRect.top + rect.top
570
+ const highlightLeft = iframeRect.left + rect.left
571
+
572
+ const highlight = document.createElement('div')
573
+ highlight.className = 'claude-dev-server-highlight'
574
+ highlight.style.top = highlightTop + 'px'
575
+ highlight.style.left = highlightLeft + 'px'
576
+ highlight.style.width = rect.width + 'px'
577
+ highlight.style.height = rect.height + 'px'
578
+ highlight.dataset.element = selector
579
+ overlay.appendChild(highlight)
580
+ }
581
+
582
+ // Get source location and send to terminal
583
+ getSourceLocationFromElement(el).then(location => {
584
+ if (location) {
585
+ sendToTerminal(location)
586
+ }
587
+ disableInspectMode()
588
+ })
589
+ } else if (e.type === 'mousemove') {
590
+ const el = e.target
591
+ const rect = el.getBoundingClientRect()
592
+ const className = el.className ? String(el.className) : ''
593
+ // Limit classnames to first 2-3 to avoid overly long selectors
594
+ const classNames = className.split(' ').filter(c => c).slice(0, 3)
595
+ const selector = el.tagName.toLowerCase() +
596
+ (el.id ? '#' + el.id : '') +
597
+ (classNames.length ? '.' + classNames.join('.') : '')
598
+
599
+ if (overlay) {
600
+ overlay.innerHTML = ''
601
+
602
+ // Get iframe position on page
603
+ const iframeRect = devIframe.getBoundingClientRect()
604
+
605
+ // Calculate highlight position relative to main document
606
+ const highlightTop = iframeRect.top + rect.top
607
+ const highlightLeft = iframeRect.left + rect.left
608
+
609
+ const highlight = document.createElement('div')
610
+ highlight.className = 'claude-dev-server-highlight'
611
+ highlight.style.top = highlightTop + 'px'
612
+ highlight.style.left = highlightLeft + 'px'
613
+ highlight.style.width = rect.width + 'px'
614
+ highlight.style.height = rect.height + 'px'
615
+ highlight.dataset.element = selector
616
+ overlay.appendChild(highlight)
617
+ }
618
+ }
619
+ }
620
+
621
+ // Store handler reference for later removal
622
+ iframeDoc._claudeInspectHandler = inspectHandler
623
+
624
+ // Add event listeners in capture phase
625
+ iframeDoc.addEventListener('click', inspectHandler, true)
626
+ iframeDoc.addEventListener('mousemove', inspectHandler, true)
627
+
628
+ // Mark listeners as registered
629
+ inspectListenersRegistered = true
499
630
  }
500
631
 
501
- function highlightElement(el) {
502
- if (!overlay) return
503
- overlay.innerHTML = ''
504
- const rect = el.getBoundingClientRect()
505
- const highlight = document.createElement('div')
506
- highlight.className = 'claude-dev-server-highlight'
507
- highlight.style.top = rect.top + 'px'
508
- highlight.style.left = rect.left + 'px'
509
- highlight.style.width = rect.width + 'px'
510
- highlight.style.height = rect.height + 'px'
511
- const className = el.className ? String(el.className) : ''
512
- highlight.dataset.element = el.tagName.toLowerCase() +
513
- (el.id ? '#' + el.id : '') +
514
- (className ? '.' + className.split(' ').join('.') : '')
515
- overlay.appendChild(highlight)
632
+ async function handleInspectElement(elementInfo) {
633
+ // This is now handled directly in setupIframeInspectListeners
516
634
  }
517
635
 
518
- async function getSourceLocation(el) {
636
+ async function getSourceLocationFromElement(element) {
637
+ // Access the iframe's document to detect Next.js chunks
638
+ const iframeDoc = devIframe?.contentDocument || document
639
+
519
640
  // Early check: detect Next.js by checking for _next/static chunks
520
- const hasNextJsChunks = Array.from(document.querySelectorAll('script[src]'))
641
+ const hasNextJsChunks = Array.from(iframeDoc.querySelectorAll('script[src]'))
521
642
  .some(script => script.getAttribute('src')?.includes('/_next/static/chunks/'))
522
643
 
523
644
  if (hasNextJsChunks) {
@@ -541,16 +662,37 @@ var CLIENT_SCRIPT = `
541
662
  const result = await response.json()
542
663
  console.log('[Claude Dev Server] Lookup result:', result)
543
664
  if (result.file) {
544
- const elClassName = el.className ? String(el.className) : ''
545
- const selector = el.tagName.toLowerCase() +
546
- (el.id ? '#' + el.id : '') +
547
- (elClassName ? '.' + elClassName.split(' ').filter(c => c).join('.') : '')
665
+ const elClassName = element.className ? String(element.className) : ''
666
+ // Limit classnames to first 2-3 to avoid overly long selectors
667
+ const classNames = elClassName.split(' ').filter(c => c).slice(0, 3)
668
+ const selector = element.tagName.toLowerCase() +
669
+ (element.id ? '#' + element.id : '') +
670
+ (classNames.length ? '.' + classNames.join('.') : '')
671
+
672
+ // Extract text content from element
673
+ let textContent = ''
674
+ if (element.nodeType === Node.TEXT_NODE) {
675
+ textContent = element.textContent ? element.textContent.trim().substring(0, 50) : ''
676
+ } else if (element.childNodes.length === 1 && element.childNodes[0].nodeType === Node.TEXT_NODE) {
677
+ textContent = element.childNodes[0].textContent ? element.childNodes[0].textContent.trim().substring(0, 50) : ''
678
+ } else {
679
+ for (const child of element.childNodes) {
680
+ if (child.nodeType === Node.TEXT_NODE && child.textContent && child.textContent.trim()) {
681
+ textContent = child.textContent.trim().substring(0, 50)
682
+ break
683
+ }
684
+ }
685
+ }
686
+ if (textContent && textContent.length >= 50) {
687
+ textContent += '...'
688
+ }
548
689
 
549
690
  return {
550
691
  url: result.file,
551
692
  line: result.line || undefined,
552
693
  column: result.column || undefined,
553
694
  selector: selector,
695
+ text: textContent,
554
696
  hint: 'File: ' + result.file
555
697
  }
556
698
  }
@@ -683,12 +825,12 @@ var CLIENT_SCRIPT = `
683
825
  }
684
826
 
685
827
  // Try React DevTools - handle React 18's randomized suffix
686
- const elKeys = Object.keys(el)
828
+ const elKeys = Object.keys(element)
687
829
  let fiberKey = elKeys.find(k => k === '__reactFiber__' || k === '__reactInternalInstance' || k.indexOf('__reactFiber') === 0)
688
830
  let propsKey = elKeys.find(k => k === '__reactProps__' || k.indexOf('__reactProps') === 0)
689
831
 
690
832
  if (fiberKey) {
691
- const fiber = el[fiberKey]
833
+ const fiber = element[fiberKey]
692
834
  console.log('[Claude Dev Server] Found fiber at key:', fiberKey)
693
835
 
694
836
  // Log fiber structure for debugging
@@ -836,7 +978,7 @@ var CLIENT_SCRIPT = `
836
978
 
837
979
  // Try Vue component
838
980
  if (!sourceFile) {
839
- const vueComponent = el.__vueParentComponent || el.__vnode
981
+ const vueComponent = element.__vueParentComponent || element.__vnode
840
982
  if (vueComponent) {
841
983
  const type = vueComponent.type || vueComponent.component
842
984
  if (type && type.__file) {
@@ -849,7 +991,7 @@ var CLIENT_SCRIPT = `
849
991
  // Try Vite's HMR source map
850
992
  if (!sourceFile) {
851
993
  // Look for data-vite-dev-id or similar attributes
852
- const viteId = el.getAttribute('data-vite-dev-id')
994
+ const viteId = element.getAttribute('data-vite-dev-id')
853
995
  if (viteId) {
854
996
  // Extract file path from viteId (remove query params if any)
855
997
  const queryIndex = viteId.indexOf('?')
@@ -860,22 +1002,22 @@ var CLIENT_SCRIPT = `
860
1002
 
861
1003
  // Check for inline script
862
1004
  if (!sourceFile) {
863
- const inlineScripts = document.querySelectorAll('script:not([src])')
1005
+ const inlineScripts = iframeDoc.querySelectorAll('script:not([src])')
864
1006
  if (inlineScripts.length > 0) {
865
1007
  const pathParts = window.location.pathname.split('/')
866
1008
  const fileName = pathParts[pathParts.length - 1] || 'index.html'
867
1009
  sourceFile = '/' + fileName
868
1010
 
869
- const html = document.documentElement.outerHTML
870
- const elId = el.id ? el.id : ''
871
- const elClass = el.className ? el.className : ''
1011
+ const html = iframeDoc.documentElement.outerHTML
1012
+ const elId = element.id ? element.id : ''
1013
+ const elClass = element.className ? element.className : ''
872
1014
  let elPattern
873
1015
  if (elId) {
874
1016
  elPattern = 'id="' + elId + '"'
875
1017
  } else if (elClass) {
876
1018
  elPattern = 'class="' + elClass.split(' ')[0] + '"'
877
1019
  } else {
878
- elPattern = el.tagName.toLowerCase()
1020
+ elPattern = element.tagName.toLowerCase()
879
1021
  }
880
1022
 
881
1023
  const matchIndex = html.indexOf(elPattern)
@@ -888,7 +1030,7 @@ var CLIENT_SCRIPT = `
888
1030
 
889
1031
  // Find main script
890
1032
  if (!sourceFile) {
891
- const scripts = document.querySelectorAll('script[src]')
1033
+ const scripts = iframeDoc.querySelectorAll('script[src]')
892
1034
  for (const script of scripts) {
893
1035
  const src = script.getAttribute('src')
894
1036
  if (src && !src.includes('cdn') && !src.includes('node_modules') &&
@@ -906,155 +1048,227 @@ var CLIENT_SCRIPT = `
906
1048
  sourceFile = '/' + fileName
907
1049
  }
908
1050
 
909
- const elClassName = el.className ? String(el.className) : ''
910
- const selector = el.tagName.toLowerCase() +
911
- (el.id ? '#' + el.id : '') +
912
- (elClassName ? '.' + elClassName.split(' ').filter(c => c).join('.') : '')
1051
+ const elClassName = element.className ? String(element.className) : ''
1052
+ // Limit classnames to first 2-3 to avoid overly long selectors
1053
+ const classNames = elClassName.split(' ').filter(c => c).slice(0, 3)
1054
+ const selector = element.tagName.toLowerCase() +
1055
+ (element.id ? '#' + element.id : '') +
1056
+ (classNames.length ? '.' + classNames.join('.') : '')
1057
+
1058
+ // Get text content for better context
1059
+ let textContent = ''
1060
+ if (element.nodeType === Node.TEXT_NODE) {
1061
+ textContent = element.textContent ? element.textContent.trim().substring(0, 50) : ''
1062
+ } else if (element.childNodes.length === 1 && element.childNodes[0].nodeType === Node.TEXT_NODE) {
1063
+ textContent = element.childNodes[0].textContent ? element.childNodes[0].textContent.trim().substring(0, 50) : ''
1064
+ } else {
1065
+ // Try to get first text node from children
1066
+ for (const child of element.childNodes) {
1067
+ if (child.nodeType === Node.TEXT_NODE && child.textContent && child.textContent.trim()) {
1068
+ textContent = child.textContent.trim().substring(0, 50)
1069
+ break
1070
+ }
1071
+ }
1072
+ }
1073
+ if (textContent && textContent.length >= 50) {
1074
+ textContent += '...'
1075
+ }
913
1076
 
914
1077
  return {
915
1078
  url: sourceFile,
916
1079
  line: sourceLine,
917
1080
  column: sourceColumn,
918
1081
  selector: selector,
1082
+ text: textContent,
919
1083
  hint: sourceFile ? 'File: ' + sourceFile : 'Element: ' + selector
920
1084
  }
921
1085
  }
922
1086
 
923
- function enableInspectMode() {
924
- isInspectMode = true
925
- overlay && overlay.classList.add('active')
926
- document.body.style.cursor = 'crosshair'
927
-
928
- // \u66F4\u65B0\u60AC\u6D6E inspect \u6309\u94AE\u72B6\u6001
929
- if (inspectBtn) inspectBtn.classList.add('active')
930
-
931
- // \u68C0\u67E5\u5143\u7D20\u662F\u5426\u5C5E\u4E8E dev tools
932
- function isDevToolElement(el) {
933
- if (!el) return false
934
- // \u68C0\u67E5\u5143\u7D20\u672C\u8EAB
935
- if (el.dataset && el.dataset.devTool === 'true') return true
936
- // \u68C0\u67E5\u7236\u5143\u7D20
937
- let parent = el.parentElement
938
- while (parent) {
939
- if (parent.dataset && parent.dataset.devTool === 'true') return true
940
- parent = parent.parentElement
1087
+ function setupDraggable(divider) {
1088
+ let startX = 0
1089
+ let startWidth = 0
1090
+ let containerWidth = 0
1091
+
1092
+ // Remove existing listeners to avoid duplicates
1093
+ divider.removeEventListener('mousedown', startDrag)
1094
+ divider.removeEventListener('touchstart', startDrag)
1095
+
1096
+ divider.addEventListener('mousedown', startDrag)
1097
+ divider.addEventListener('touchstart', startDrag)
1098
+
1099
+ function startDrag(e) {
1100
+ isDragging = true
1101
+ divider.classList.add('dragging')
1102
+
1103
+ // Disable pointer events on iframes to prevent interference
1104
+ const iframes = document.querySelectorAll('iframe')
1105
+ iframes.forEach(iframe => iframe.style.pointerEvents = 'none')
1106
+
1107
+ const clientX = e.touches ? e.touches[0].clientX : e.clientX
1108
+ startX = clientX
1109
+
1110
+ const container = document.querySelector('.claude-dev-server-container')
1111
+ const leftPanelEl = document.querySelector('.claude-dev-server-left')
1112
+
1113
+ if (container && leftPanelEl) {
1114
+ containerWidth = container.offsetWidth
1115
+ startWidth = leftPanelEl.offsetWidth
941
1116
  }
942
- return false
1117
+
1118
+ e.preventDefault()
943
1119
  }
944
1120
 
945
- const onMouseMove = async (e) => {
946
- if (!isInspectMode) return
947
- const el = document.elementFromPoint(e.clientX, e.clientY)
948
- // \u5FFD\u7565 dev tools \u5143\u7D20\u548C overlay
949
- if (el && el !== overlay && !isDevToolElement(el) && (!overlay || !overlay.contains(el))) {
950
- highlightElement(el)
1121
+ function drag(e) {
1122
+ if (!isDragging) return
1123
+
1124
+ // Safety check: if mouse button is not pressed, end drag
1125
+ if (e.buttons === 0 && !e.touches) {
1126
+ endDrag()
1127
+ return
1128
+ }
1129
+
1130
+ const clientX = e.touches ? e.touches[0].clientX : e.clientX
1131
+ const deltaX = clientX - startX
1132
+
1133
+ // Calculate new width in pixels, then convert to percentage
1134
+ let newWidth = startWidth + deltaX
1135
+ let percentage = (newWidth / containerWidth) * 100
1136
+
1137
+ // Clamp between 20% and 80%
1138
+ const clamped = Math.max(20, Math.min(80, percentage))
1139
+
1140
+ const leftPanelEl = document.querySelector('.claude-dev-server-left')
1141
+ if (leftPanelEl) {
1142
+ leftPanelEl.style.flex = 'none'
1143
+ leftPanelEl.style.width = clamped + '%'
951
1144
  }
952
1145
  }
953
1146
 
954
- const onClick = async (e) => {
955
- if (!isInspectMode) return
956
- e.preventDefault()
957
- e.stopPropagation()
1147
+ function endDrag() {
1148
+ if (isDragging) {
1149
+ isDragging = false
1150
+ divider.classList.remove('dragging')
958
1151
 
959
- const el = document.elementFromPoint(e.clientX, e.clientY)
960
- // \u5FFD\u7565 dev tools \u5143\u7D20\u548C overlay
961
- if (el && el !== overlay && !isDevToolElement(el) && (!overlay || !overlay.contains(el))) {
962
- const location = await getSourceLocation(el)
963
- if (location) {
964
- await inspectElement(location, el)
965
- }
966
- disableInspectMode()
1152
+ // Re-enable pointer events on iframes
1153
+ const iframes = document.querySelectorAll('iframe')
1154
+ iframes.forEach(iframe => iframe.style.pointerEvents = '')
967
1155
  }
968
1156
  }
969
1157
 
970
- document.addEventListener('mousemove', onMouseMove, true)
971
- document.addEventListener('click', onClick, true)
1158
+ // Use capture phase to ensure events are caught
1159
+ // Remove old listeners first to prevent duplicates
1160
+ document.removeEventListener('mousemove', drag, true)
1161
+ document.removeEventListener('touchmove', drag, true)
1162
+ document.removeEventListener('mouseup', endDrag, true)
1163
+ document.removeEventListener('touchend', endDrag, true)
1164
+
1165
+ document.addEventListener('mousemove', drag, true)
1166
+ document.addEventListener('touchmove', drag, { passive: false, capture: true })
1167
+ document.addEventListener('mouseup', endDrag, true)
1168
+ document.addEventListener('touchend', endDrag, true)
1169
+ }
972
1170
 
973
- if (overlay) overlay._inspectHandlers = { onMouseMove, onClick }
1171
+ function createOverlay() {
1172
+ if (overlay) return
1173
+ overlay = document.createElement('div')
1174
+ overlay.className = 'claude-dev-server-inspect-overlay'
1175
+ document.body.appendChild(overlay)
1176
+ }
1177
+
1178
+ function enableInspectMode() {
1179
+ isInspectMode = true
1180
+ if (overlay) overlay.classList.add('active')
1181
+ // Setup inspect listeners on the iframe
1182
+ setupIframeInspectListeners()
1183
+ // Change cursor in iframe
1184
+ if (devIframe && devIframe.contentDocument && devIframe.contentDocument.body) {
1185
+ devIframe.contentDocument.body.style.cursor = 'crosshair'
1186
+ }
1187
+ const btn = document.querySelector('.claude-dev-server-btn-inspect')
1188
+ if (btn) btn.classList.add('active')
974
1189
  }
975
1190
 
976
1191
  function disableInspectMode() {
977
1192
  isInspectMode = false
978
- if (overlay) overlay.classList.remove('active')
979
- document.body.style.cursor = ''
980
- if (overlay) overlay.innerHTML = ''
1193
+ if (overlay) {
1194
+ overlay.classList.remove('active')
1195
+ overlay.innerHTML = ''
1196
+ }
1197
+ // Restore cursor in iframe
1198
+ if (devIframe && devIframe.contentDocument && devIframe.contentDocument.body) {
1199
+ devIframe.contentDocument.body.style.cursor = ''
1200
+ }
1201
+ const btn = document.querySelector('.claude-dev-server-btn-inspect')
1202
+ if (btn) btn.classList.remove('active')
981
1203
 
982
- // \u66F4\u65B0\u60AC\u6D6E inspect \u6309\u94AE\u72B6\u6001
983
- if (inspectBtn) inspectBtn.classList.remove('active')
1204
+ // Remove inspect listeners to free up resources
1205
+ removeIframeInspectListeners()
1206
+ }
984
1207
 
985
- const btn = panel && panel.querySelector('.claude-dev-server-btn-inspect')
986
- if (btn) btn.classList.remove('active')
1208
+ function removeIframeInspectListeners() {
1209
+ if (!devIframe || !devIframe.contentDocument) return
987
1210
 
988
- const handlers = overlay && overlay._inspectHandlers
989
- if (handlers) {
990
- document.removeEventListener('mousemove', handlers.onMouseMove, true)
991
- document.removeEventListener('click', handlers.onClick, true)
1211
+ const iframeDoc = devIframe.contentDocument
1212
+ const existingHandler = iframeDoc._claudeInspectHandler
1213
+
1214
+ if (existingHandler) {
1215
+ iframeDoc.removeEventListener('click', existingHandler, true)
1216
+ iframeDoc.removeEventListener('mousemove', existingHandler, true)
1217
+ delete iframeDoc._claudeInspectHandler
1218
+ inspectListenersRegistered = false
992
1219
  }
993
1220
  }
994
1221
 
995
- async function inspectElement(location, el) {
996
- // \u6784\u5EFA\u683C\u5F0F\u5316\u7684 prompt
1222
+ async function sendToTerminal(location) {
1223
+ // Use format: @filename <selector> "text content" (without line number)
997
1224
  const filePath = location.url || location.file || 'unknown'
998
- const tagName = el.tagName ? el.tagName.toLowerCase() : ''
999
- const id = el.id ? '#' + el.id : ''
1000
-
1001
- // \u5BF9\u4E8E Tailwind CSS \u7B49\u5927\u91CF class \u7684\u60C5\u51B5\uFF0C\u53EA\u53D6\u524D 2-3 \u4E2A class
1002
- let className = ''
1003
- if (el.className) {
1004
- const classes = String(el.className).split(' ').filter(c => c)
1005
- // \u5982\u679C class \u592A\u591A\uFF08\u53EF\u80FD\u662F Tailwind\uFF09\uFF0C\u53EA\u53D6\u524D 2 \u4E2A
1006
- if (classes.length > 5) {
1007
- className = '.' + classes.slice(0, 2).join('.')
1008
- } else {
1009
- className = '.' + classes.join('.')
1010
- }
1011
- }
1012
1225
 
1013
- const selector = tagName + id + className
1226
+ // Build selector - handle Tailwind CSS classes by limiting
1227
+ const tagName = location.selector ? location.selector.split(/[.#]/)[0] : 'div'
1228
+ let selector = location.selector || tagName
1014
1229
 
1015
- // \u83B7\u53D6\u5143\u7D20\u7684\u6587\u672C\u5185\u5BB9\uFF08\u5982\u679C\u662F\u6587\u672C\u8282\u70B9\u6216\u77ED\u6587\u672C\u5143\u7D20\uFF09
1230
+ // Get text content for better context
1016
1231
  let textContent = ''
1017
- if (el.nodeType === Node.TEXT_NODE) {
1018
- textContent = el.textContent ? el.textContent.trim().substring(0, 50) : ''
1019
- } else if (el.childNodes.length === 1 && el.childNodes[0].nodeType === Node.TEXT_NODE) {
1020
- textContent = el.childNodes[0].textContent ? el.childNodes[0].textContent.trim().substring(0, 50) : ''
1232
+ if (location.text) {
1233
+ textContent = location.text
1234
+ } else if (location.hint && location.hint.includes('File:')) {
1235
+ // No text content available
1021
1236
  }
1022
1237
 
1023
- // Check if file is in node_modules - if so, skip the @filename part
1024
- const isNodeModules = filePath.includes('node_modules')
1025
-
1026
- // \u683C\u5F0F: @filename:line <selector> "text content" \u6216 @filename <selector> "text content"
1027
- // \u5982\u679C\u662F node_modules \u6587\u4EF6\uFF0C\u53EA\u4FDD\u7559 <selector> "text content"
1028
- let prompt = ''
1029
- if (isNodeModules) {
1030
- // Skip @filename for node_modules, just show element and text
1031
- prompt = \`<\${selector}>\`
1032
- if (textContent) {
1033
- prompt += \` "\${textContent}"\`
1034
- }
1035
- console.log('[Claude Dev Server] Skipping node_modules path:', filePath)
1036
- } else {
1037
- const linePart = location.line ? \`:\${location.line}\` : ''
1038
- prompt = \`@\${filePath}\${linePart} <\${selector}>\`
1039
- if (textContent) {
1040
- prompt += \` "\${textContent}"\`
1041
- }
1238
+ // Format: @filename <selector> "text content" (no line number)
1239
+ let prompt = \`@\${filePath} <\${selector}>\`
1240
+ if (textContent) {
1241
+ prompt += \` "\${textContent}"\`
1242
+ }
1243
+
1244
+ console.log('[Claude Dev Server] Sending to terminal:', prompt)
1245
+
1246
+ // Store the interval ID so we can clear it if needed
1247
+ if (!window._claudeSendRetryInterval) {
1248
+ window._claudeSendRetryInterval = null
1042
1249
  }
1043
1250
 
1044
- // \u53D1\u9001\u6210\u529F\u540E\u5C55\u5F00\u9762\u677F
1045
- const sendToTerminal = () => {
1046
- if (!terminalIframe || !terminalIframe.contentWindow) {
1251
+ // Clear any existing retry interval
1252
+ if (window._claudeSendRetryInterval) {
1253
+ clearInterval(window._claudeSendRetryInterval)
1254
+ window._claudeSendRetryInterval = null
1255
+ }
1256
+
1257
+ // Send to terminal with retry logic
1258
+ const sendToTerminalInternal = () => {
1259
+ if (!ttydIframe || !ttydIframe.contentWindow) {
1047
1260
  return false
1048
1261
  }
1049
1262
 
1050
- const win = terminalIframe.contentWindow
1051
-
1052
- // Check if sendToTerminal function is available
1263
+ const win = ttydIframe.contentWindow
1053
1264
  if (win.sendToTerminal) {
1054
1265
  win.sendToTerminal(prompt)
1055
1266
  console.log('[Claude Dev Server] Sent via sendToTerminal:', prompt)
1056
- // \u53D1\u9001\u6210\u529F\u540E\u5C55\u5F00\u9762\u677F
1057
- setTimeout(() => togglePanel(true), 100)
1267
+ // Clear interval on success
1268
+ if (window._claudeSendRetryInterval) {
1269
+ clearInterval(window._claudeSendRetryInterval)
1270
+ window._claudeSendRetryInterval = null
1271
+ }
1058
1272
  return true
1059
1273
  }
1060
1274
 
@@ -1062,17 +1276,18 @@ var CLIENT_SCRIPT = `
1062
1276
  }
1063
1277
 
1064
1278
  // Try immediately
1065
- if (sendToTerminal()) {
1279
+ if (sendToTerminalInternal()) {
1066
1280
  return
1067
1281
  }
1068
1282
 
1069
1283
  // If not ready, wait and retry
1070
1284
  let attempts = 0
1071
1285
  const maxAttempts = 50
1072
- const retryInterval = setInterval(() => {
1286
+ window._claudeSendRetryInterval = setInterval(() => {
1073
1287
  attempts++
1074
- if (sendToTerminal() || attempts >= maxAttempts) {
1075
- clearInterval(retryInterval)
1288
+ if (sendToTerminalInternal() || attempts >= maxAttempts) {
1289
+ clearInterval(window._claudeSendRetryInterval)
1290
+ window._claudeSendRetryInterval = null
1076
1291
  if (attempts >= maxAttempts) {
1077
1292
  console.warn('[Claude Dev Server] Could not send to terminal after retries')
1078
1293
  }
@@ -1081,93 +1296,12 @@ var CLIENT_SCRIPT = `
1081
1296
  }
1082
1297
 
1083
1298
  function loadTerminalIframe(ttydUrl) {
1084
- if (!terminalContainer || terminalIframe) return
1299
+ if (!ttydIframe) return
1085
1300
  ttydWsUrl = ttydUrl
1086
-
1087
- // Create iframe pointing to ttyd/index.html with ttyd WebSocket URL
1088
- terminalIframe = document.createElement('iframe')
1089
- // Pass the ttyd WebSocket URL as query param
1090
- terminalIframe.src = '/ttyd/index.html?ws=' + encodeURIComponent(ttydUrl)
1091
- terminalIframe.allow = 'clipboard-read; clipboard-write'
1092
-
1093
- // Load event - iframe is ready
1094
- terminalIframe.onload = () => {
1301
+ ttydIframe.src = '/ttyd/index.html?ws=' + encodeURIComponent(ttydUrl)
1302
+ ttydIframe.onload = () => {
1095
1303
  console.log('[Claude Dev Server] Terminal iframe loaded')
1096
1304
  }
1097
-
1098
- terminalContainer.appendChild(terminalIframe)
1099
- }
1100
-
1101
- function createPanel() {
1102
- if (panel) return
1103
-
1104
- panel = document.createElement('div')
1105
- panel.className = 'claude-dev-server-panel'
1106
- panel.innerHTML = \`
1107
- <div class="claude-dev-server-header">
1108
- <span class="claude-dev-server-title">
1109
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none">
1110
- <path d="M11.376 24L10.776 23.544L10.44 22.8L10.776 21.312L11.16 19.392L11.472 17.856L11.76 15.96L11.928 15.336L11.904 15.288L11.784 15.312L10.344 17.28L8.16 20.232L6.432 22.056L6.024 22.224L5.304 21.864L5.376 21.192L5.784 20.616L8.16 17.568L9.6 15.672L10.536 14.592L10.512 14.448H10.464L4.128 18.576L3 18.72L2.496 18.264L2.568 17.52L2.808 17.28L4.704 15.96L9.432 13.32L9.504 13.08L9.432 12.96H9.192L8.4 12.912L5.712 12.84L3.384 12.744L1.104 12.624L0.528 12.504L0 11.784L0.048 11.424L0.528 11.112L1.224 11.16L2.736 11.28L5.016 11.424L6.672 11.52L9.12 11.784H9.504L9.552 11.616L9.432 11.52L9.336 11.424L6.96 9.84L4.416 8.16L3.072 7.176L2.352 6.672L1.992 6.216L1.848 5.208L2.496 4.488L3.384 4.56L3.6 4.608L4.488 5.304L6.384 6.768L8.88 8.616L9.24 8.904L9.408 8.808V8.736L9.24 8.472L7.896 6.024L6.456 3.528L5.808 2.496L5.64 1.872C5.576 1.656 5.544 1.416 5.544 1.152L6.288 0.144001L6.696 0L7.704 0.144001L8.112 0.504001L8.736 1.92L9.72 4.152L11.28 7.176L11.736 8.088L11.976 8.904L12.072 9.168H12.24V9.024L12.36 7.296L12.6 5.208L12.84 2.52L12.912 1.752L13.296 0.840001L14.04 0.360001L14.616 0.624001L15.096 1.32L15.024 1.752L14.76 3.6L14.184 6.504L13.824 8.472H14.04L14.28 8.208L15.264 6.912L16.92 4.848L17.64 4.032L18.504 3.12L19.056 2.688H20.088L20.832 3.816L20.496 4.992L19.44 6.336L18.552 7.464L17.28 9.168L16.512 10.536L16.584 10.632H16.752L19.608 10.008L21.168 9.744L22.992 9.432L23.832 9.816L23.928 10.2L23.592 11.016L21.624 11.496L19.32 11.952L15.888 12.768L15.84 12.792L15.888 12.864L17.424 13.008L18.096 13.056H19.728L22.752 13.272L23.544 13.8L24 14.424L23.928 14.928L22.704 15.528L21.072 15.144L17.232 14.232L15.936 13.92H15.744V14.016L16.848 15.096L18.84 16.896L21.36 19.224L21.48 19.8L21.168 20.28L20.832 20.232L18.624 18.552L17.76 17.808L15.84 16.2H15.72V16.368L16.152 17.016L18.504 20.544L18.624 21.624L18.456 21.96L17.832 22.176L17.184 22.056L15.792 20.136L14.376 17.952L13.224 16.008L13.104 16.104L12.408 23.352L12.096 23.712L11.376 24Z" fill="#d97757"/>
1111
- </svg>
1112
- Claude Code
1113
- </span>
1114
- <div class="claude-dev-server-actions">
1115
- <button class="claude-dev-server-btn claude-dev-server-btn-inspect" title="Inspect Element">
1116
- <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1117
- <path d="M19 11V4a2 2 0 00-2-2H4a2 2 0 00-2 2v13a2 2 0 002 2h7"/>
1118
- <path d="M12 12l4.166 10 1.48-4.355L22 16.166 12 12z"/>
1119
- <path d="M18 18l3 3"/>
1120
- </svg>
1121
- Inspect
1122
- </button>
1123
- </div>
1124
- <button class="claude-dev-server-close" aria-label="Close" title="Minimize to icon">&times;</button>
1125
- </div>
1126
- <div class="claude-dev-server-terminal"></div>
1127
- \`
1128
-
1129
- document.body.appendChild(panel)
1130
-
1131
- terminalContainer = panel.querySelector('.claude-dev-server-terminal')
1132
- const closeBtn = panel.querySelector('.claude-dev-server-close')
1133
- const inspectBtn = panel.querySelector('.claude-dev-server-btn-inspect')
1134
-
1135
- closeBtn && closeBtn.addEventListener('click', () => togglePanel(false))
1136
-
1137
- inspectBtn && inspectBtn.addEventListener('click', () => {
1138
- if (isInspectMode) {
1139
- disableInspectMode()
1140
- } else {
1141
- // \u70B9\u51FB Inspect \u65F6\u5148\u6536\u8D77\u9762\u677F
1142
- if (isOpen) {
1143
- togglePanel(false)
1144
- }
1145
- enableInspectMode()
1146
- }
1147
- })
1148
- }
1149
-
1150
- function togglePanel(force) {
1151
- createPanel()
1152
- if (typeof force === 'boolean') {
1153
- isOpen = force
1154
- } else {
1155
- isOpen = !isOpen
1156
- }
1157
-
1158
- if (panel) panel.classList.toggle('open', isOpen)
1159
- if (toggleBtn) toggleBtn.classList.toggle('hidden', isOpen)
1160
-
1161
- if (isOpen) {
1162
- // Focus iframe when panel opens
1163
- if (terminalIframe && terminalIframe.contentWindow) {
1164
- setTimeout(() => {
1165
- if (terminalIframe.contentWindow.terminalInstance) {
1166
- terminalIframe.contentWindow.terminalInstance.focus()
1167
- }
1168
- }, 100)
1169
- }
1170
- }
1171
1305
  }
1172
1306
 
1173
1307
  function connect(port) {
@@ -1184,10 +1318,6 @@ var CLIENT_SCRIPT = `
1184
1318
  console.log('[Claude Dev Server] Received message:', msg.type, msg)
1185
1319
  if (msg.type === 'ready' && msg.ttydUrl) {
1186
1320
  loadTerminalIframe(msg.ttydUrl)
1187
- } else if (msg.type === 'inspectResult') {
1188
- if (msg.location) {
1189
- showContextPanel(msg.location, null, false)
1190
- }
1191
1321
  }
1192
1322
  } catch (err) {
1193
1323
  console.error('[Claude Dev Server] Message parse error:', err)
@@ -1207,12 +1337,13 @@ var CLIENT_SCRIPT = `
1207
1337
  initWhenReady()
1208
1338
 
1209
1339
  document.addEventListener('keydown', (e) => {
1210
- if ((e.metaKey || e.ctrlKey) && e.code === 'Backquote') {
1340
+ if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key === 'I') {
1211
1341
  e.preventDefault()
1212
- togglePanel()
1213
- }
1214
- if (e.key === 'Escape' && isOpen) {
1215
- togglePanel(false)
1342
+ if (isInspectMode) {
1343
+ disableInspectMode()
1344
+ } else {
1345
+ enableInspectMode()
1346
+ }
1216
1347
  }
1217
1348
  if (e.key === 'Escape' && isInspectMode) {
1218
1349
  disableInspectMode()
@@ -1501,21 +1632,59 @@ async function handleTurbopackLookup(projectRoot, pagePath, targetPort) {
1501
1632
  return { error: String(err) };
1502
1633
  }
1503
1634
  }
1635
+ function isHtmlPageRequest(req) {
1636
+ const accept = req.headers.accept || "";
1637
+ const url = req.url || "";
1638
+ if (!accept.includes("text/html")) {
1639
+ return false;
1640
+ }
1641
+ if (url.startsWith("/@") || url.startsWith("/_next/") || url.startsWith("/ttyd") || url.startsWith("/dev.html")) {
1642
+ return false;
1643
+ }
1644
+ return true;
1645
+ }
1504
1646
  function createProxyServer(targetPort, wsPort, projectRoot) {
1505
1647
  const moduleDir = dirname(fileURLToPath(import.meta.url));
1506
1648
  const assetsPath = join(moduleDir, "assets");
1507
1649
  let ttydHtml;
1508
1650
  let ttydBridgeJs;
1651
+ let devHtml;
1509
1652
  try {
1510
1653
  ttydHtml = readFileSync(join(assetsPath, "ttyd-terminal.html"), "utf-8");
1511
1654
  ttydBridgeJs = readFileSync(join(assetsPath, "ttyd-bridge.js"), "utf-8");
1655
+ devHtml = readFileSync(join(assetsPath, "dev.html"), "utf-8");
1512
1656
  } catch (e) {
1513
- console.error("[Claude Dev Server] Failed to read ttyd assets from", assetsPath);
1657
+ console.error("[Claude Dev Server] Failed to read assets from", assetsPath);
1514
1658
  console.error("[Claude Dev Server] moduleDir:", moduleDir);
1515
1659
  console.error("[Claude Dev Server] Error:", e.message);
1516
- throw new Error("ttyd assets not found. Please run `npm run build` first.");
1660
+ throw new Error("Assets not found. Please run `npm run build` first.");
1517
1661
  }
1518
1662
  const server = http.createServer((req, res) => {
1663
+ const referer = req.headers.referer || "";
1664
+ const isFromDevPage = referer.includes("dev.html");
1665
+ if (req.url?.startsWith("/dev.html")) {
1666
+ const urlParams = new URL(req.url || "", `http://${req.headers.host}`);
1667
+ const originalPath = urlParams.searchParams.get("path") || "/";
1668
+ const host = req.headers.host || "localhost:3000";
1669
+ const origin = `http://${host}`;
1670
+ const modifiedDevHtml = devHtml.replace(
1671
+ /__CLAUDE_IFRAME_SRC__/g,
1672
+ `${origin}${originalPath}`
1673
+ ).replace(
1674
+ /__CLAUDE_ORIGINAL_PATH__/g,
1675
+ originalPath
1676
+ );
1677
+ res.setHeader("Content-Type", "text/html");
1678
+ res.end(modifiedDevHtml);
1679
+ return;
1680
+ }
1681
+ if (!isFromDevPage && isHtmlPageRequest(req)) {
1682
+ const currentPath = req.url || "/";
1683
+ const devPageUrl = `/dev.html?path=${encodeURIComponent(currentPath)}`;
1684
+ res.writeHead(302, { "Location": devPageUrl });
1685
+ res.end();
1686
+ return;
1687
+ }
1519
1688
  if (req.url === "/@claude-port") {
1520
1689
  res.setHeader("Content-Type", "application/json");
1521
1690
  res.end(JSON.stringify({ port: wsPort }));
@@ -1577,6 +1746,7 @@ function createProxyServer(targetPort, wsPort, projectRoot) {
1577
1746
  }
1578
1747
  const proxyHeaders = { ...req.headers };
1579
1748
  delete proxyHeaders["accept-encoding"];
1749
+ const shouldInject = !isFromDevPage;
1580
1750
  const options = {
1581
1751
  hostname: "localhost",
1582
1752
  port: targetPort,
@@ -1585,7 +1755,7 @@ function createProxyServer(targetPort, wsPort, projectRoot) {
1585
1755
  headers: proxyHeaders
1586
1756
  };
1587
1757
  const proxyReq = http.request(options, (proxyRes) => {
1588
- if (proxyRes.headers["content-type"]?.includes("text/html")) {
1758
+ if (proxyRes.headers["content-type"]?.includes("text/html") && shouldInject) {
1589
1759
  const body = [];
1590
1760
  proxyRes.on("data", (chunk) => body.push(chunk));
1591
1761
  proxyRes.on("end", () => {
@@ -1667,20 +1837,33 @@ function createProxyServer(targetPort, wsPort, projectRoot) {
1667
1837
  return server;
1668
1838
  }
1669
1839
  function injectScripts(html, wsPort, projectRoot) {
1670
- const projectRootScript = `<script>window.__CLAUDE_PROJECT_ROOT__ = ${JSON.stringify(projectRoot)}</script>`;
1671
- return html.replace(
1672
- "</head>",
1673
- `<!-- Claude Dev Server -->
1674
- ${CLIENT_STYLES}
1675
- ${projectRootScript}
1676
- <script type="module">${CLIENT_SCRIPT}</script>
1677
- </head>`
1678
- ).replace(
1679
- /wsPort:\s*\d+/,
1680
- `wsPort: ${wsPort}`
1681
- );
1840
+ if (html.includes("claude-dev-server-container") || html.includes("__CLAUDE_ORIGINAL_HTML__")) {
1841
+ console.log("[Claude Dev Server] HTML already injected, returning as-is");
1842
+ return html;
1843
+ }
1844
+ const hasOurScripts = html.includes("CLIENT_SCRIPT") || html.includes("claude-dev-server-container");
1845
+ if (hasOurScripts) {
1846
+ console.log("[Claude Dev Server] Warning: Original HTML already has our scripts");
1847
+ }
1848
+ const modifiedHtml = html;
1849
+ console.log("[Claude Dev Server] Creating split layout, original HTML length:", html.length);
1850
+ const base64Html = Buffer.from(modifiedHtml, "utf-8").toString("base64");
1851
+ const projectRootScript = `<script>window.__CLAUDE_PROJECT_ROOT__ = ${JSON.stringify(projectRoot)};window.__CLAUDE_ORIGINAL_HTML_BASE64__ = "${base64Html}";</script>`;
1852
+ return `<!DOCTYPE html>
1853
+ <html lang="en">
1854
+ <head>
1855
+ <meta charset="UTF-8">
1856
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1857
+ <title>Claude Dev Server</title>
1858
+ ${CLIENT_STYLES}
1859
+ ${projectRootScript}
1860
+ </head>
1861
+ <body>
1862
+ <script type="module">${CLIENT_SCRIPT.replace(/wsPort:\s*\d+/, `wsPort: ${wsPort}`)}</script>
1863
+ </body>
1864
+ </html>`;
1682
1865
  }
1683
1866
 
1684
1867
  export { startUniversalServer };
1685
- //# sourceMappingURL=chunk-RFPIOREI.js.map
1686
- //# sourceMappingURL=chunk-RFPIOREI.js.map
1868
+ //# sourceMappingURL=chunk-PAE5WTS2.js.map
1869
+ //# sourceMappingURL=chunk-PAE5WTS2.js.map