aicodeman 0.9.7 → 0.9.9

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.
Files changed (177) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +11 -3
  3. package/dist/file-stream-manager.d.ts.map +1 -1
  4. package/dist/file-stream-manager.js +6 -2
  5. package/dist/file-stream-manager.js.map +1 -1
  6. package/dist/mux-interface.d.ts +11 -1
  7. package/dist/mux-interface.d.ts.map +1 -1
  8. package/dist/session.d.ts +39 -2
  9. package/dist/session.d.ts.map +1 -1
  10. package/dist/session.js +75 -8
  11. package/dist/session.js.map +1 -1
  12. package/dist/tmux-manager.d.ts +44 -3
  13. package/dist/tmux-manager.d.ts.map +1 -1
  14. package/dist/tmux-manager.js +446 -18
  15. package/dist/tmux-manager.js.map +1 -1
  16. package/dist/types/api.d.ts +10 -17
  17. package/dist/types/api.d.ts.map +1 -1
  18. package/dist/types/api.js +32 -3
  19. package/dist/types/api.js.map +1 -1
  20. package/dist/types/session.d.ts +17 -2
  21. package/dist/types/session.d.ts.map +1 -1
  22. package/dist/types/session.js +1 -1
  23. package/dist/types/session.js.map +1 -1
  24. package/dist/utils/codex-cli-resolver.d.ts +21 -0
  25. package/dist/utils/codex-cli-resolver.d.ts.map +1 -0
  26. package/dist/utils/codex-cli-resolver.js +64 -0
  27. package/dist/utils/codex-cli-resolver.js.map +1 -0
  28. package/dist/utils/index.d.ts +2 -0
  29. package/dist/utils/index.d.ts.map +1 -1
  30. package/dist/utils/index.js +2 -0
  31. package/dist/utils/index.js.map +1 -1
  32. package/dist/utils/push-endpoint-validation.d.ts +6 -0
  33. package/dist/utils/push-endpoint-validation.d.ts.map +1 -0
  34. package/dist/utils/push-endpoint-validation.js +80 -0
  35. package/dist/utils/push-endpoint-validation.js.map +1 -0
  36. package/dist/web/public/{api-client.3adebdc2.js → api-client.c9b1cddc.js} +10 -1
  37. package/dist/web/public/api-client.c9b1cddc.js.br +0 -0
  38. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  39. package/dist/web/public/app.a8663e79.js +35 -0
  40. package/dist/web/public/app.a8663e79.js.br +0 -0
  41. package/dist/web/public/app.a8663e79.js.gz +0 -0
  42. package/dist/web/public/{constants.5b68d2de.js → constants.74211deb.js} +1 -0
  43. package/dist/web/public/constants.74211deb.js.br +0 -0
  44. package/dist/web/public/constants.74211deb.js.gz +0 -0
  45. package/dist/web/public/{image-input.7cade6a8.js → image-input.0ea86695.js} +1 -1
  46. package/dist/web/public/{image-input.7cade6a8.js.br → image-input.0ea86695.js.br} +0 -0
  47. package/dist/web/public/image-input.0ea86695.js.gz +0 -0
  48. package/dist/web/public/index.html +66 -21
  49. package/dist/web/public/index.html.br +0 -0
  50. package/dist/web/public/index.html.gz +0 -0
  51. package/dist/web/public/input-cjk.b8686b5e.js +1 -0
  52. package/dist/web/public/input-cjk.b8686b5e.js.br +0 -0
  53. package/dist/web/public/input-cjk.b8686b5e.js.gz +0 -0
  54. package/dist/web/public/{keyboard-accessory.cdfd8c04.js → keyboard-accessory.bc753cc7.js} +3 -2
  55. package/dist/web/public/keyboard-accessory.bc753cc7.js.br +0 -0
  56. package/dist/web/public/keyboard-accessory.bc753cc7.js.gz +0 -0
  57. package/dist/web/public/{mobile-handlers.1e2a8ef8.js → mobile-handlers.d54d97d6.js} +26 -10
  58. package/dist/web/public/mobile-handlers.d54d97d6.js.br +0 -0
  59. package/dist/web/public/mobile-handlers.d54d97d6.js.gz +0 -0
  60. package/dist/web/public/mobile.959f6fe2.css +1 -0
  61. package/dist/web/public/mobile.959f6fe2.css.br +0 -0
  62. package/dist/web/public/mobile.959f6fe2.css.gz +0 -0
  63. package/dist/web/public/notification-manager.9c984ac2.js.gz +0 -0
  64. package/dist/web/public/orchestrator-panel.js +3 -3
  65. package/dist/web/public/orchestrator-panel.js.br +0 -0
  66. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  67. package/dist/web/public/{panels-ui.5192a2c0.js → panels-ui.6bb3169f.js} +4 -4
  68. package/dist/web/public/panels-ui.6bb3169f.js.br +0 -0
  69. package/dist/web/public/panels-ui.6bb3169f.js.gz +0 -0
  70. package/dist/web/public/{ralph-panel.61076370.js → ralph-panel.6de2d0f8.js} +2 -2
  71. package/dist/web/public/{ralph-panel.61076370.js.br → ralph-panel.6de2d0f8.js.br} +0 -0
  72. package/dist/web/public/{ralph-panel.61076370.js.gz → ralph-panel.6de2d0f8.js.gz} +0 -0
  73. package/dist/web/public/{ralph-wizard.52d533d2.js → ralph-wizard.a6b2d36b.js} +6 -6
  74. package/dist/web/public/ralph-wizard.a6b2d36b.js.br +0 -0
  75. package/dist/web/public/ralph-wizard.a6b2d36b.js.gz +0 -0
  76. package/dist/web/public/{respawn-ui.5377f958.js → respawn-ui.2d249da9.js} +1 -1
  77. package/dist/web/public/{respawn-ui.5377f958.js.br → respawn-ui.2d249da9.js.br} +0 -0
  78. package/dist/web/public/{respawn-ui.5377f958.js.gz → respawn-ui.2d249da9.js.gz} +0 -0
  79. package/dist/web/public/session-ui.512816d8.js +36 -0
  80. package/dist/web/public/session-ui.512816d8.js.br +0 -0
  81. package/dist/web/public/session-ui.512816d8.js.gz +0 -0
  82. package/dist/web/public/settings-ui.21b009ca.js +55 -0
  83. package/dist/web/public/settings-ui.21b009ca.js.br +0 -0
  84. package/dist/web/public/settings-ui.21b009ca.js.gz +0 -0
  85. package/dist/web/public/styles.f3a0faa3.css +1 -0
  86. package/dist/web/public/styles.f3a0faa3.css.br +0 -0
  87. package/dist/web/public/styles.f3a0faa3.css.gz +0 -0
  88. package/dist/web/public/subagent-windows.a366a4ad.js.gz +0 -0
  89. package/dist/web/public/sw.js.gz +0 -0
  90. package/dist/web/public/terminal-ui.6ce91b0b.js +3 -0
  91. package/dist/web/public/terminal-ui.6ce91b0b.js.br +0 -0
  92. package/dist/web/public/terminal-ui.6ce91b0b.js.gz +0 -0
  93. package/dist/web/public/upload.html.gz +0 -0
  94. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  95. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  96. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  97. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  98. package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
  99. package/dist/web/public/vendor/xterm.css.gz +0 -0
  100. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  101. package/dist/web/public/voice-input.085e9e73.js.gz +0 -0
  102. package/dist/web/routes/case-routes.d.ts.map +1 -1
  103. package/dist/web/routes/case-routes.js +1 -2
  104. package/dist/web/routes/case-routes.js.map +1 -1
  105. package/dist/web/routes/clipboard-routes.d.ts.map +1 -1
  106. package/dist/web/routes/clipboard-routes.js +3 -2
  107. package/dist/web/routes/clipboard-routes.js.map +1 -1
  108. package/dist/web/routes/file-routes.d.ts.map +1 -1
  109. package/dist/web/routes/file-routes.js +5 -2
  110. package/dist/web/routes/file-routes.js.map +1 -1
  111. package/dist/web/routes/hook-event-routes.js +1 -1
  112. package/dist/web/routes/hook-event-routes.js.map +1 -1
  113. package/dist/web/routes/mux-routes.js +3 -3
  114. package/dist/web/routes/mux-routes.js.map +1 -1
  115. package/dist/web/routes/plan-routes.js +1 -1
  116. package/dist/web/routes/plan-routes.js.map +1 -1
  117. package/dist/web/routes/push-routes.js +2 -2
  118. package/dist/web/routes/push-routes.js.map +1 -1
  119. package/dist/web/routes/ralph-routes.d.ts.map +1 -1
  120. package/dist/web/routes/ralph-routes.js +6 -6
  121. package/dist/web/routes/ralph-routes.js.map +1 -1
  122. package/dist/web/routes/respawn-routes.d.ts.map +1 -1
  123. package/dist/web/routes/respawn-routes.js +17 -17
  124. package/dist/web/routes/respawn-routes.js.map +1 -1
  125. package/dist/web/routes/scheduled-routes.js +2 -2
  126. package/dist/web/routes/scheduled-routes.js.map +1 -1
  127. package/dist/web/routes/session-routes.d.ts.map +1 -1
  128. package/dist/web/routes/session-routes.js +55 -29
  129. package/dist/web/routes/session-routes.js.map +1 -1
  130. package/dist/web/routes/system-routes.d.ts.map +1 -1
  131. package/dist/web/routes/system-routes.js +20 -17
  132. package/dist/web/routes/system-routes.js.map +1 -1
  133. package/dist/web/routes/ws-routes.d.ts.map +1 -1
  134. package/dist/web/routes/ws-routes.js +21 -1
  135. package/dist/web/routes/ws-routes.js.map +1 -1
  136. package/dist/web/schemas.d.ts +28 -0
  137. package/dist/web/schemas.d.ts.map +1 -1
  138. package/dist/web/schemas.js +38 -6
  139. package/dist/web/schemas.js.map +1 -1
  140. package/dist/web/server.d.ts.map +1 -1
  141. package/dist/web/server.js +72 -18
  142. package/dist/web/server.js.map +1 -1
  143. package/package.json +6 -3
  144. package/dist/web/public/api-client.3adebdc2.js.br +0 -0
  145. package/dist/web/public/api-client.3adebdc2.js.gz +0 -0
  146. package/dist/web/public/app.c860ea08.js +0 -34
  147. package/dist/web/public/app.c860ea08.js.br +0 -0
  148. package/dist/web/public/app.c860ea08.js.gz +0 -0
  149. package/dist/web/public/constants.5b68d2de.js.br +0 -0
  150. package/dist/web/public/constants.5b68d2de.js.gz +0 -0
  151. package/dist/web/public/image-input.7cade6a8.js.gz +0 -0
  152. package/dist/web/public/input-cjk.88082175.js +0 -1
  153. package/dist/web/public/input-cjk.88082175.js.br +0 -0
  154. package/dist/web/public/input-cjk.88082175.js.gz +0 -0
  155. package/dist/web/public/keyboard-accessory.cdfd8c04.js.br +0 -0
  156. package/dist/web/public/keyboard-accessory.cdfd8c04.js.gz +0 -0
  157. package/dist/web/public/mobile-handlers.1e2a8ef8.js.br +0 -0
  158. package/dist/web/public/mobile-handlers.1e2a8ef8.js.gz +0 -0
  159. package/dist/web/public/mobile.26dc30d6.css +0 -1
  160. package/dist/web/public/mobile.26dc30d6.css.br +0 -0
  161. package/dist/web/public/mobile.26dc30d6.css.gz +0 -0
  162. package/dist/web/public/panels-ui.5192a2c0.js.br +0 -0
  163. package/dist/web/public/panels-ui.5192a2c0.js.gz +0 -0
  164. package/dist/web/public/ralph-wizard.52d533d2.js.br +0 -0
  165. package/dist/web/public/ralph-wizard.52d533d2.js.gz +0 -0
  166. package/dist/web/public/session-ui.3e0cf024.js +0 -36
  167. package/dist/web/public/session-ui.3e0cf024.js.br +0 -0
  168. package/dist/web/public/session-ui.3e0cf024.js.gz +0 -0
  169. package/dist/web/public/settings-ui.2b70e2c8.js +0 -55
  170. package/dist/web/public/settings-ui.2b70e2c8.js.br +0 -0
  171. package/dist/web/public/settings-ui.2b70e2c8.js.gz +0 -0
  172. package/dist/web/public/styles.e87cb785.css +0 -1
  173. package/dist/web/public/styles.e87cb785.css.br +0 -0
  174. package/dist/web/public/styles.e87cb785.css.gz +0 -0
  175. package/dist/web/public/terminal-ui.37caa926.js +0 -3
  176. package/dist/web/public/terminal-ui.37caa926.js.br +0 -0
  177. package/dist/web/public/terminal-ui.37caa926.js.gz +0 -0
@@ -255,10 +255,9 @@ const KeyboardHandler = {
255
255
  if (heightDiff > 150 && !this.keyboardVisible) {
256
256
  this.keyboardVisible = true;
257
257
  document.body.classList.add('keyboard-visible');
258
- // Restore --app-height: MobileDetection's resize listener fires before ours
259
- // and may have already shrunk it for the keyboard viewport change.
260
- // Use initialViewportHeight (captured before keyboard opened).
261
- document.documentElement.style.setProperty('--app-height', `${this.initialViewportHeight}px`);
258
+ // While the keyboard is open, size the app to the visual viewport so
259
+ // xterm's bottom row and cursor sit above the OS keyboard.
260
+ document.documentElement.style.setProperty('--app-height', `${currentHeight}px`);
262
261
  this.onKeyboardShow();
263
262
  }
264
263
  // Keyboard hidden (viewport grew back close to initial)
@@ -277,6 +276,8 @@ const KeyboardHandler = {
277
276
  // state changes, orientation changes, and other viewport shifts
278
277
  if (!this.keyboardVisible) {
279
278
  this.initialViewportHeight = currentHeight;
279
+ } else {
280
+ document.documentElement.style.setProperty('--app-height', `${currentHeight}px`);
280
281
  }
281
282
 
282
283
  this.updateLayoutForKeyboard();
@@ -295,6 +296,7 @@ const KeyboardHandler = {
295
296
 
296
297
  const toolbar = document.querySelector('.toolbar');
297
298
  const accessoryBar = document.querySelector('.keyboard-accessory-bar');
299
+ const cjkInput = document.getElementById('cjkInput');
298
300
  const main = document.querySelector('.main');
299
301
 
300
302
  if (this.keyboardVisible) {
@@ -302,7 +304,8 @@ const KeyboardHandler = {
302
304
  // translate up so it sits at the bottom of the visual viewport.
303
305
  // This formula accounts for iOS scrolling the visual viewport (offsetTop)
304
306
  // when the user types in xterm's hidden textarea.
305
- const layoutHeight = window.innerHeight;
307
+ const appEl = document.querySelector('.app');
308
+ const layoutHeight = appEl?.getBoundingClientRect().bottom || window.innerHeight;
306
309
  const visualBottom = window.visualViewport.offsetTop + window.visualViewport.height;
307
310
  const keyboardOffset = Math.max(0, layoutHeight - visualBottom);
308
311
 
@@ -317,13 +320,17 @@ const KeyboardHandler = {
317
320
  if (accessoryBar) {
318
321
  accessoryBar.style.transform = keyboardOffset > 0 ? `translateY(${-keyboardOffset}px)` : '';
319
322
  }
323
+ if (cjkInput?.classList.contains('cjk-input-visible')) {
324
+ cjkInput.style.transform = keyboardOffset > 0 ? `translateY(${-keyboardOffset}px)` : '';
325
+ }
320
326
 
321
- // Shrink main content area so terminal doesn't extend behind keyboard.
322
- // Use stable keyboard height (not scroll-dependent) for padding.
323
- // 84px = toolbar (40px) + accessory bar (44px).
327
+ // Reserve only Codeman's visible controls. The OS keyboard is outside
328
+ // the visual viewport; adding its height here creates a large blank area
329
+ // above the mobile toolbar on iPhone.
324
330
  const keyboardHeight = this.initialViewportHeight - (window.visualViewport.height || window.innerHeight);
325
331
  if (main && keyboardHeight > 0) {
326
- main.style.paddingBottom = `${keyboardHeight + 84}px`;
332
+ const cjkInputHeight = cjkInput?.classList.contains('cjk-input-visible') ? 44 : 0;
333
+ main.style.paddingBottom = `${84 + cjkInputHeight}px`;
327
334
  }
328
335
  } else {
329
336
  this.resetLayout();
@@ -334,6 +341,7 @@ const KeyboardHandler = {
334
341
  resetLayout() {
335
342
  const toolbar = document.querySelector('.toolbar');
336
343
  const accessoryBar = document.querySelector('.keyboard-accessory-bar');
344
+ const cjkInput = document.getElementById('cjkInput');
337
345
  const main = document.querySelector('.main');
338
346
 
339
347
  if (toolbar) {
@@ -342,6 +350,9 @@ const KeyboardHandler = {
342
350
  if (accessoryBar) {
343
351
  accessoryBar.style.transform = '';
344
352
  }
353
+ if (cjkInput) {
354
+ cjkInput.style.transform = '';
355
+ }
345
356
  if (main) {
346
357
  main.style.paddingBottom = '';
347
358
  }
@@ -376,6 +387,8 @@ const KeyboardHandler = {
376
387
  // to the accessory bar.
377
388
  this._shrinkPaddingToFit();
378
389
  app.terminal.scrollToBottom();
390
+ app._syncMobileHelperTextareaToCursor?.();
391
+ app._localEchoOverlay?.rerender?.();
379
392
  // Send resize to server so PTY dimensions match xterm
380
393
  this._sendTerminalResize();
381
394
  }
@@ -421,10 +434,13 @@ const KeyboardHandler = {
421
434
  const cols = Math.max(dims.cols, 40);
422
435
  const rows = Math.max(dims.rows, 10);
423
436
  app._lastResizeDims = { cols, rows };
437
+ // Declare the viewport type so resize arbitration can ignore this
438
+ // while a desktop connection is sizing the same session.
439
+ const viewportType = MobileDetection.getDeviceType ? MobileDetection.getDeviceType() : 'mobile';
424
440
  fetch(`/api/sessions/${app.activeSessionId}/resize`, {
425
441
  method: 'POST',
426
442
  headers: { 'Content-Type': 'application/json' },
427
- body: JSON.stringify({ cols, rows }),
443
+ body: JSON.stringify({ cols, rows, viewportType }),
428
444
  }).catch(() => {});
429
445
  }
430
446
  } catch {}
@@ -0,0 +1 @@
1
+ html.mobile-init .header-font-controls,html.mobile-init .header-system-stats,html.mobile-init .header-tokens,html.mobile-init .monitor-panel,html.mobile-init .subagents-panel,html.mobile-init .project-insights-panel,html.mobile-init .file-browser-panel{display:none!important}@media(max-width:768px){input,textarea,select{font-size:16px!important}html{touch-action:manipulation}}@media(max-width:768px)and (min-width:430px){.header{position:fixed;top:0;left:0;right:0;min-height:48px;max-height:48px;padding:.35rem .5rem;padding-left:calc(.5rem + var(--safe-area-left));padding-right:calc(.5rem + var(--safe-area-right));background:#0a0a0a;border-bottom:1px solid rgba(255,255,255,.08);contain:none;overflow:visible;z-index:1200}.ios-device .header{padding-top:calc(.35rem + var(--safe-area-top));min-height:calc(48px + var(--safe-area-top));max-height:calc(48px + var(--safe-area-top))}.app{padding-top:54px}.ios-device .app{padding-top:calc(54px + var(--safe-area-top))}.header-font-controls{gap:.15rem;padding:.15rem .25rem}.header-font-controls .btn-icon-sm{width:18px;height:18px}.header-system-stats{font-size:.6rem;gap:.25rem}.mobile-header-utility-toggle{display:flex;align-items:center;justify-content:center;width:44px;height:44px;padding:0;margin:-4px .2rem -4px 0;background:transparent;border:none;border-radius:6px;color:var(--text-muted);order:-1;position:relative;z-index:2;flex-shrink:0}.mobile-header-utility-toggle.active,.mobile-header-utility-toggle:active{background:#ffffff14;color:var(--text)}.header-right{position:fixed;top:calc(52px + var(--safe-area-top));left:calc(.5rem + var(--safe-area-left));right:auto;display:flex;align-items:center;gap:.35rem;max-width:calc(100vw - 1rem - var(--safe-area-left) - var(--safe-area-right));padding:.35rem;background:#0a0a0af5;border:1px solid rgba(255,255,255,.12);border-radius:8px;box-shadow:0 10px 28px #00000073;overflow-x:auto;scrollbar-width:none;z-index:2000}.header-right::-webkit-scrollbar{display:none}.header-right.mobile-collapsed{display:none}.btn-icon-header:not(.btn-sm){width:44px;height:44px}.session-tabs,.session-tabs.tabs-two-rows{flex-wrap:nowrap;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;scrollbar-width:none;max-height:52px;gap:3px}.session-tabs::-webkit-scrollbar{display:none}.session-tab{padding:.4rem .6rem;font-size:.75rem;min-height:40px}.session-tab .tab-name{max-width:80px}.toolbar{padding:.4rem .5rem;padding-left:calc(.5rem + var(--safe-area-left));padding-right:calc(.5rem + var(--safe-area-right));gap:.4rem}.tab-count-group{display:none}.btn-toolbar{padding:.4rem .8rem;font-size:.8rem;min-height:40px}.case-select-group{max-width:150px}.toolbar-select{font-size:.75rem}.monitor-panel,.subagents-panel{width:100%;max-width:100%;left:0;right:0;border-radius:8px 8px 0 0;max-height:40vh}.project-insights-panel{max-width:280px;font-size:.7rem}.modal-tabs{overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none;flex-wrap:nowrap}.modal-tabs::-webkit-scrollbar{display:none}.modal-tab-btn{padding:.4rem .75rem;font-size:.7rem;white-space:nowrap;flex-shrink:0}.settings-grid{gap:.4rem .75rem}.settings-item{font-size:.7rem}.event-type-grid{grid-template-columns:1fr 40px 40px 40px;gap:5px 6px}.event-label{font-size:.7rem}.subagent-window{width:320px;height:280px;min-width:240px;min-height:160px}.subagent-window-header{padding:.35rem .5rem}.subagent-window-title .id{font-size:.7rem;max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.subagent-window-title .status{font-size:.55rem}.subagent-window-body{font-size:.7rem}.respawn-banner{padding:.25rem .5rem;padding-left:calc(.5rem + var(--safe-area-left));padding-right:calc(.5rem + var(--safe-area-right));font-size:.65rem}.respawn-compact-layout{gap:.75rem}.respawn-status-row1{gap:.4rem}.respawn-action-log{max-height:2.6em;font-size:.6rem}.respawn-countdown-timer{font-size:.55rem}.respawn-timer-bar{width:24px}.toolbar-center .btn-toolbar.btn-voice{display:flex!important}.toolbar{padding:0 .5rem;gap:.5rem}.toolbar-left,.toolbar-right,.toolbar-group{gap:.5rem}.btn-toolbar{padding:.4rem .75rem;font-size:.75rem;min-height:unset}}@media(max-width:430px){.header-brand{padding-right:.25rem;margin-right:.2rem;border-right:none}.header-brand .logo{font-size:.7rem}.header-font-controls{gap:.1rem;padding:.1rem .2rem}.header-font-controls .btn-icon-sm{width:16px;height:16px}.header-font-controls .font-size-display{font-size:.6rem;min-width:14px}.header-system-stats{font-size:.55rem;gap:.15rem}.header-tokens{font-size:.6rem;padding:.1rem .3rem}.header{position:fixed;top:0;left:0;right:0;min-height:36px;max-height:36px;padding:.15rem .3rem;padding-left:calc(.3rem + var(--safe-area-left));padding-right:calc(.3rem + var(--safe-area-right));gap:.15rem;overflow:hidden;background:#0a0a0a;border-bottom:1px solid rgba(255,255,255,.08);contain:none;z-index:1200}.ios-device .header{padding-top:calc(.15rem + var(--safe-area-top));min-height:calc(36px + var(--safe-area-top));max-height:calc(36px + var(--safe-area-top))}.app{padding-top:42px}.ios-device .app{padding-top:calc(42px + var(--safe-area-top))}.main{padding-bottom:calc(40px + var(--safe-area-bottom))}.ios-device.safari-browser .main{padding-bottom:calc(40px + var(--safe-area-bottom) + (100vh - var(--app-height, 100vh)))}.header-right{position:fixed;top:calc(40px + var(--safe-area-top));left:calc(.3rem + var(--safe-area-left));right:auto;display:flex;align-items:center;padding-left:.2rem;gap:.1rem;max-width:calc(100vw - .6rem - var(--safe-area-left) - var(--safe-area-right));padding:.25rem;background:#0a0a0af5;border:1px solid rgba(255,255,255,.12);border-radius:8px;box-shadow:0 10px 28px #00000073;overflow-x:auto;scrollbar-width:none;border-left:none;z-index:2000}.header-right::-webkit-scrollbar{display:none}.header-right.mobile-collapsed{display:none}.mobile-header-utility-toggle{display:flex;align-items:center;justify-content:center;width:44px;height:44px;padding:0;margin:-6px .15rem -6px 0;background:transparent;border:none;border-radius:5px;color:var(--text-muted);order:-1;position:relative;z-index:2;flex-shrink:0;border-left:none}.btn-icon-header{width:26px;height:26px;padding:0}.btn-icon-header svg{width:12px;height:12px}.btn-icon-header.btn-settings,.btn-icon-header.btn-lifecycle-log{display:none!important}.btn-voice-mobile{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;background:#1a2a3f;border:1px solid rgba(59,130,246,.3);border-radius:4px;color:#93c5fd;cursor:pointer;flex-shrink:0;order:5}.btn-voice-mobile svg{width:13px;height:13px}.btn-voice-mobile:active{background:#2a3a5f}.btn-voice-mobile.recording{background:#ef444440;border-color:#ef444499;color:#ef4444;animation:mobile-voice-pulse 1.2s ease-in-out infinite}@keyframes mobile-voice-pulse{0%,to{box-shadow:inset 0 0 0 1px #ef444459;background:#ef444433}50%{box-shadow:inset 0 0 0 2px #ef4444bf;background:#ef444459}}.btn-settings-mobile{display:inline-flex!important;align-items:center;justify-content:center;width:26px;height:26px;background:transparent;border:1px solid rgba(255,255,255,.2);border-radius:4px;color:#9ca3af;font-size:.85rem;cursor:pointer;flex-shrink:0;order:6;position:relative}.btn-settings-mobile svg{width:13px;height:13px}.btn-settings-mobile:active{background:#ffffff1a;color:#fff}.app{overflow:hidden;position:relative}.keyboard-visible .app{position:fixed;inset:0 0 auto;height:var(--app-height, 100dvh)}.session-tabs,.session-tabs.tabs-two-rows{flex:1;flex-wrap:nowrap;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;scrollbar-width:none;max-height:36px;gap:2px;padding:0}.session-tabs::-webkit-scrollbar{display:none}.session-tab{flex-shrink:0;min-height:32px;max-height:32px;padding:.35rem .5rem;font-size:.7rem;gap:.25rem;border-radius:4px}.session-tab .tab-status{width:4px;height:4px}.session-tab .tab-name{max-width:50px;overflow:hidden;text-overflow:ellipsis}.session-tab .tab-close,.session-tab .tab-gear{display:none}.session-tab.active .tab-gear{display:inline-flex;align-items:center;justify-content:center;font-size:.65rem;line-height:1;width:32px;height:32px;margin-left:auto;opacity:.6}.session-tab.active .tab-close{display:inline-flex;align-items:center;justify-content:center;font-size:1.1rem;line-height:1;width:20px;height:18px;margin-left:-2px}.toolbar{position:fixed;bottom:var(--safe-area-bottom);left:0;right:0;height:40px;min-height:40px;max-height:40px;padding:.3rem .4rem;padding-left:calc(.4rem + var(--safe-area-left));padding-right:calc(.4rem + var(--safe-area-right));flex-wrap:nowrap;gap:.3rem;align-items:center;justify-content:space-between;background:#111;border-top:1px solid rgba(255,255,255,.1);z-index:50;transition:transform .15s ease-out;will-change:transform}.ios-device.safari-browser .toolbar{bottom:calc(var(--safe-area-bottom) + (100vh - var(--app-height, 100vh)))}.keyboard-visible.ios-device.safari-browser .toolbar{bottom:var(--safe-area-bottom)}.keyboard-visible.ios-device.safari-browser .keyboard-accessory-bar{bottom:calc(var(--safe-area-bottom) + 40px)}.toolbar-center{display:flex!important;align-items:center}.toolbar-center .btn-toolbar.btn-voice,.toolbar-right,.version-display{display:none!important}.toolbar-left{flex:1 1 0;min-width:0;align-items:center;gap:.3rem}.toolbar-left .toolbar-group{display:flex;flex:1 1 0;min-width:0;align-items:center;gap:.3rem}.toolbar-left .toolbar-group:first-child{flex:1 1 0;min-width:0;justify-content:space-between;gap:.3rem;flex-wrap:nowrap;align-items:center}.tab-count-group{display:none!important}.btn-toolbar{min-height:26px!important;max-height:26px!important;height:26px!important;padding:0 .5rem!important;font-size:.65rem!important;border-radius:4px;line-height:26px;display:inline-flex;align-items:center;justify-content:center;font-weight:500;letter-spacing:.01em;color:#fff}.run-btn-group{display:flex;flex:0 0 auto}.btn-toolbar.btn-run{flex:0 0 auto;padding:0 .6rem!important;font-weight:500;border-radius:4px 0 0 4px!important;border-right:none!important}.btn-toolbar.btn-run svg{width:10px;height:10px;margin-right:3px}.btn-toolbar.btn-run-gear{flex:0 0 auto;padding:0 .35rem!important;min-width:unset!important;border-radius:0 4px 4px 0!important;border-left:1px solid rgba(255,255,255,.15)!important}.btn-toolbar.btn-run-gear svg{width:10px;height:10px}.btn-toolbar.btn-run.mode-claude,.btn-toolbar.btn-run-gear.mode-claude{background:#1e3a5f;border-color:#3b82f64d;color:#93c5fd}.btn-toolbar.btn-run.mode-claude:active,.btn-toolbar.btn-run-gear.mode-claude:active{background:#2563eb;border-color:#3b82f680}.btn-toolbar.btn-run.mode-opencode,.btn-toolbar.btn-run-gear.mode-opencode{background:#0a2e2a;border-color:#10b9814d;color:#6ee7b7}.btn-toolbar.btn-run.mode-opencode:active,.btn-toolbar.btn-run-gear.mode-opencode:active{background:#0d4a40;border-color:#10b98180}.run-mode-menu{bottom:100%;left:0;margin-bottom:6px;min-width:160px;max-width:80vw}.run-mode-option{padding:10px 12px;font-size:.8rem;cursor:pointer;-webkit-tap-highlight-color:rgba(255,255,255,.1)}.run-mode-history{-webkit-overflow-scrolling:touch;touch-action:manipulation}.btn-toolbar.btn-stop{display:flex!important;flex:0 0 auto;padding:0 8px!important;min-width:unset;order:3}.btn-toolbar.btn-stop svg{width:10px;height:10px;margin-right:0}.btn-toolbar.btn-shell{flex:0 0 auto;background:transparent;border:1px solid rgba(255,255,255,.2);color:#9ca3af;order:4}.btn-toolbar.btn-shell:hover,.btn-toolbar.btn-shell:active{background:#ffffff1a;color:#fff}.case-select-group{display:none!important}.toolbar-left .toolbar-group:first-child{width:100%;gap:8px}.btn-toolbar.btn-shell{flex:0 0 auto;min-width:54px;width:54px;white-space:nowrap;padding:0 8px!important;overflow:hidden;font-size:0!important}.btn-toolbar.btn-shell:after{content:"Shell";font-size:.65rem}.btn-toolbar.btn-case-mobile{display:flex!important;flex:1 1 0!important;min-width:0;padding:0 8px!important;order:2;overflow:hidden;justify-content:center;gap:4px}.btn-toolbar.btn-case-mobile #mobileCaseName{max-width:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.btn-case-settings-mobile{display:inline-flex!important;align-items:center;justify-content:center;width:26px;height:26px;background:transparent;border:1px solid rgba(255,255,255,.2);border-radius:4px;color:#9ca3af;cursor:pointer;flex-shrink:0;order:1}.btn-case-settings-mobile:active{background:#ffffff1a;color:#fff}@media(max-width:374px){.toolbar{padding:0 2px}.toolbar-left,.toolbar-left .toolbar-group,.toolbar-left .toolbar-group:first-child{gap:2px}}.btn-case-settings-mobile{display:none!important}.case-settings-popover-mobile{position:fixed;bottom:calc(var(--safe-area-bottom) + 46px);left:8px;right:8px;background:#1e1e1e;border:1px solid rgba(255,255,255,.15);border-radius:8px;padding:.6rem .75rem;z-index:1000;box-shadow:0 4px 16px #00000080;display:block}.case-settings-popover-mobile.hidden{display:none!important}.case-settings-popover-mobile .checkbox-inline{display:flex;align-items:center;gap:6px;font-size:.75rem;color:#e5e7eb}.case-settings-popover-mobile .form-hint{display:block;margin-top:.2rem;font-size:.6rem;color:#6b7280}.keyboard-accessory-bar{display:none;position:fixed;bottom:calc(var(--safe-area-bottom) + 40px);left:0;right:0;height:44px;background:#1a1a1a;border-top:1px solid rgba(255,255,255,.1);padding:6px 8px;padding-left:calc(8px + var(--safe-area-left));padding-right:calc(8px + var(--safe-area-right));gap:8px;align-items:center;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;z-index:51;transition:transform .15s ease-out;will-change:transform}.keyboard-accessory-bar.visible{display:flex}.keyboard-accessory-bar::-webkit-scrollbar{display:none}.accessory-btn{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;gap:4px;padding:6px 12px;background:#2a2a2a;border:1px solid rgba(255,255,255,.15);border-radius:6px;color:#e5e5e5;font-size:.65rem;font-weight:500;cursor:pointer;transition:background .15s,border-color .15s}.accessory-btn.confirming{background:#6b4f00;border-color:#b8860b;color:#ffd54f}.accessory-btn:active{background:#3a3a3a}.accessory-btn svg{width:14px;height:14px}.accessory-btn-arrow{padding:6px 10px;background:#2563eb;border-color:#3b82f680;color:#fff}.accessory-btn-arrow:active{background:#1d4ed8}.accessory-btn-dismiss{margin-left:auto;flex:1 1 0;max-width:100px;padding:10px 8px;background:#2563eb;border-color:#3b82f680;color:#fff;font-weight:600}.accessory-btn-dismiss svg{width:22px;height:22px}.accessory-btn-dismiss:active{background:#1d4ed8}.voice-preview{bottom:calc(var(--safe-area-bottom) + 94px);font-size:.8rem;max-width:90%;padding:6px 14px}.btn-case-add,.btn-case-settings{display:none!important}.paste-overlay{position:fixed;inset:0;background:#0009;z-index:10000;display:flex;align-items:flex-start;justify-content:center;padding-top:15vh}.paste-dialog{background:var(--bg-secondary, #1e1e2e);border:1px solid var(--border-color, #444);border-radius:12px;padding:12px;width:calc(100% - 24px);max-width:400px}.paste-textarea{width:100%;min-height:80px;max-height:200px;background:var(--bg-primary, #0d0d14);color:var(--text-primary, #e0e0e0);border:1px solid var(--border-color, #444);border-radius:8px;padding:8px;font-family:inherit;font-size:16px;resize:none;box-sizing:border-box}.paste-textarea:focus{outline:none;border-color:var(--accent-color, #7aa2f7)}.paste-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:10px}.paste-cancel,.paste-new,.paste-send,.paste-image{padding:8px 18px;border:none;border-radius:8px;font-size:14px;cursor:pointer}.paste-image{margin-right:auto;background:var(--bg-tertiary, #333);color:var(--accent-color, #7aa2f7);border:1px solid var(--accent-color, #7aa2f7)}.paste-cancel{background:var(--bg-tertiary, #333);color:var(--text-secondary, #aaa)}.paste-new{background:var(--bg-tertiary, #333);color:var(--accent-color, #7aa2f7);border:1px solid var(--accent-color, #7aa2f7)}.paste-send{background:var(--accent-color, #7aa2f7);color:#fff;font-weight:600}.toolbar-select{display:none!important}.toolbar .btn-case-add{min-width:26px!important;max-width:26px!important;width:26px!important;min-height:26px!important;max-height:26px!important;height:26px!important;padding:0!important;font-size:.9rem;font-weight:700;display:inline-flex!important;align-items:center;justify-content:center;line-height:1;border-radius:4px;background:transparent;border:1px solid rgba(255,255,255,.15);color:#9ca3af}.btn-case-add:hover,.btn-case-add:active{background:#ffffff1a;color:#fff}.monitor-panel,.subagents-panel{width:100%;max-width:100%;left:0;right:0;border-radius:8px 8px 0 0;bottom:calc(44px + 2rem + var(--safe-area-bottom));max-height:35vh}.modal-content{width:100%;max-width:100%;height:100%;max-height:100%;border-radius:0;margin:0;display:flex;flex-direction:column}.modal-content.modal-sm{height:auto;max-height:85vh;border-radius:12px;margin:1rem;width:calc(100% - 2rem)}.ios-device .modal-content{padding-top:var(--safe-area-top);padding-bottom:var(--safe-area-bottom);padding-left:var(--safe-area-left);padding-right:var(--safe-area-right)}.modal-header{padding:.75rem 1rem}.modal-header h3{font-size:1rem}.modal-body{padding:.75rem 1rem;flex:1;min-height:0;overflow-y:auto;-webkit-overflow-scrolling:touch}.modal-footer,.form-actions{padding:.75rem 1rem;padding-bottom:calc(.75rem + var(--safe-area-bottom))}.ios-device .terminal-container{padding-bottom:var(--safe-area-bottom)}.main{flex:1;min-height:0}.cli-info-bar{display:block}.terminal-container{height:100%;min-height:0;position:relative;overflow:visible;touch-action:none}.terminal-container .xterm,.terminal-container .xterm-viewport,.terminal-container .xterm-screen{touch-action:none}.response-viewer{padding-bottom:var(--safe-area-bottom, 0px)}.response-viewer-body{font-size:12px;padding:12px}.welcome-content{max-width:calc(100vw - 1.5rem);padding:1rem .75rem}.welcome-title{font-size:1.2rem;margin-bottom:.5rem}.welcome-desc{font-size:.8rem;margin-bottom:.4rem}.welcome-actions{flex-direction:column;gap:.5rem;margin-top:1rem}.welcome-btn{width:100%;justify-content:center;min-height:44px;padding:.75rem 1rem;font-size:.85rem}.history-show-more{display:block;margin-bottom:.75rem}.welcome-ralph-link{display:block;margin:.75rem auto 0}.welcome-hint{font-size:.7rem;margin-top:.75rem}.modal-wizard{display:flex;flex-direction:column}.wizard-progress{padding:.5rem}.wizard-step{padding:.3rem}.wizard-step-number{width:24px;height:24px;font-size:.7rem}.wizard-step-label{display:none}.timer-banner{padding-left:calc(.5rem + var(--safe-area-left));padding-right:calc(.5rem + var(--safe-area-right))}.respawn-banner{padding:.3rem .5rem;padding-left:calc(.5rem + var(--safe-area-left));padding-right:calc(.5rem + var(--safe-area-right));font-size:.65rem}.respawn-compact-layout{flex-direction:column;gap:.15rem}.respawn-status-col{flex-shrink:1;min-width:0;gap:.1rem}.respawn-status-row1{flex-wrap:wrap;gap:.25rem;row-gap:.1rem}.respawn-state{font-size:.6rem;padding:.05rem .3rem}.respawn-banner .detection-confidence{font-size:.5rem;padding:.02rem .2rem}.respawn-cycles{font-size:.55rem}.respawn-timer{font-size:.55rem;padding:0 .25rem}.respawn-tokens{font-size:.55rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:120px}.respawn-indicator{font-size:.65rem}.respawn-banner .btn-icon-only{min-width:36px;min-height:36px;font-size:1rem;display:flex;align-items:center;justify-content:center;margin-left:auto;border-radius:6px;flex-shrink:0}.respawn-status-row2{font-size:.55rem;gap:.3rem;min-height:0;flex-wrap:wrap}.respawn-banner .detection-hook,.respawn-banner .detection-ai-check,.respawn-banner .detection-status{font-size:.55rem;padding:.02rem .2rem}.respawn-countdown-timers{gap:.2rem}.respawn-countdown-timer{font-size:.5rem;padding:.02rem .2rem;gap:.15rem}.respawn-countdown-timer .respawn-timer-bar{display:none}.respawn-action-log{border-left:none;border-top:1px solid rgba(34,197,94,.15);padding-left:0;padding-top:.15rem;max-height:1.4em;font-size:.55rem;overflow:hidden}.respawn-header{flex-direction:row;gap:.4rem;align-items:center}.respawn-header .respawn-actions{display:flex;gap:.3rem;flex-shrink:0}.respawn-header .respawn-actions .btn-toolbar{min-height:28px!important;font-size:.7rem!important;padding:.2rem .5rem!important;border-radius:5px}.duration-presets{display:grid;grid-template-columns:repeat(4,1fr);gap:.2rem}.duration-preset-btn{min-height:32px;padding:.2rem .25rem;font-size:.65rem;border-radius:5px;text-align:center}.duration-custom{grid-column:1 / -1;display:flex;gap:.25rem;align-items:center}.duration-custom .duration-preset-btn{flex:0 0 auto;min-width:50px}.duration-custom-input.visible{flex:1}.duration-custom-input input{width:100%;min-height:28px;font-size:16px}.preset-selector{display:grid;grid-template-columns:1fr 1fr;gap:.25rem}.preset-selector select{grid-column:1 / -1;min-height:32px;font-size:16px;border-radius:5px;padding:.2rem .4rem}.preset-selector .btn{min-height:32px;font-size:.65rem;border-radius:5px;padding:.2rem .4rem}#sessionOptionsModal textarea{font-size:16px;min-height:32px!important;max-height:56px;height:auto!important;border-radius:6px;padding:.3rem .5rem}#modalRespawnPrompt{min-height:32px!important;max-height:56px}#modalRespawnKickstart{min-height:32px!important;max-height:48px}#sessionOptionsModal .checkbox-inline{min-height:30px;font-size:.75rem;padding:.15rem 0;gap:.4rem}#sessionOptionsModal .checkbox-inline input[type=checkbox]{width:18px;height:18px}#sessionOptionsModal .respawn-options-row{gap:.5rem}#sessionOptionsModal .form-section-header{margin-top:.75rem;padding-top:.5rem;font-size:.65rem}#sessionOptionsModal .form-row{margin-bottom:.5rem}#sessionOptionsModal .form-row label{font-size:.65rem;margin-bottom:.2rem}#sessionOptionsModal .form-hint{font-size:.6rem;margin-top:.2rem}.context-setting{padding:.625rem}.context-setting-header{flex-wrap:wrap;gap:.4rem}.context-setting-header .input-suffix-sm input{width:60px;font-size:16px;min-height:36px}.context-setting input[type=text]{font-size:16px;min-height:36px;padding:.4rem .6rem;border-radius:6px}.color-picker{gap:8px}.color-swatch{width:36px;height:36px;border-radius:6px}.form-row-switch{min-height:40px;gap:.4rem}.context-settings-grid input[type=number]{font-size:16px;min-height:36px}.ralph-limits-grid{grid-template-columns:1fr 1fr;gap:.5rem}.ralph-limits-grid .form-col input[type=number]{font-size:16px;min-height:36px;padding:.4rem .5rem;border-radius:6px}#modalRalphPhrase{font-size:16px;min-height:40px;border-radius:6px}.ralph-config-actions{margin-top:1rem}.ralph-config-actions .btn-toolbar{width:100%;min-height:44px;font-size:.85rem;border-radius:8px}.run-summary-filters{gap:.3rem;margin-bottom:.5rem}.filter-btn{padding:.35rem .75rem;font-size:.7rem;min-height:32px}.timeline-event{padding:.4rem .5rem;margin-bottom:.25rem;font-size:.7rem}.run-summary-footer{flex-direction:column;gap:.4rem;padding:.5rem 0}.run-summary-actions{flex-wrap:wrap;gap:.3rem}.run-summary-actions .btn-toolbar{flex:1;min-height:36px;font-size:.7rem;white-space:nowrap}.auto-refresh-label{font-size:.7rem}.ralph-panel{font-size:.75rem}.ralph-summary{padding:.4rem .5rem;padding-left:calc(.5rem + var(--safe-area-left));padding-right:calc(.5rem + var(--safe-area-right))}.project-insights-panel{max-width:100%;font-size:.65rem;bottom:calc(44px + 2rem + var(--safe-area-bottom))}.file-browser-panel{max-width:100%;max-height:50vh;bottom:calc(44px + 2rem + var(--safe-area-bottom))}.notification-drawer{width:100%;max-width:100%;right:0;border-radius:0;padding-left:var(--safe-area-left);padding-right:var(--safe-area-right);padding-bottom:var(--safe-area-bottom)}input,textarea,[contenteditable]{scroll-margin-bottom:200px;scroll-margin-top:80px}.keyboard-visible .modal-body{max-height:40vh;overflow-y:auto}.keyboard-visible #createCaseModal .modal-body{max-height:60vh}.mobile-case-picker .modal-backdrop{background:#00000080}.mobile-case-picker-sheet{max-height:60vh;padding-bottom:var(--safe-area-bottom);animation:slideUp .2s ease-out}@keyframes slideUp{0%{transform:translateY(100%)}to{transform:translateY(0)}}.mobile-case-picker-header .modal-close{width:32px;height:32px;font-size:1.5rem}.mobile-case-picker-footer{padding-bottom:calc(12px + var(--safe-area-bottom))}.modal-tabs{overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none;gap:.25rem;padding:0 .75rem .5rem;flex-wrap:nowrap}.modal-tabs::-webkit-scrollbar{display:none}.modal-tab-btn{padding:.35rem .6rem;font-size:.65rem;white-space:nowrap;flex-shrink:0}#createCaseModal .modal-tabs{gap:.5rem;padding:.5rem 1rem .75rem}#createCaseModal .modal-tab-btn{flex:1;min-height:44px;padding:.6rem 1rem;font-size:.8rem;font-weight:500;border-radius:8px;justify-content:center;text-align:center}#createCaseModal .form-row{margin-bottom:1rem}#createCaseModal .form-row label{font-size:.8rem;margin-bottom:.4rem;font-weight:500;color:#e5e7eb}#createCaseModal .form-row input[type=text]{min-height:44px;font-size:16px;padding:.5rem .75rem;border-radius:8px}#createCaseModal .form-row .form-hint{font-size:.65rem;margin-top:.35rem;line-height:1.4}#createCaseModal .form-actions{padding:.75rem 1rem;padding-bottom:calc(.75rem + var(--safe-area-bottom));gap:.75rem}#createCaseModal .form-actions .btn-toolbar{min-height:44px!important;max-height:44px!important;height:44px!important;font-size:.85rem!important;font-weight:500;border-radius:8px}#caseModalSubmit.loading{opacity:.6;pointer-events:none}#createCaseModal.from-mobile .modal-content{animation:caseModalSlideUp .25s ease-out}@keyframes caseModalSlideUp{0%{transform:translateY(30px);opacity:0}to{transform:translateY(0);opacity:1}}.btn-case-create-mobile{min-height:48px!important;max-height:48px!important;height:48px!important;font-size:.85rem!important;font-weight:500;border-radius:10px}.settings-grid{grid-template-columns:1fr;gap:.35rem}.settings-grid-3col{grid-template-columns:1fr 1fr 1fr}.settings-item{padding:.35rem .5rem;font-size:.7rem}.settings-item-label{font-size:.7rem}.settings-section-header{font-size:.6rem;padding:.35rem 0 .2rem;margin-top:.5rem}.form-row{margin-bottom:.5rem}.form-row label{font-size:.7rem;margin-bottom:.2rem}.form-row .form-hint{font-size:.6rem}.form-row input[type=text],.form-row input[type=number],.form-row textarea,.form-row .form-select,.form-row select{font-size:.75rem;padding:.35rem .5rem;min-height:32px}.form-row-switch{gap:.3rem}.form-row-switch>label:first-child{font-size:.7rem}.form-section-header{font-size:.6rem;margin-top:.75rem}.event-type-grid{grid-template-columns:1fr 36px 36px 36px;gap:4px 4px;padding:6px;margin-top:6px}.event-header{font-size:.55rem}.event-label{font-size:.65rem}.event-type-grid input[type=checkbox]{width:12px;height:12px}.form-actions{gap:.5rem}.form-actions .btn-toolbar{flex:1;min-height:36px!important;max-height:36px!important;height:36px!important;font-size:.75rem!important}.subagent-window{position:fixed;width:calc(100% - 8px);max-width:calc(100% - 8px);height:110px;min-height:80px;max-height:110px;min-width:200px;border-radius:6px;resize:none;box-shadow:0 2px 8px #0006}.subagent-window:after{display:none}.subagent-window-header{padding:.2rem .4rem;min-height:24px}.subagent-window-title{gap:.2rem;overflow:hidden}.subagent-window-title .icon{font-size:.7rem}.subagent-window-title .id{font-size:.6rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:45vw}.subagent-window-title .status{font-size:.45rem;padding:.05rem .15rem}.subagent-model-badge{font-size:.45rem!important;padding:.05rem .15rem!important}.subagent-window-actions button{font-size:.7rem;padding:.15rem .35rem;min-width:26px;min-height:26px;display:flex;align-items:center;justify-content:center}.subagent-window-body{font-size:.6rem;padding:.2rem .35rem}.subagent-window-body .activity-line{gap:.15rem;padding:.05rem 0}.subagent-window-body .activity-line .time{font-size:.5rem}.subagent-window-body .activity-line .tool-name,.subagent-window-body .activity-line .tool-detail{font-size:.55rem}.subagent-window-parent{display:none}.session-tab .tab-subagent-badge{height:14px;padding:0 3px;border-radius:7px;margin-left:2px}.session-tab .tab-subagent-badge .subagent-label{font-size:.45rem}.subagent-dropdown{position:fixed!important;left:8px!important;right:8px!important;bottom:auto!important;min-width:auto!important;max-width:none!important;border-radius:8px;transform:none!important}}.keyboard-accessory-bar{display:none;height:44px;background:#1a1a1a;border-top:1px solid rgba(255,255,255,.1);padding:6px 8px;gap:8px;align-items:center;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;z-index:51}.keyboard-accessory-bar.visible{display:flex}.keyboard-accessory-bar::-webkit-scrollbar{display:none}.accessory-btn{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;gap:4px;padding:6px 12px;background:#2a2a2a;border:1px solid rgba(255,255,255,.15);border-radius:6px;color:#e5e5e5;font-size:.65rem;font-weight:500;cursor:pointer;transition:background .15s,border-color .15s}.accessory-btn.confirming{background:#6b4f00;border-color:#b8860b;color:#ffd54f}.accessory-btn:active{background:#3a3a3a}.accessory-btn svg{width:14px;height:14px}.accessory-btn-arrow{padding:6px 10px;background:#2563eb;border-color:#3b82f680;color:#fff}.accessory-btn-arrow:active{background:#1d4ed8}.accessory-btn-dismiss{margin-left:auto;flex:1 1 0;max-width:80px;padding:10px 8px;background:#334d6e;border-color:#6496c866;color:#c0d4e8;font-weight:600}.accessory-btn-dismiss svg{width:20px;height:20px}.accessory-btn-dismiss:active{background:#3d5f85}.ios-device.safari-browser{overscroll-behavior:none}@media(hover:none)and (pointer:coarse){.session-tab .tab-close,.session-tab .tab-gear{opacity:1;width:auto;padding:.15rem .25rem;align-items:center;justify-content:center}.btn-toolbar:hover,.btn-icon-header:hover{transform:none}.subagent-dropdown-trigger:active+.subagent-dropdown-menu,.subagent-dropdown-trigger:focus+.subagent-dropdown-menu{display:block}}
@@ -182,7 +182,7 @@ Object.assign(CodemanApp.prototype, {
182
182
  body: JSON.stringify({ goal, config }),
183
183
  });
184
184
  const data = await res.json();
185
- if (data.ok) {
185
+ if (data.data?.ok) {
186
186
  this.orchestratorState = { state: 'planning', plan: null };
187
187
  this.showOrchestratorPanel();
188
188
  this.renderOrchestratorPanel();
@@ -259,8 +259,8 @@ Object.assign(CodemanApp.prototype, {
259
259
  try {
260
260
  const res = await fetch('/api/orchestrator/status');
261
261
  const data = await res.json();
262
- if (data.ok) {
263
- this.orchestratorState = data;
262
+ if (data.data?.ok) {
263
+ this.orchestratorState = data.data;
264
264
  this.renderOrchestratorPanel();
265
265
  }
266
266
  } catch (err) {
@@ -1,4 +1,4 @@
1
- "use strict";Object.assign(CodemanApp.prototype,{_addActivityEntry(e,t,s=50){const n=this.subagentActivity.get(e)||[];n.push(t),n.length>s&&n.shift(),this.subagentActivity.set(e,n)},_onTaskCreated(e){this.renderSessionTabs(),e.sessionId===this.activeSessionId&&this.renderTaskPanel()},_onTaskCompleted(e){this.renderSessionTabs(),e.sessionId===this.activeSessionId&&this.renderTaskPanel()},_onTaskFailed(e){this.renderSessionTabs(),e.sessionId===this.activeSessionId&&this.renderTaskPanel()},_onTaskUpdated(e){e.sessionId===this.activeSessionId&&this.renderTaskPanel()},_onMuxCreated(e){this.muxSessions.push(e),this.renderMuxSessions()},_onMuxKilled(e){this.muxSessions=this.muxSessions.filter(t=>t.sessionId!==e.sessionId),this.renderMuxSessions()},_onMuxDied(e){this.muxSessions=this.muxSessions.filter(t=>t.sessionId!==e.sessionId),this.renderMuxSessions(),this.showToast("Mux session died: "+this.getShortId(e.sessionId),"warning")},_onMuxStatsUpdated(e){this.muxSessions=e,document.getElementById("monitorPanel").classList.contains("open")&&this.renderMuxSessions()},_onBashToolStart(e){this.handleBashToolStart(e.sessionId,e.tool)},_onBashToolEnd(e){this.handleBashToolEnd(e.sessionId,e.tool)},_onBashToolsUpdate(e){this.handleBashToolsUpdate(e.sessionId,e.tools)},_onSubagentDiscovered(e){this.subagents.set(e.agentId,e),this.subagentActivity.set(e.agentId,[]),this.subagentToolResults.delete(e.agentId),this.subagentWindows.has(e.agentId)&&this.forceCloseSubagentWindow(e.agentId),this.renderSubagentPanel(),this.findParentSessionForSubagent(e.agentId),requestAnimationFrame(()=>{this.updateConnectionLines()});const t=this.subagentParentMap.get(e.agentId);this._notifySession(t||e.sessionId,"info","subagent-spawn","Subagent Spawned",e.description||"New background agent started")},_onSubagentUpdated(e){const t=this.subagents.get(e.agentId);t?(Object.assign(t,e),this.subagents.set(e.agentId,t)):this.subagents.set(e.agentId,e),this.renderSubagentPanel(),this.subagentWindows.has(e.agentId)&&(this.renderSubagentWindowContent(e.agentId),this.updateSubagentWindowHeader(e.agentId))},_onSubagentToolCall(e){this._addActivityEntry(e.agentId,{type:"tool",...e}),this.activeSubagentId===e.agentId&&this.renderSubagentDetail(),this.renderSubagentPanel(),this.subagentWindows.has(e.agentId)&&this.scheduleSubagentWindowRender(e.agentId)},_onSubagentProgress(e){this._addActivityEntry(e.agentId,{type:"progress",...e}),this.activeSubagentId===e.agentId&&this.renderSubagentDetail(),this.subagentWindows.has(e.agentId)&&this.scheduleSubagentWindowRender(e.agentId)},_onSubagentMessage(e){this._addActivityEntry(e.agentId,{type:"message",...e}),this.activeSubagentId===e.agentId&&this.renderSubagentDetail(),this.subagentWindows.has(e.agentId)&&this.scheduleSubagentWindowRender(e.agentId)},_onSubagentToolResult(e){this.subagentToolResults.has(e.agentId)||this.subagentToolResults.set(e.agentId,new Map);const t=this.subagentToolResults.get(e.agentId);if(t.set(e.toolUseId,e),t.size>50){const s=t.keys().next().value;t.delete(s)}this._addActivityEntry(e.agentId,{type:"tool_result",...e}),this.activeSubagentId===e.agentId&&this.renderSubagentDetail(),this.subagentWindows.has(e.agentId)&&this.scheduleSubagentWindowRender(e.agentId)},async _onSubagentCompleted(e){const t=this.subagents.get(e.agentId);if(t&&(t.status="completed",this.subagents.set(e.agentId,t)),this.renderSubagentPanel(),this.updateSubagentWindows(),this.subagentWindows.has(e.agentId)){const n=this.subagentWindows.get(e.agentId);n&&!n.minimized&&(await this.closeSubagentWindow(e.agentId),this.saveSubagentWindowStates())}const s=this.subagentParentMap.get(e.agentId);this._notifySession(s||t?.sessionId||e.sessionId,"info","subagent-complete","Subagent Completed",t?.description||e.description||"Background agent finished"),setTimeout(()=>{this.subagents.get(e.agentId)?.status==="completed"&&(this.subagentActivity.delete(e.agentId),this.subagentToolResults.delete(e.agentId))},300*1e3),setTimeout(()=>{this.subagents.get(e.agentId)?.status==="completed"&&!this.subagentWindows.has(e.agentId)&&(this.subagents.delete(e.agentId),this.subagentParentMap.delete(e.agentId))},1800*1e3)},_onImageDetected(e){console.log("[Image Detected]",e),this.openImagePopup(e)},async openTokenStats(){try{const t=await(await fetch("/api/token-stats")).json();t.success?(this.renderTokenStats(t),document.getElementById("tokenStatsModal").classList.add("active")):this.showToast("Failed to load token stats","error")}catch(e){console.error("Failed to fetch token stats:",e),this.showToast("Failed to load token stats","error")}},renderTokenStats(e){const{daily:t,totals:s}=e,n=new Date().toISOString().split("T")[0],a=t.find(v=>v.date===n)||{inputTokens:0,outputTokens:0,estimatedCost:0},o=new Date;o.setDate(o.getDate()-7);const r=t.filter(v=>new Date(v.date)>=o),i=r.reduce((v,S)=>v+S.inputTokens,0),l=r.reduce((v,S)=>v+S.outputTokens,0),c=this.estimateCost(i,l),d=s.totalInputTokens,u=s.totalOutputTokens,h=this.estimateCost(d,u),m=document.getElementById("statsSummary");m.innerHTML=`
1
+ "use strict";Object.assign(CodemanApp.prototype,{_addActivityEntry(e,t,s=50){const n=this.subagentActivity.get(e)||[];n.push(t),n.length>s&&n.shift(),this.subagentActivity.set(e,n)},_onTaskCreated(e){this.renderSessionTabs(),e.sessionId===this.activeSessionId&&this.renderTaskPanel()},_onTaskCompleted(e){this.renderSessionTabs(),e.sessionId===this.activeSessionId&&this.renderTaskPanel()},_onTaskFailed(e){this.renderSessionTabs(),e.sessionId===this.activeSessionId&&this.renderTaskPanel()},_onTaskUpdated(e){e.sessionId===this.activeSessionId&&this.renderTaskPanel()},_onMuxCreated(e){this.muxSessions.push(e),this.renderMuxSessions()},_onMuxKilled(e){this.muxSessions=this.muxSessions.filter(t=>t.sessionId!==e.sessionId),this.renderMuxSessions()},_onMuxDied(e){this.muxSessions=this.muxSessions.filter(t=>t.sessionId!==e.sessionId),this.renderMuxSessions(),this.showToast("Mux session died: "+this.getShortId(e.sessionId),"warning")},_onMuxStatsUpdated(e){this.muxSessions=e,document.getElementById("monitorPanel").classList.contains("open")&&this.renderMuxSessions()},_onBashToolStart(e){this.handleBashToolStart(e.sessionId,e.tool)},_onBashToolEnd(e){this.handleBashToolEnd(e.sessionId,e.tool)},_onBashToolsUpdate(e){this.handleBashToolsUpdate(e.sessionId,e.tools)},_onSubagentDiscovered(e){this.subagents.set(e.agentId,e),this.subagentActivity.set(e.agentId,[]),this.subagentToolResults.delete(e.agentId),this.subagentWindows.has(e.agentId)&&this.forceCloseSubagentWindow(e.agentId),this.renderSubagentPanel(),this.findParentSessionForSubagent(e.agentId),requestAnimationFrame(()=>{this.updateConnectionLines()});const t=this.subagentParentMap.get(e.agentId);this._notifySession(t||e.sessionId,"info","subagent-spawn","Subagent Spawned",e.description||"New background agent started")},_onSubagentUpdated(e){const t=this.subagents.get(e.agentId);t?(Object.assign(t,e),this.subagents.set(e.agentId,t)):this.subagents.set(e.agentId,e),this.renderSubagentPanel(),this.subagentWindows.has(e.agentId)&&(this.renderSubagentWindowContent(e.agentId),this.updateSubagentWindowHeader(e.agentId))},_onSubagentToolCall(e){this._addActivityEntry(e.agentId,{type:"tool",...e}),this.activeSubagentId===e.agentId&&this.renderSubagentDetail(),this.renderSubagentPanel(),this.subagentWindows.has(e.agentId)&&this.scheduleSubagentWindowRender(e.agentId)},_onSubagentProgress(e){this._addActivityEntry(e.agentId,{type:"progress",...e}),this.activeSubagentId===e.agentId&&this.renderSubagentDetail(),this.subagentWindows.has(e.agentId)&&this.scheduleSubagentWindowRender(e.agentId)},_onSubagentMessage(e){this._addActivityEntry(e.agentId,{type:"message",...e}),this.activeSubagentId===e.agentId&&this.renderSubagentDetail(),this.subagentWindows.has(e.agentId)&&this.scheduleSubagentWindowRender(e.agentId)},_onSubagentToolResult(e){this.subagentToolResults.has(e.agentId)||this.subagentToolResults.set(e.agentId,new Map);const t=this.subagentToolResults.get(e.agentId);if(t.set(e.toolUseId,e),t.size>50){const s=t.keys().next().value;t.delete(s)}this._addActivityEntry(e.agentId,{type:"tool_result",...e}),this.activeSubagentId===e.agentId&&this.renderSubagentDetail(),this.subagentWindows.has(e.agentId)&&this.scheduleSubagentWindowRender(e.agentId)},async _onSubagentCompleted(e){const t=this.subagents.get(e.agentId);if(t&&(t.status="completed",this.subagents.set(e.agentId,t)),this.renderSubagentPanel(),this.updateSubagentWindows(),this.subagentWindows.has(e.agentId)){const n=this.subagentWindows.get(e.agentId);n&&!n.minimized&&(await this.closeSubagentWindow(e.agentId),this.saveSubagentWindowStates())}const s=this.subagentParentMap.get(e.agentId);this._notifySession(s||t?.sessionId||e.sessionId,"info","subagent-complete","Subagent Completed",t?.description||e.description||"Background agent finished"),setTimeout(()=>{this.subagents.get(e.agentId)?.status==="completed"&&(this.subagentActivity.delete(e.agentId),this.subagentToolResults.delete(e.agentId))},300*1e3),setTimeout(()=>{this.subagents.get(e.agentId)?.status==="completed"&&!this.subagentWindows.has(e.agentId)&&(this.subagents.delete(e.agentId),this.subagentParentMap.delete(e.agentId))},1800*1e3)},_onImageDetected(e){console.log("[Image Detected]",e),this.openImagePopup(e)},async openTokenStats(){try{const t=await(await fetch("/api/token-stats")).json();t.success?(this.renderTokenStats(t.data),document.getElementById("tokenStatsModal").classList.add("active")):this.showToast("Failed to load token stats","error")}catch(e){console.error("Failed to fetch token stats:",e),this.showToast("Failed to load token stats","error")}},renderTokenStats(e){const{daily:t,totals:s}=e,n=new Date().toISOString().split("T")[0],a=t.find(v=>v.date===n)||{inputTokens:0,outputTokens:0,estimatedCost:0},o=new Date;o.setDate(o.getDate()-7);const r=t.filter(v=>new Date(v.date)>=o),i=r.reduce((v,S)=>v+S.inputTokens,0),l=r.reduce((v,S)=>v+S.outputTokens,0),c=this.estimateCost(i,l),d=s.totalInputTokens,u=s.totalOutputTokens,h=this.estimateCost(d,u),m=document.getElementById("statsSummary");m.innerHTML=`
2
2
  <div class="stat-card">
3
3
  <span class="stat-card-label">Today</span>
4
4
  <span class="stat-card-value">${this.formatTokens(a.inputTokens+a.outputTokens)}</span>
@@ -29,7 +29,7 @@
29
29
  <span class="cell cell-cost">$${v.estimatedCost.toFixed(2)}</span>
30
30
  </div>
31
31
  `).join("")}
32
- `},closeTokenStats(){const e=document.getElementById("tokenStatsModal");e&&e.classList.remove("active")},async toggleMonitorPanel(){const e=document.getElementById("monitorPanel"),t=document.getElementById("monitorToggleBtn");e.classList.toggle("open"),e.classList.contains("open")?(await this.loadMuxSessions(),await fetch("/api/mux-sessions/stats/start",{method:"POST"}),this.renderTaskPanel(),t&&(t.innerHTML="&#x25BC;")):(await fetch("/api/mux-sessions/stats/stop",{method:"POST"}),t&&(t.innerHTML="&#x25B2;"))},toggleTaskPanel(){this.toggleMonitorPanel()},toggleMonitorDetach(){const e=document.getElementById("monitorPanel"),t=document.getElementById("monitorDetachBtn");e.classList.contains("detached")?(e.classList.remove("detached"),e.style.top="",e.style.left="",e.style.width="",e.style.height="",t&&(t.innerHTML="&#x29C9;",t.title="Detach panel")):(e.classList.add("detached"),e.classList.add("open"),t&&(t.innerHTML="&#x229E;",t.title="Attach panel"),this.setupMonitorDrag())},setupMonitorDrag(){const e=document.getElementById("monitorPanel"),t=document.getElementById("monitorPanelHeader");if(!e||!t)return;let s=!1,n,a,o,r;const i=d=>{if(d.target.closest("button")||!e.classList.contains("detached"))return;s=!0;const u=getEventCoords(d);n=u.clientX,a=u.clientY;const h=e.getBoundingClientRect();o=h.left,r=h.top,document.addEventListener("mousemove",l),document.addEventListener("mouseup",c),document.addEventListener("touchmove",l,{passive:!1}),document.addEventListener("touchend",c),d.preventDefault()},l=d=>{if(!s)return;const u=getEventCoords(d),h=u.clientX-n,m=u.clientY-a;let p=o+h,f=r+m;const b=e.getBoundingClientRect();p=Math.max(0,Math.min(window.innerWidth-b.width,p)),f=Math.max(0,Math.min(window.innerHeight-b.height,f)),e.style.left=p+"px",e.style.top=f+"px"},c=()=>{s=!1,document.removeEventListener("mousemove",l),document.removeEventListener("mouseup",c),document.removeEventListener("touchmove",l),document.removeEventListener("touchend",c)};t.removeEventListener("mousedown",t._dragHandler),t.removeEventListener("touchstart",t._touchDragHandler),t._dragHandler=i,t._touchDragHandler=i,t.addEventListener("mousedown",i),t.addEventListener("touchstart",i,{passive:!1})},toggleSubagentsDetach(){const e=document.getElementById("subagentsPanel"),t=document.getElementById("subagentsDetachBtn");e.classList.contains("detached")?(e.classList.remove("detached"),e.style.top="",e.style.left="",e.style.width="",e.style.height="",t&&(t.innerHTML="&#x29C9;",t.title="Detach panel")):(e.classList.add("detached"),e.classList.add("open"),t&&(t.innerHTML="&#x229E;",t.title="Attach panel"),this.setupSubagentsDrag())},setupSubagentsDrag(){const e=document.getElementById("subagentsPanel"),t=document.getElementById("subagentsPanelHeader");if(!e||!t)return;let s=!1,n,a,o,r;const i=d=>{if(d.target.closest("button")||!e.classList.contains("detached"))return;s=!0;const u=getEventCoords(d);n=u.clientX,a=u.clientY;const h=e.getBoundingClientRect();o=h.left,r=h.top,document.addEventListener("mousemove",l),document.addEventListener("mouseup",c),document.addEventListener("touchmove",l,{passive:!1}),document.addEventListener("touchend",c),d.preventDefault()},l=d=>{if(!s)return;const u=getEventCoords(d),h=u.clientX-n,m=u.clientY-a;let p=o+h,f=r+m;const b=e.getBoundingClientRect();p=Math.max(0,Math.min(window.innerWidth-b.width,p)),f=Math.max(0,Math.min(window.innerHeight-b.height,f)),e.style.left=p+"px",e.style.top=f+"px"},c=()=>{s=!1,document.removeEventListener("mousemove",l),document.removeEventListener("mouseup",c),document.removeEventListener("touchmove",l),document.removeEventListener("touchend",c)};t.removeEventListener("mousedown",t._dragHandler),t.removeEventListener("touchstart",t._touchDragHandler),t._dragHandler=i,t._touchDragHandler=i,t.addEventListener("mousedown",i),t.addEventListener("touchstart",i,{passive:!1})},renderTaskPanel(){this._debouncedCall("taskPanel",this._renderTaskPanelImmediate)},_renderTaskPanelImmediate(){const e=this.sessions.get(this.activeSessionId),t=document.getElementById("backgroundTasksBody"),s=document.getElementById("taskPanelStats"),n=document.getElementById("backgroundTasksSection");if(!e||!e.taskTree||e.taskTree.length===0){n&&(n.style.display="none"),t.innerHTML="",s.textContent="0 tasks";return}n&&(n.style.display="");const a=e.taskStats||{running:0,completed:0,failed:0,total:0};s.textContent=`${a.running} running, ${a.completed} done`;const o=(l,c)=>{const d=l.status==="running"?"":l.status==="completed"?"&#x2713;":"&#x2717;",u=l.endTime?`${((l.endTime-l.startTime)/1e3).toFixed(1)}s`:`${((Date.now()-l.startTime)/1e3).toFixed(0)}s...`;let h="";if(l.children&&l.children.length>0){h='<div class="task-children">';for(const m of l.children){const p=c.find(f=>f.id===m);p&&(h+=`<div class="task-node">${o(p,c)}</div>`)}h+="</div>"}return`
32
+ `},closeTokenStats(){const e=document.getElementById("tokenStatsModal");e&&e.classList.remove("active")},async toggleMonitorPanel(){const e=document.getElementById("monitorPanel"),t=document.getElementById("monitorToggleBtn");e.classList.toggle("open"),e.classList.contains("open")?(e.style.display="",await this.loadMuxSessions(),await fetch("/api/mux-sessions/stats/start",{method:"POST"}),this.renderTaskPanel(),t&&(t.innerHTML="&#x25BC;")):(await fetch("/api/mux-sessions/stats/stop",{method:"POST"}),t&&(t.innerHTML="&#x25B2;"))},toggleTaskPanel(){this.toggleMonitorPanel()},toggleMonitorDetach(){const e=document.getElementById("monitorPanel"),t=document.getElementById("monitorDetachBtn");e.classList.contains("detached")?(e.classList.remove("detached"),e.style.top="",e.style.left="",e.style.width="",e.style.height="",t&&(t.innerHTML="&#x29C9;",t.title="Detach panel")):(e.classList.add("detached"),e.classList.add("open"),t&&(t.innerHTML="&#x229E;",t.title="Attach panel"),this.setupMonitorDrag())},setupMonitorDrag(){const e=document.getElementById("monitorPanel"),t=document.getElementById("monitorPanelHeader");if(!e||!t)return;let s=!1,n,a,o,r;const i=d=>{if(d.target.closest("button")||!e.classList.contains("detached"))return;s=!0;const u=getEventCoords(d);n=u.clientX,a=u.clientY;const h=e.getBoundingClientRect();o=h.left,r=h.top,document.addEventListener("mousemove",l),document.addEventListener("mouseup",c),document.addEventListener("touchmove",l,{passive:!1}),document.addEventListener("touchend",c),d.preventDefault()},l=d=>{if(!s)return;const u=getEventCoords(d),h=u.clientX-n,m=u.clientY-a;let p=o+h,f=r+m;const b=e.getBoundingClientRect();p=Math.max(0,Math.min(window.innerWidth-b.width,p)),f=Math.max(0,Math.min(window.innerHeight-b.height,f)),e.style.left=p+"px",e.style.top=f+"px"},c=()=>{s=!1,document.removeEventListener("mousemove",l),document.removeEventListener("mouseup",c),document.removeEventListener("touchmove",l),document.removeEventListener("touchend",c)};t.removeEventListener("mousedown",t._dragHandler),t.removeEventListener("touchstart",t._touchDragHandler),t._dragHandler=i,t._touchDragHandler=i,t.addEventListener("mousedown",i),t.addEventListener("touchstart",i,{passive:!1})},toggleSubagentsDetach(){const e=document.getElementById("subagentsPanel"),t=document.getElementById("subagentsDetachBtn");e.classList.contains("detached")?(e.classList.remove("detached"),e.style.top="",e.style.left="",e.style.width="",e.style.height="",t&&(t.innerHTML="&#x29C9;",t.title="Detach panel")):(e.classList.add("detached"),e.classList.add("open"),t&&(t.innerHTML="&#x229E;",t.title="Attach panel"),this.setupSubagentsDrag())},setupSubagentsDrag(){const e=document.getElementById("subagentsPanel"),t=document.getElementById("subagentsPanelHeader");if(!e||!t)return;let s=!1,n,a,o,r;const i=d=>{if(d.target.closest("button")||!e.classList.contains("detached"))return;s=!0;const u=getEventCoords(d);n=u.clientX,a=u.clientY;const h=e.getBoundingClientRect();o=h.left,r=h.top,document.addEventListener("mousemove",l),document.addEventListener("mouseup",c),document.addEventListener("touchmove",l,{passive:!1}),document.addEventListener("touchend",c),d.preventDefault()},l=d=>{if(!s)return;const u=getEventCoords(d),h=u.clientX-n,m=u.clientY-a;let p=o+h,f=r+m;const b=e.getBoundingClientRect();p=Math.max(0,Math.min(window.innerWidth-b.width,p)),f=Math.max(0,Math.min(window.innerHeight-b.height,f)),e.style.left=p+"px",e.style.top=f+"px"},c=()=>{s=!1,document.removeEventListener("mousemove",l),document.removeEventListener("mouseup",c),document.removeEventListener("touchmove",l),document.removeEventListener("touchend",c)};t.removeEventListener("mousedown",t._dragHandler),t.removeEventListener("touchstart",t._touchDragHandler),t._dragHandler=i,t._touchDragHandler=i,t.addEventListener("mousedown",i),t.addEventListener("touchstart",i,{passive:!1})},renderTaskPanel(){this._debouncedCall("taskPanel",this._renderTaskPanelImmediate)},_renderTaskPanelImmediate(){const e=this.sessions.get(this.activeSessionId),t=document.getElementById("backgroundTasksBody"),s=document.getElementById("taskPanelStats"),n=document.getElementById("backgroundTasksSection");if(!e||!e.taskTree||e.taskTree.length===0){n&&(n.style.display="none"),t.innerHTML="",s.textContent="0 tasks";return}n&&(n.style.display="");const a=e.taskStats||{running:0,completed:0,failed:0,total:0};s.textContent=`${a.running} running, ${a.completed} done`;const o=(l,c)=>{const d=l.status==="running"?"":l.status==="completed"?"&#x2713;":"&#x2717;",u=l.endTime?`${((l.endTime-l.startTime)/1e3).toFixed(1)}s`:`${((Date.now()-l.startTime)/1e3).toFixed(0)}s...`;let h="";if(l.children&&l.children.length>0){h='<div class="task-children">';for(const m of l.children){const p=c.find(f=>f.id===m);p&&(h+=`<div class="task-node">${o(p,c)}</div>`)}h+="</div>"}return`
33
33
  <div class="task-item">
34
34
  <span class="task-status-icon ${l.status}">${d}</span>
35
35
  <div class="task-info">
@@ -207,7 +207,7 @@
207
207
  onerror="this.parentElement.innerHTML='<div class=\\'image-error\\'>Failed to load image</div>'"
208
208
  onclick="app.openImageInNewTab('${escapeHtml(g)}')" />
209
209
  </div>
210
- `,document.body.appendChild(w);const T=this.makeWindowDraggable(w,w.querySelector(".image-popup-header"));w.addEventListener("mousedown",()=>{w.style.zIndex=++this.imagePopupZIndex}),this.imagePopups.set(i,{element:w,sessionId:t,filePath:s,dragListeners:T})},closeImagePopup(e){const t=this.imagePopups.get(e);t&&(t.dragListeners&&(document.removeEventListener("mousemove",t.dragListeners.move),document.removeEventListener("mouseup",t.dragListeners.up),t.dragListeners.touchMove&&(document.removeEventListener("touchmove",t.dragListeners.touchMove),document.removeEventListener("touchend",t.dragListeners.up),document.removeEventListener("touchcancel",t.dragListeners.up)),t.dragListeners.handle&&(t.dragListeners.handle.removeEventListener("mousedown",t.dragListeners.handleMouseDown),t.dragListeners.handle.removeEventListener("touchstart",t.dragListeners.handleTouchStart))),t.element.remove(),this.imagePopups.delete(e))},openImageInNewTab(e){window.open(e,"_blank")},closeSessionImagePopups(e){const t=[];for(const[s,n]of this.imagePopups)n.sessionId===e&&t.push(s);for(const s of t)this.closeImagePopup(s)},async loadMuxSessions(){try{const t=await(await fetch("/api/mux-sessions")).json();this.muxSessions=t.sessions||[],this.renderMuxSessions()}catch(e){console.error("Failed to load mux sessions:",e)}},killAllMuxSessions(){const e=this.muxSessions?.length||0;if(e===0){alert("No sessions to kill");return}document.getElementById("killAllCount").textContent=e;const t=document.getElementById("killAllModal");t.classList.add("active"),this.activeFocusTrap=new FocusTrap(t),this.activeFocusTrap.activate()},closeKillAllModal(){document.getElementById("killAllModal").classList.remove("active"),this.activeFocusTrap&&(this.activeFocusTrap.deactivate(),this.activeFocusTrap=null)},async confirmKillAll(e){this.closeKillAllModal();try{if(e){if((await(await fetch("/api/sessions",{method:"DELETE"})).json()).success){this.sessions.clear(),this.muxSessions=[],this.activeSessionId=null;try{localStorage.removeItem("codeman-active-session")}catch{}this.renderSessionTabs(),this.renderMuxSessions(),this.terminal.clear(),this.terminal.reset(),this.toast("All sessions and tmux killed","success")}}else{this.sessions.clear(),this.activeSessionId=null;try{localStorage.removeItem("codeman-active-session")}catch{}this.renderSessionTabs(),this.terminal.clear(),this.terminal.reset(),this.toast("All tabs removed, tmux still running","info")}}catch(t){console.error("Failed to kill sessions:",t),this.toast("Failed to kill sessions: "+t.message,"error")}},renderMuxSessions(){this._debouncedCall("muxSessions",this._renderMuxSessionsImmediate)},_renderMuxSessionsImmediate(){const e=document.getElementById("muxSessionsBody");if(!this.muxSessions||this.muxSessions.length===0){e.innerHTML='<div class="monitor-empty">No mux sessions</div>';return}let t="";for(const s of this.muxSessions){const n=s.stats||{memoryMB:0,cpuPercent:0,childCount:0},a=this.sessions.get(s.sessionId),o=a?a.status:"unknown",r=a?a.isWorking:!1;let i,l;o==="idle"&&!r?(i="IDLE",l="status-idle"):o==="busy"||r?(i="WORKING",l="status-working"):o==="stopped"?(i="STOPPED",l="status-stopped"):(i=o.toUpperCase(),l="");const c=a&&a.tokens?a.tokens:null,d=a?a.totalCost:0,u=a&&a.cliModel||"",h=u.includes("opus")?"opus":u.includes("sonnet")?"sonnet":u.includes("haiku")?"haiku":"",m=a?a.ralphTodoStats:null;let p="";if(m&&m.total>0){const T=Math.round(m.completed/m.total*100);p=`<span class="process-stat todo-progress">${m.completed}/${m.total} (${T}%)</span>`}let f="";c&&c.total>0&&(f=`<span class="process-stat tokens">${(c.total/1e3).toFixed(1)}k tok</span>`);let b="";d>0&&(b=`<span class="process-stat cost">$${d.toFixed(2)}</span>`);let g="";h&&(g=`<span class="monitor-model-badge ${h}">${h}</span>`);const w=escapeHtml(s.sessionId);t+=`
210
+ `,document.body.appendChild(w);const T=this.makeWindowDraggable(w,w.querySelector(".image-popup-header"));w.addEventListener("mousedown",()=>{w.style.zIndex=++this.imagePopupZIndex}),this.imagePopups.set(i,{element:w,sessionId:t,filePath:s,dragListeners:T})},closeImagePopup(e){const t=this.imagePopups.get(e);t&&(t.dragListeners&&(document.removeEventListener("mousemove",t.dragListeners.move),document.removeEventListener("mouseup",t.dragListeners.up),t.dragListeners.touchMove&&(document.removeEventListener("touchmove",t.dragListeners.touchMove),document.removeEventListener("touchend",t.dragListeners.up),document.removeEventListener("touchcancel",t.dragListeners.up)),t.dragListeners.handle&&(t.dragListeners.handle.removeEventListener("mousedown",t.dragListeners.handleMouseDown),t.dragListeners.handle.removeEventListener("touchstart",t.dragListeners.handleTouchStart))),t.element.remove(),this.imagePopups.delete(e))},openImageInNewTab(e){window.open(e,"_blank")},closeSessionImagePopups(e){const t=[];for(const[s,n]of this.imagePopups)n.sessionId===e&&t.push(s);for(const s of t)this.closeImagePopup(s)},async loadMuxSessions(){try{const t=await(await fetch("/api/mux-sessions")).json();this.muxSessions=t.data?.sessions||[],this.renderMuxSessions()}catch(e){console.error("Failed to load mux sessions:",e)}},killAllMuxSessions(){const e=this.muxSessions?.length||0;if(e===0){alert("No sessions to kill");return}document.getElementById("killAllCount").textContent=e;const t=document.getElementById("killAllModal");t.classList.add("active"),this.activeFocusTrap=new FocusTrap(t),this.activeFocusTrap.activate()},closeKillAllModal(){document.getElementById("killAllModal").classList.remove("active"),this.activeFocusTrap&&(this.activeFocusTrap.deactivate(),this.activeFocusTrap=null)},async confirmKillAll(e){this.closeKillAllModal();try{if(e){if((await(await fetch("/api/sessions",{method:"DELETE"})).json()).success){this.sessions.clear(),this.muxSessions=[],this.activeSessionId=null;try{localStorage.removeItem("codeman-active-session")}catch{}this.renderSessionTabs(),this.renderMuxSessions(),this.terminal.clear(),this.terminal.reset(),this.toast("All sessions and tmux killed","success")}}else{this.sessions.clear(),this.activeSessionId=null;try{localStorage.removeItem("codeman-active-session")}catch{}this.renderSessionTabs(),this.terminal.clear(),this.terminal.reset(),this.toast("All tabs removed, tmux still running","info")}}catch(t){console.error("Failed to kill sessions:",t),this.toast("Failed to kill sessions: "+t.message,"error")}},renderMuxSessions(){this._debouncedCall("muxSessions",this._renderMuxSessionsImmediate)},_renderMuxSessionsImmediate(){const e=document.getElementById("muxSessionsBody");if(!this.muxSessions||this.muxSessions.length===0){e.innerHTML='<div class="monitor-empty">No mux sessions</div>';return}let t="";for(const s of this.muxSessions){const n=s.stats||{memoryMB:0,cpuPercent:0,childCount:0},a=this.sessions.get(s.sessionId),o=a?a.status:"unknown",r=a?a.isWorking:!1;let i,l;o==="idle"&&!r?(i="IDLE",l="status-idle"):o==="busy"||r?(i="WORKING",l="status-working"):o==="stopped"?(i="STOPPED",l="status-stopped"):(i=o.toUpperCase(),l="");const c=a&&a.tokens?a.tokens:null,d=a?a.totalCost:0,u=a&&a.cliModel||"",h=u.includes("opus")?"opus":u.includes("sonnet")?"sonnet":u.includes("haiku")?"haiku":"",m=a?a.ralphTodoStats:null;let p="";if(m&&m.total>0){const T=Math.round(m.completed/m.total*100);p=`<span class="process-stat todo-progress">${m.completed}/${m.total} (${T}%)</span>`}let f="";c&&c.total>0&&(f=`<span class="process-stat tokens">${(c.total/1e3).toFixed(1)}k tok</span>`);let b="";d>0&&(b=`<span class="process-stat cost">$${d.toFixed(2)}</span>`);let g="";h&&(g=`<span class="monitor-model-badge ${h}">${h}</span>`);const w=escapeHtml(s.sessionId);t+=`
211
211
  <div class="process-item process-item-clickable" onclick="app.selectSession('${w}')" title="Switch to session">
212
212
  <span class="monitor-status-badge ${l}">${i}</span>
213
213
  <div class="process-info">
@@ -238,4 +238,4 @@
238
238
  ${o.status!=="completed"?`<button class="btn-toolbar btn-sm btn-danger" onclick="app.killSubagent('${escapeHtml(o.agentId)}')" title="Kill agent">Kill</button>`:""}
239
239
  </div>
240
240
  </div>
241
- `}e.innerHTML=a},async killMuxSession(e){if(confirm("Kill this mux session?")){try{await this.closeSession(e,!0)}catch{try{await fetch(`/api/mux-sessions/${e}`,{method:"DELETE"})}catch{}this.showToast("Tmux session killed","success")}this.muxSessions=this.muxSessions.filter(t=>t.sessionId!==e),this.renderMuxSessions()}},async reconcileMuxSessions(){try{const t=await(await fetch("/api/mux-sessions/reconcile",{method:"POST"})).json();t.dead&&t.dead.length>0?(this.showToast(`Found ${t.dead.length} dead mux session(s)`,"warning"),await this.loadMuxSessions()):this.showToast("All mux sessions are alive","success")}catch{this.showToast("Failed to reconcile mux sessions","error")}},toggleNotifications(){this.notificationManager?.toggleDrawer()},async launchMultiMonitor(){try{const e=await fetch("/api/system/span-displays",{method:"POST"}),t=await e.json().catch(()=>({}));e.ok&&t.success?this.showToast("Opening Codeman across all displays\u2026","success"):this.showToast(t.error||"Could not open spanning window","error")}catch(e){this.showToast("Could not open spanning window: "+(e?.message||e),"error")}},toast(e,t="info"){return this.showToast(e,t)},showToast(e,t="info",s={}){const{duration:n=3e3,action:a}=s,o=document.createElement("div");o.className=`toast toast-${t}`;const r=document.createElement("span");if(r.textContent=e,o.appendChild(r),a){const i=document.createElement("button");i.textContent=a.label,i.style.cssText="margin-left:12px;padding:2px 10px;background:rgba(255,255,255,0.15);border:1px solid rgba(255,255,255,0.3);border-radius:3px;color:inherit;cursor:pointer;font-size:12px",i.onclick=l=>{l.stopPropagation(),a.onClick(),o.remove()},o.appendChild(i)}this._toastContainer||(this._toastContainer=document.querySelector(".toast-container"),this._toastContainer||(this._toastContainer=document.createElement("div"),this._toastContainer.className="toast-container",document.body.appendChild(this._toastContainer))),this._toastContainer.appendChild(o),requestAnimationFrame(()=>o.classList.add("show")),setTimeout(()=>{o.classList.remove("show"),setTimeout(()=>o.remove(),200)},n)},startSystemStatsPolling(){this.stopSystemStatsPolling(),this.fetchSystemStats(),this.systemStatsInterval=setInterval(()=>{this.fetchSystemStats()},2e3)},stopSystemStatsPolling(){this.systemStatsInterval&&(clearInterval(this.systemStatsInterval),this.systemStatsInterval=null)},async fetchSystemStats(){const e=document.getElementById("headerSystemStats");if(!(!e||e.style.display==="none"))try{const s=await(await fetch("/api/system/stats")).json();this.updateSystemStatsDisplay(s)}catch{}},updateSystemStatsDisplay(e){const t=this.$("statCpu"),s=this.$("statCpuBar"),n=this.$("statMem"),a=this.$("statMemBar");if(t&&s&&(t.textContent=`${e.cpu}%`,s.style.width=`${Math.min(100,e.cpu)}%`,s.classList.remove("medium","high"),t.classList.remove("high"),e.cpu>80?(s.classList.add("high"),t.classList.add("high")):e.cpu>50&&s.classList.add("medium")),n&&a){const o=(e.memory.usedMB/1024).toFixed(1);n.textContent=`${o}G`,a.style.width=`${Math.min(100,e.memory.percent)}%`,a.classList.remove("medium","high"),n.classList.remove("high"),e.memory.percent>80?(a.classList.add("high"),n.classList.add("high")):e.memory.percent>50&&a.classList.add("medium")}},async _onClipboardWrite(e){const t=e?.text;if(typeof t=="string")try{await navigator.clipboard.writeText(t),this.showToast(`Copied to clipboard (${t.length} chars)`,"success")}catch{this._showClipboardFallback(t)}},_showClipboardFallback(e){const t=document.createElement("div");t.style.cssText="position:fixed;inset:0;background:rgba(0,0,0,0.6);z-index:10000;display:flex;align-items:center;justify-content:center";const s=document.createElement("div");s.style.cssText="background:#1e1e2e;border:1px solid #444;border-radius:8px;padding:16px;max-width:600px;width:90%;max-height:60vh;display:flex;flex-direction:column;gap:12px";const n=document.createElement("div");n.style.cssText="display:flex;justify-content:space-between;align-items:center";const a=document.createElement("span");a.style.cssText="color:#cdd6f4;font-weight:600",a.textContent="Clipboard (browser blocked auto-copy)";const o=document.createElement("button");o.style.cssText="background:none;border:none;color:#cdd6f4;font-size:18px;cursor:pointer",o.textContent="\xD7",n.appendChild(a),n.appendChild(o);const r=document.createElement("textarea");r.readOnly=!0,r.style.cssText="background:#181825;color:#cdd6f4;border:1px solid #555;border-radius:4px;padding:8px;font-family:monospace;font-size:13px;resize:none;height:200px;width:100%",r.value=e;const i=document.createElement("button");i.style.cssText="background:#89b4fa;color:#1e1e2e;border:none;border-radius:4px;padding:8px 16px;cursor:pointer;font-weight:600",i.textContent="Copy to Clipboard",s.appendChild(n),s.appendChild(r),s.appendChild(i),t.appendChild(s),document.body.appendChild(t),i.onclick=async()=>{try{await navigator.clipboard.writeText(e),this.showToast("Copied to clipboard","success"),t.remove()}catch{r.select(),document.execCommand("copy"),this.showToast("Copied (fallback)","success"),t.remove()}};const l=()=>t.remove();o.onclick=l,t.onclick=c=>{c.target===t&&l()}}});
241
+ `}e.innerHTML=a},async killMuxSession(e){if(confirm("Kill this mux session?")){try{await this.closeSession(e,!0)}catch{try{await fetch(`/api/mux-sessions/${e}`,{method:"DELETE"})}catch{}this.showToast("Tmux session killed","success")}this.muxSessions=this.muxSessions.filter(t=>t.sessionId!==e),this.renderMuxSessions()}},async reconcileMuxSessions(){try{const t=await(await fetch("/api/mux-sessions/reconcile",{method:"POST"})).json();t.data?.dead&&t.data.dead.length>0?(this.showToast(`Found ${t.data.dead.length} dead mux session(s)`,"warning"),await this.loadMuxSessions()):this.showToast("All mux sessions are alive","success")}catch{this.showToast("Failed to reconcile mux sessions","error")}},toggleNotifications(){this.notificationManager?.toggleDrawer()},async launchMultiMonitor(){try{const e=await fetch("/api/system/span-displays",{method:"POST"}),t=await e.json().catch(()=>({}));e.ok&&t.success?this.showToast("Opening Codeman across all displays\u2026","success"):this.showToast(t.error||"Could not open spanning window","error")}catch(e){this.showToast("Could not open spanning window: "+(e?.message||e),"error")}},toast(e,t="info"){return this.showToast(e,t)},showToast(e,t="info",s={}){const{duration:n=3e3,action:a}=s,o=document.createElement("div");o.className=`toast toast-${t}`;const r=document.createElement("span");if(r.textContent=e,o.appendChild(r),a){const i=document.createElement("button");i.textContent=a.label,i.style.cssText="margin-left:12px;padding:2px 10px;background:rgba(255,255,255,0.15);border:1px solid rgba(255,255,255,0.3);border-radius:3px;color:inherit;cursor:pointer;font-size:12px",i.onclick=l=>{l.stopPropagation(),a.onClick(),o.remove()},o.appendChild(i)}this._toastContainer||(this._toastContainer=document.querySelector(".toast-container"),this._toastContainer||(this._toastContainer=document.createElement("div"),this._toastContainer.className="toast-container",document.body.appendChild(this._toastContainer))),this._toastContainer.appendChild(o),requestAnimationFrame(()=>o.classList.add("show")),setTimeout(()=>{o.classList.remove("show"),setTimeout(()=>o.remove(),200)},n)},startSystemStatsPolling(){this.stopSystemStatsPolling(),this.fetchSystemStats(),this.systemStatsInterval=setInterval(()=>{this.fetchSystemStats()},2e3)},stopSystemStatsPolling(){this.systemStatsInterval&&(clearInterval(this.systemStatsInterval),this.systemStatsInterval=null)},async fetchSystemStats(){const e=document.getElementById("headerSystemStats");if(!(!e||e.style.display==="none"))try{const s=await(await fetch("/api/system/stats")).json();this.updateSystemStatsDisplay(s.data)}catch{}},updateSystemStatsDisplay(e){const t=this.$("statCpu"),s=this.$("statCpuBar"),n=this.$("statMem"),a=this.$("statMemBar");if(t&&s&&(t.textContent=`${e.cpu}%`,s.style.width=`${Math.min(100,e.cpu)}%`,s.classList.remove("medium","high"),t.classList.remove("high"),e.cpu>80?(s.classList.add("high"),t.classList.add("high")):e.cpu>50&&s.classList.add("medium")),n&&a){const o=(e.memory.usedMB/1024).toFixed(1);n.textContent=`${o}G`,a.style.width=`${Math.min(100,e.memory.percent)}%`,a.classList.remove("medium","high"),n.classList.remove("high"),e.memory.percent>80?(a.classList.add("high"),n.classList.add("high")):e.memory.percent>50&&a.classList.add("medium")}},async _onClipboardWrite(e){const t=e?.text;if(typeof t=="string")try{await navigator.clipboard.writeText(t),this.showToast(`Copied to clipboard (${t.length} chars)`,"success")}catch{this._showClipboardFallback(t)}},_showClipboardFallback(e){const t=document.createElement("div");t.style.cssText="position:fixed;inset:0;background:rgba(0,0,0,0.6);z-index:10000;display:flex;align-items:center;justify-content:center";const s=document.createElement("div");s.style.cssText="background:#1e1e2e;border:1px solid #444;border-radius:8px;padding:16px;max-width:600px;width:90%;max-height:60vh;display:flex;flex-direction:column;gap:12px";const n=document.createElement("div");n.style.cssText="display:flex;justify-content:space-between;align-items:center";const a=document.createElement("span");a.style.cssText="color:#cdd6f4;font-weight:600",a.textContent="Clipboard (browser blocked auto-copy)";const o=document.createElement("button");o.style.cssText="background:none;border:none;color:#cdd6f4;font-size:18px;cursor:pointer",o.textContent="\xD7",n.appendChild(a),n.appendChild(o);const r=document.createElement("textarea");r.readOnly=!0,r.style.cssText="background:#181825;color:#cdd6f4;border:1px solid #555;border-radius:4px;padding:8px;font-family:monospace;font-size:13px;resize:none;height:200px;width:100%",r.value=e;const i=document.createElement("button");i.style.cssText="background:#89b4fa;color:#1e1e2e;border:none;border-radius:4px;padding:8px 16px;cursor:pointer;font-weight:600",i.textContent="Copy to Clipboard",s.appendChild(n),s.appendChild(r),s.appendChild(i),t.appendChild(s),document.body.appendChild(t),i.onclick=async()=>{try{await navigator.clipboard.writeText(e),this.showToast("Copied to clipboard","success"),t.remove()}catch{r.select(),document.execCommand("copy"),this.showToast("Copied (fallback)","success"),t.remove()}};const l=()=>t.remove();o.onclick=l,t.onclick=c=>{c.target===t&&l()}}});
@@ -26,7 +26,7 @@
26
26
  <span>\u2713 ${escapeHtml(String(t.tasksCompletedThisLoop))} tasks</span>
27
27
  <span>${i} Tests: ${escapeHtml(t.testsStatus)}</span>
28
28
  </div>
29
- `;t.recommendation&&(o+=`<div class="ralph-status-block-recommendation">${escapeHtml(t.recommendation)}</div>`),e.innerHTML=o},renderRalphTasks(t){const e=this.$("ralphTasksGrid");if(!e)return;if(t.length===0){(e.children.length!==1||!e.querySelector(".ralph-state-empty"))&&(e.innerHTML='<div class="ralph-state-empty">No tasks detected</div>');return}const s={P0:0,P1:1,P2:2,null:3},a={in_progress:0,pending:1,completed:2},i=[...t].sort((o,l)=>{const n=s[o.priority]??3,c=s[l.priority]??3;return n!==c?n-c:(a[o.status]||1)-(a[l.status]||1)}),r=document.createDocumentFragment();i.forEach((o,l)=>{const n=this.createRalphTaskCard(o,l);r.appendChild(n)}),e.innerHTML="",e.appendChild(r)},createRalphTaskCard(t,e){const s=document.createElement("div"),a=`task-${t.status.replace("_","-")}`,i=t.priority?`task-priority-${t.priority.toLowerCase()}`:"";s.className=`ralph-task-card ${a} ${i}`.trim(),s.dataset.taskId=t.id||e;const r=document.createElement("span");if(r.className="ralph-task-icon",r.textContent=this.getRalphTaskIcon(t.status),s.appendChild(r),t.priority){const n=document.createElement("span");n.className=`ralph-task-priority priority-${t.priority.toLowerCase()}`,n.textContent=t.priority,s.appendChild(n)}const o=document.createElement("span");if(o.className="ralph-task-content",o.textContent=t.content,s.appendChild(o),t.attempts&&t.attempts>0){const n=document.createElement("span");n.className="ralph-task-attempts",t.lastError&&(n.classList.add("has-errors"),n.title=`Last error: ${t.lastError}`),n.textContent=`#${t.attempts}`,s.appendChild(n)}if(t.verificationCriteria){const n=document.createElement("span");n.className="ralph-task-verify-badge",n.title=`Verify: ${t.verificationCriteria}`,n.textContent="\u2713",s.appendChild(n)}if(t.dependencies&&t.dependencies.length>0){const n=document.createElement("span");n.className="ralph-task-deps-indicator",n.title=`Depends on: ${t.dependencies.join(", ")}`,n.textContent=`\u2197${t.dependencies.length}`,s.appendChild(n)}const l=document.createElement("div");if(l.className="ralph-task-actions",t.status!=="completed"){const n=document.createElement("button");n.className="ralph-task-action-btn",n.textContent="\u2713",n.title="Mark complete",n.onclick=c=>{c.stopPropagation(),this.updateRalphTaskStatus(t.id,"completed")},l.appendChild(n)}if(t.status==="completed"){const n=document.createElement("button");n.className="ralph-task-action-btn",n.textContent="\u21BA",n.title="Reopen",n.onclick=c=>{c.stopPropagation(),this.updateRalphTaskStatus(t.id,"pending")},l.appendChild(n)}if(t.lastError){const n=document.createElement("button");n.className="ralph-task-action-btn",n.textContent="\u21BB",n.title="Retry (clear error)",n.onclick=c=>{c.stopPropagation(),this.retryRalphTask(t.id)},l.appendChild(n)}return s.appendChild(l),s},async updateRalphTaskStatus(t,e){if(this.activeSessionId)try{const s=await fetch(`/api/sessions/${this.activeSessionId}/plan/task/${t}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({status:e})});if(!s.ok){const a=await s.json();throw new Error(a.error||"Failed to update task")}this.showToast(`Task ${e==="completed"?"completed":"reopened"}`,"success")}catch(s){this.showToast("Failed to update task: "+s.message,"error")}},async retryRalphTask(t){if(this.activeSessionId)try{const e=await fetch(`/api/sessions/${this.activeSessionId}/plan/task/${t}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({attempts:0,lastError:null,status:"pending"})});if(!e.ok){const s=await e.json();throw new Error(s.error||"Failed to retry task")}this.showToast("Task reset for retry","success")}catch(e){this.showToast("Failed to retry task: "+e.message,"error")}},getRalphTaskIcon(t){switch(t){case"completed":return"\u2713";case"in_progress":return"\u25D0";default:return"\u25CB"}},getTodoIcon(t){return this.getRalphTaskIcon(t)},updatePlanVersionDisplay(t,e){const s=this.$("ralphVersionRow"),a=this.$("ralphPlanVersion"),i=this.$("ralphRollbackBtn");s&&(t&&t>0?(s.style.display="",a&&(a.textContent=`v${t}`),i&&(i.style.display=e>1?"":"none")):s.style.display="none")},async showPlanHistory(){if(this.activeSessionId)try{const e=await(await fetch(`/api/sessions/${this.activeSessionId}/plan/history`)).json();if(e.error){this.showToast("Failed to load plan history: "+e.error,"error");return}const s=e.history||[];if(s.length===0){this.showToast("No plan history available","info");return}this.showPlanHistoryModal(s,e.currentVersion)}catch(t){this.showToast("Failed to load plan history: "+t.message,"error")}},showPlanHistoryModal(t,e){const s=document.getElementById("planHistoryModal");s&&s.remove();const a=document.createElement("div");a.id="planHistoryModal",a.className="modal active",a.innerHTML=`
29
+ `;t.recommendation&&(o+=`<div class="ralph-status-block-recommendation">${escapeHtml(t.recommendation)}</div>`),e.innerHTML=o},renderRalphTasks(t){const e=this.$("ralphTasksGrid");if(!e)return;if(t.length===0){(e.children.length!==1||!e.querySelector(".ralph-state-empty"))&&(e.innerHTML='<div class="ralph-state-empty">No tasks detected</div>');return}const s={P0:0,P1:1,P2:2,null:3},a={in_progress:0,pending:1,completed:2},i=[...t].sort((o,l)=>{const n=s[o.priority]??3,c=s[l.priority]??3;return n!==c?n-c:(a[o.status]||1)-(a[l.status]||1)}),r=document.createDocumentFragment();i.forEach((o,l)=>{const n=this.createRalphTaskCard(o,l);r.appendChild(n)}),e.innerHTML="",e.appendChild(r)},createRalphTaskCard(t,e){const s=document.createElement("div"),a=`task-${t.status.replace("_","-")}`,i=t.priority?`task-priority-${t.priority.toLowerCase()}`:"";s.className=`ralph-task-card ${a} ${i}`.trim(),s.dataset.taskId=t.id||e;const r=document.createElement("span");if(r.className="ralph-task-icon",r.textContent=this.getRalphTaskIcon(t.status),s.appendChild(r),t.priority){const n=document.createElement("span");n.className=`ralph-task-priority priority-${t.priority.toLowerCase()}`,n.textContent=t.priority,s.appendChild(n)}const o=document.createElement("span");if(o.className="ralph-task-content",o.textContent=t.content,s.appendChild(o),t.attempts&&t.attempts>0){const n=document.createElement("span");n.className="ralph-task-attempts",t.lastError&&(n.classList.add("has-errors"),n.title=`Last error: ${t.lastError}`),n.textContent=`#${t.attempts}`,s.appendChild(n)}if(t.verificationCriteria){const n=document.createElement("span");n.className="ralph-task-verify-badge",n.title=`Verify: ${t.verificationCriteria}`,n.textContent="\u2713",s.appendChild(n)}if(t.dependencies&&t.dependencies.length>0){const n=document.createElement("span");n.className="ralph-task-deps-indicator",n.title=`Depends on: ${t.dependencies.join(", ")}`,n.textContent=`\u2197${t.dependencies.length}`,s.appendChild(n)}const l=document.createElement("div");if(l.className="ralph-task-actions",t.status!=="completed"){const n=document.createElement("button");n.className="ralph-task-action-btn",n.textContent="\u2713",n.title="Mark complete",n.onclick=c=>{c.stopPropagation(),this.updateRalphTaskStatus(t.id,"completed")},l.appendChild(n)}if(t.status==="completed"){const n=document.createElement("button");n.className="ralph-task-action-btn",n.textContent="\u21BA",n.title="Reopen",n.onclick=c=>{c.stopPropagation(),this.updateRalphTaskStatus(t.id,"pending")},l.appendChild(n)}if(t.lastError){const n=document.createElement("button");n.className="ralph-task-action-btn",n.textContent="\u21BB",n.title="Retry (clear error)",n.onclick=c=>{c.stopPropagation(),this.retryRalphTask(t.id)},l.appendChild(n)}return s.appendChild(l),s},async updateRalphTaskStatus(t,e){if(this.activeSessionId)try{const s=await fetch(`/api/sessions/${this.activeSessionId}/plan/task/${t}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({status:e})});if(!s.ok){const a=await s.json();throw new Error(a.error||"Failed to update task")}this.showToast(`Task ${e==="completed"?"completed":"reopened"}`,"success")}catch(s){this.showToast("Failed to update task: "+s.message,"error")}},async retryRalphTask(t){if(this.activeSessionId)try{const e=await fetch(`/api/sessions/${this.activeSessionId}/plan/task/${t}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({attempts:0,lastError:null,status:"pending"})});if(!e.ok){const s=await e.json();throw new Error(s.error||"Failed to retry task")}this.showToast("Task reset for retry","success")}catch(e){this.showToast("Failed to retry task: "+e.message,"error")}},getRalphTaskIcon(t){switch(t){case"completed":return"\u2713";case"in_progress":return"\u25D0";default:return"\u25CB"}},getTodoIcon(t){return this.getRalphTaskIcon(t)},updatePlanVersionDisplay(t,e){const s=this.$("ralphVersionRow"),a=this.$("ralphPlanVersion"),i=this.$("ralphRollbackBtn");s&&(t&&t>0?(s.style.display="",a&&(a.textContent=`v${t}`),i&&(i.style.display=e>1?"":"none")):s.style.display="none")},async showPlanHistory(){if(this.activeSessionId)try{const e=await(await fetch(`/api/sessions/${this.activeSessionId}/plan/history`)).json();if(e.error){this.showToast("Failed to load plan history: "+e.error,"error");return}const s=e.data.history||[];if(s.length===0){this.showToast("No plan history available","info");return}this.showPlanHistoryModal(s,e.data.currentVersion)}catch(t){this.showToast("Failed to load plan history: "+t.message,"error")}},showPlanHistoryModal(t,e){const s=document.getElementById("planHistoryModal");s&&s.remove();const a=document.createElement("div");a.id="planHistoryModal",a.className="modal active",a.innerHTML=`
30
30
  <div class="modal-backdrop" onclick="app.closePlanHistoryModal()"></div>
31
31
  <div class="modal-content modal-sm">
32
32
  <div class="modal-header">
@@ -43,7 +43,7 @@
43
43
  onclick="app.rollbackToPlanVersion(${i.version})">
44
44
  <div>
45
45
  <span class="plan-history-version">v${i.version}</span>
46
- <span class="plan-history-tasks">${i.taskCount||0} tasks</span>
46
+ <span class="plan-history-tasks">${i.stats?.total??0} tasks</span>
47
47
  </div>
48
48
  <span class="plan-history-time">${this.formatRelativeTime(i.timestamp)}</span>
49
49
  </div>
@@ -151,11 +151,11 @@ Object.assign(CodemanApp.prototype, {
151
151
  const res = await fetch(`/api/cases/${encodeURIComponent(caseName)}/fix-plan`);
152
152
  const data = await res.json();
153
153
 
154
- if (data.success && data.exists && data.todos?.length > 0) {
154
+ if (data.success && data.data.exists && data.data.todos?.length > 0) {
155
155
  this.ralphWizardConfig.existingPlan = {
156
- todos: data.todos,
157
- stats: data.stats,
158
- content: data.content,
156
+ todos: data.data.todos,
157
+ stats: data.data.stats,
158
+ content: data.data.content,
159
159
  };
160
160
  this.updateExistingPlanUI();
161
161
  } else {
@@ -1054,8 +1054,8 @@ Object.assign(CodemanApp.prototype, {
1054
1054
  this.showToast(data.error || 'Failed to start', 'error');
1055
1055
  return;
1056
1056
  }
1057
- this.ralphClosedSessions.delete(data.sessionId);
1058
- await this.selectSession(data.sessionId);
1057
+ this.ralphClosedSessions.delete(data.data.sessionId);
1058
+ await this.selectSession(data.data.sessionId);
1059
1059
  this.showToast(`Ralph Loop started in ${config.caseName}`, 'success');
1060
1060
  } catch (err) {
1061
1061
  console.error('Failed to start Ralph loop:', err);
@@ -33,7 +33,7 @@
33
33
  `;else{o+=`| Time | Type | Severity | Title | Details |
34
34
  `,o+=`|------|------|----------|-------|----------|
35
35
  `;for(const d of s){const u=new Date(d.timestamp).toLocaleTimeString("en-US",{hour12:!1}),m=d.details?d.details.replace(/\|/g,"\\|"):"-";o+=`| ${u} | ${d.type} | ${d.severity} | ${d.title} | ${m} |
36
- `}}this.downloadFile(`${l}.md`,o,"text/markdown")}this.showToast(`Exported as ${e.toUpperCase()}`,"success")},downloadFile(e,t,s){const n=new Blob([t],{type:s}),i=URL.createObjectURL(n),a=document.createElement("a");a.href=i,a.download=e,document.body.appendChild(a),a.click(),document.body.removeChild(a),URL.revokeObjectURL(i)},async loadRunSummary(e){const t=document.getElementById("runSummaryTimeline");t.innerHTML='<p class="empty-message">Loading summary...</p>';try{const n=await(await fetch(`/api/sessions/${e}/run-summary`)).json();if(!n.success){t.innerHTML=`<p class="empty-message">Failed to load summary: ${escapeHtml(n.error)}</p>`;return}this.runSummaryData=n.summary,this.renderRunSummary()}catch(s){console.error("Failed to load run summary:",s),t.innerHTML='<p class="empty-message">Failed to load summary</p>'}},renderRunSummary(){if(!this.runSummaryData)return;const{stats:e,events:t,sessionName:s,startedAt:n,lastUpdatedAt:i}=this.runSummaryData,a=i-n;document.getElementById("runSummarySessionInfo").textContent=`${s||"Session"} - ${this.formatDuration(a)} total`;const c=this.filterRunSummaryEvents(t);this.renderRunSummaryTimeline(c)},filterRunSummaryEvents(e){return this.runSummaryFilter==="all"?e:e.filter(t=>{switch(this.runSummaryFilter){case"errors":return t.severity==="error";case"warnings":return t.severity==="warning"||t.severity==="error";case"respawn":return t.type.startsWith("respawn_")||t.type==="state_stuck";case"idle":return t.type==="idle_detected"||t.type==="working_detected";default:return!0}})},filterRunSummary(e){this.runSummaryFilter=e,document.querySelectorAll(".run-summary-filters .filter-btn").forEach(t=>{t.classList.toggle("active",t.dataset.filter===e)}),this.renderRunSummary()},renderRunSummaryTimeline(e){const t=document.getElementById("runSummaryTimeline");if(!e||e.length===0){t.innerHTML='<p class="empty-message">No events recorded yet</p>';return}const n=[...e].reverse().map(i=>{const a=new Date(i.timestamp).toLocaleTimeString("en-US",{hour12:!1}),c=`event-${i.severity}`,l=this.getEventIcon(i.type,i.severity);return`
36
+ `}}this.downloadFile(`${l}.md`,o,"text/markdown")}this.showToast(`Exported as ${e.toUpperCase()}`,"success")},downloadFile(e,t,s){const n=new Blob([t],{type:s}),i=URL.createObjectURL(n),a=document.createElement("a");a.href=i,a.download=e,document.body.appendChild(a),a.click(),document.body.removeChild(a),URL.revokeObjectURL(i)},async loadRunSummary(e){const t=document.getElementById("runSummaryTimeline");t.innerHTML='<p class="empty-message">Loading summary...</p>';try{const n=await(await fetch(`/api/sessions/${e}/run-summary`)).json();if(!n.success){t.innerHTML=`<p class="empty-message">Failed to load summary: ${escapeHtml(n.error)}</p>`;return}this.runSummaryData=n.data.summary,this.renderRunSummary()}catch(s){console.error("Failed to load run summary:",s),t.innerHTML='<p class="empty-message">Failed to load summary</p>'}},renderRunSummary(){if(!this.runSummaryData)return;const{stats:e,events:t,sessionName:s,startedAt:n,lastUpdatedAt:i}=this.runSummaryData,a=i-n;document.getElementById("runSummarySessionInfo").textContent=`${s||"Session"} - ${this.formatDuration(a)} total`;const c=this.filterRunSummaryEvents(t);this.renderRunSummaryTimeline(c)},filterRunSummaryEvents(e){return this.runSummaryFilter==="all"?e:e.filter(t=>{switch(this.runSummaryFilter){case"errors":return t.severity==="error";case"warnings":return t.severity==="warning"||t.severity==="error";case"respawn":return t.type.startsWith("respawn_")||t.type==="state_stuck";case"idle":return t.type==="idle_detected"||t.type==="working_detected";default:return!0}})},filterRunSummary(e){this.runSummaryFilter=e,document.querySelectorAll(".run-summary-filters .filter-btn").forEach(t=>{t.classList.toggle("active",t.dataset.filter===e)}),this.renderRunSummary()},renderRunSummaryTimeline(e){const t=document.getElementById("runSummaryTimeline");if(!e||e.length===0){t.innerHTML='<p class="empty-message">No events recorded yet</p>';return}const n=[...e].reverse().map(i=>{const a=new Date(i.timestamp).toLocaleTimeString("en-US",{hour12:!1}),c=`event-${i.severity}`,l=this.getEventIcon(i.type,i.severity);return`
37
37
  <div class="timeline-event ${c}">
38
38
  <div class="event-icon">${l}</div>
39
39
  <div class="event-content">
@@ -0,0 +1,36 @@
1
+ "use strict";Object.assign(CodemanApp.prototype,{buildEnvOverrides(e,t){const s={};return(e?.agentTeams||t?.agentTeamsEnabled)&&(s.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS="1"),s},getEffortSetting(e){const t=e?.thinkingEffort;return["low","medium","high","xhigh","max","ultracode"].includes(t)?t:void 0},async loadQuickStartCases(e=null,t=null){try{let s=null;try{const l=t?await t:await fetch("/api/settings").then(r=>r.ok?r.json():null).then(r=>r?.data??null);l&&(s=l.lastUsedCase||null)}catch{}const n=(await(await fetch("/api/cases")).json()).data;this.cases=n,console.log("[loadQuickStartCases] Loaded cases:",n.map(l=>l.name),"lastUsedCase:",s);const i=document.getElementById("quickStartCase");let o="";const m=n.some(l=>l.name==="testcase"),d=MobileDetection.getDeviceType()==="mobile"?8:20;if(n.forEach(l=>{const r=l.name.length>d?l.name.substring(0,d)+"\u2026":l.name;o+=`<option value="${escapeHtml(l.name)}">${escapeHtml(r)}</option>`}),m||(o='<option value="testcase">testcase</option>'+o),i.innerHTML=o,console.log("[loadQuickStartCases] Set options:",i.innerHTML.substring(0,200)),e)i.value=e,this.updateDirDisplayForCase(e),this.updateMobileCaseLabel(e);else if(s&&n.some(l=>l.name===s))i.value=s,this.updateDirDisplayForCase(s),this.updateMobileCaseLabel(s);else if(n.length>0){const l=n.find(r=>r.name==="testcase")||n[0];i.value=l.name,this.updateDirDisplayForCase(l.name),this.updateMobileCaseLabel(l.name)}else i.value="testcase",document.getElementById("dirDisplay").textContent="~/codeman-cases/testcase",this.updateMobileCaseLabel("testcase");i.dataset.listenerAdded||(i.addEventListener("change",()=>{this.updateDirDisplayForCase(i.value),this.saveLastUsedCase(i.value),this.updateMobileCaseLabel(i.value)}),i.dataset.listenerAdded="true")}catch(s){console.error("Failed to load cases:",s)}},async updateDirDisplayForCase(e){try{const s=(await(await fetch(`/api/cases/${e}`)).json()).data;s.path&&(document.getElementById("dirDisplay").textContent=s.path,document.getElementById("dirInput").value=s.path)}catch{document.getElementById("dirDisplay").textContent=e}},async saveLastUsedCase(e){try{await fetch("/api/settings",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({lastUsedCase:e})})}catch(t){console.error("Failed to save last used case:",t)}},async quickStart(){return this.run()},async run(){const e=this._runMode||"claude";return e==="opencode"?this.runOpenCode():e==="codex"?this.runCodex():this.runClaude()},setRunMode(e){this._runMode=e;try{localStorage.setItem("codeman_runMode",e)}catch{}this._applyRunMode(),this._apiPut("/api/settings",{runMode:e}).catch(()=>{}),document.getElementById("runModeMenu")?.classList.remove("active")},toggleRunModeMenu(e){e?.stopPropagation();const t=document.getElementById("runModeMenu");if(t&&(t.classList.toggle("active"),t.querySelectorAll(".run-mode-option").forEach(s=>{s.classList.toggle("selected",s.dataset.mode===this.runMode)}),t.classList.contains("active"))){this._loadRunModeHistory();const s=a=>{t.contains(a.target)||(t.classList.remove("active"),document.removeEventListener("click",s))};setTimeout(()=>document.addEventListener("click",s),0)}},async _loadRunModeHistory(){const e=document.getElementById("runModeHistory");if(e){e.innerHTML='<div class="run-mode-hist-empty">Loading...</div>';try{const t=await this._fetchHistorySessions(10);if(t.length===0){e.innerHTML='<div class="run-mode-hist-empty">No history</div>';return}e.replaceChildren();for(const s of t){const a=new Date(s.lastModified),n=a.toLocaleDateString("en",{month:"short",day:"numeric"})+" "+a.toLocaleTimeString("en",{hour:"2-digit",minute:"2-digit",hour12:!1}),i=s.workingDir.replace(/^\/home\/[^/]+\//,"~/"),o=document.createElement("button");o.className="run-mode-option",o.title=s.workingDir,o.dataset.sessionId=s.sessionId,o.dataset.workingDir=s.workingDir;const m=document.createElement("span");m.className="hist-dir",m.textContent=i;const c=document.createElement("span");c.className="hist-meta",c.textContent=n,o.append(m,c),o.addEventListener("click",d=>{d.stopPropagation(),this.resumeHistorySession(s.sessionId,s.workingDir)}),e.appendChild(o)}}catch{e.innerHTML='<div class="run-mode-hist-empty">Failed to load</div>'}}},_applyRunMode(){const e=this.runMode,t=document.getElementById("runBtn"),s=t?.nextElementSibling,a=document.getElementById("runBtnLabel");t&&(t.className=`btn-toolbar btn-run mode-${e}`),s&&(s.className=`btn-toolbar btn-run-gear mode-${e}`),a&&(a.textContent=e==="opencode"?"Run OC":e==="codex"?"Run CX":"Run")},_initRunMode(){try{this._runMode=localStorage.getItem("codeman_runMode")||"claude"}catch{this._runMode="claude"}this._applyRunMode()},incrementTabCount(){const e=document.getElementById("tabCount"),t=parseInt(e.value)||1;e.value=Math.min(20,t+1)},decrementTabCount(){const e=document.getElementById("tabCount"),t=parseInt(e.value)||1;e.value=Math.max(1,t-1)},incrementShellCount(){const e=document.getElementById("shellCount"),t=parseInt(e.value)||1;e.value=Math.min(20,t+1)},decrementShellCount(){const e=document.getElementById("shellCount"),t=parseInt(e.value)||1;e.value=Math.max(1,t-1)},async runClaude(){const e=document.getElementById("quickStartCase").value||"testcase",t=Math.min(20,Math.max(1,parseInt(document.getElementById("tabCount").value)||1));this.terminal.clear(),this.terminal.writeln(`\x1B[1;32m Starting ${t} Claude session(s) in ${e}...\x1B[0m`),this.terminal.writeln(""),this.terminal.focus();try{let a=(await(await fetch(`/api/cases/${e}`)).json())?.data??{};if(!a.path){const y=await(await fetch("/api/cases",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,description:""})})).json();if(!y.success)throw new Error(y.error||"Failed to create case");a=y.data.case}const n=a.path;if(!n)throw new Error("Case path not found");let i=null,o=1;for(const[,h]of this.sessions){const y=h.name&&h.name.match(/^w(\d+)-([a-zA-Z0-9_-]+)/);if(y&&y[2]===e){const b=parseInt(y[1]);b>=o&&(o=b+1)}}const m=this.isRalphTrackerEnabledByDefault(),c=[];for(let h=0;h<t;h++)c.push(`w${o+h}-${e}`);const d=this.getCaseSettings(e),l=this.loadAppSettingsFromStorage(),r=this.buildEnvOverrides(d,l),u=Object.keys(r).length>0,g=this.getEffortSetting(l),f=d.opusContext1m||l.opusContext1mEnabled,p=l.claudeModel||(f?"opus[1m]":"");this.terminal.writeln(`\x1B[90m Creating ${t} session(s)...\x1B[0m`);const w=c.map(h=>fetch("/api/sessions",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({workingDir:n,name:h,...u?{envOverrides:r}:{},...g?{effort:g}:{},...p!==void 0?{modelOverride:p}:{}})}).then(y=>y.json())),v=await Promise.all(w),C=[];for(const h of v){if(!h.success)throw new Error(h.error);C.push(h.data.session.id)}i=C[0],await Promise.all(C.map(h=>fetch(`/api/sessions/${h}/ralph-config`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({enabled:m,disableAutoEnable:!m})}))),this.terminal.writeln(`\x1B[90m Starting ${t} session(s) in parallel...\x1B[0m`),await Promise.all(C.map(h=>fetch(`/api/sessions/${h}/interactive`,{method:"POST"}))),this.terminal.writeln(`\x1B[90m All ${t} sessions ready\x1B[0m`),i&&(await this.selectSession(i),this.loadQuickStartCases()),this.terminal.focus()}catch(s){this.terminal.writeln(`\x1B[1;31m Error: ${s.message}\x1B[0m`)}},stopClaude(){if(!this.activeSessionId)return;const e=document.querySelector(".btn-toolbar.btn-stop");e&&(this._stopConfirmTimer?(clearTimeout(this._stopConfirmTimer),this._stopConfirmTimer=null,e.innerHTML=e.dataset.origHtml,delete e.dataset.origHtml,e.classList.remove("confirming"),fetch(`/api/sessions/${this.activeSessionId}/input`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({input:""})})):(e.dataset.origHtml=e.innerHTML,e.textContent="Tap again",e.classList.add("confirming"),this._stopConfirmTimer=setTimeout(()=>{this._stopConfirmTimer=null,e.dataset.origHtml&&(e.innerHTML=e.dataset.origHtml,delete e.dataset.origHtml),e.classList.remove("confirming")},2e3)))},async runShell(){const e=document.getElementById("quickStartCase").value||"testcase",t=Math.min(20,Math.max(1,parseInt(document.getElementById("shellCount").value)||1));this.terminal.clear(),this.terminal.writeln(`\x1B[1;33m Starting ${t} Shell session(s) in ${e}...\x1B[0m`),this.terminal.writeln("");try{let a=(await(await fetch(`/api/cases/${e}`)).json())?.data??{};if(!a.path){const u=await(await fetch("/api/cases",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,description:""})})).json();if(!u.success)throw new Error(u.error||"Failed to create case");a=u.data.case}const n=a.path;if(!n)throw new Error("Case path not found");let i=1;for(const[,r]of this.sessions){const u=r.name&&r.name.match(/^s(\d+)-([a-zA-Z0-9_-]+)/);if(u&&u[2]===e){const g=parseInt(u[1]);g>=i&&(i=g+1)}}const o=[];for(let r=0;r<t;r++)o.push(`s${i+r}-${e}`);const m=o.map(r=>fetch("/api/sessions",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({workingDir:n,mode:"shell",name:r})}).then(u=>u.json())),c=await Promise.all(m),d=[];for(const r of c){if(!r.success)throw new Error(r.error);d.push(r.data.session.id)}await Promise.all(d.map(r=>fetch(`/api/sessions/${r}/shell`,{method:"POST"})));const l=this.getTerminalDimensions();l&&await Promise.all(d.map(r=>fetch(`/api/sessions/${r}/resize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(l)}))),d.length>0&&(this.activeSessionId=d[0],await this.selectSession(d[0])),this.terminal.focus()}catch(s){this.terminal.writeln(`\x1B[1;31m Error: ${s.message}\x1B[0m`)}},async runOpenCode(){const e=document.getElementById("quickStartCase").value||"testcase";this.terminal.clear(),this.terminal.writeln(`\x1B[1;32m Starting OpenCode session in ${e}...\x1B[0m`),this.terminal.writeln(""),this.terminal.focus();try{if(!(await(await fetch("/api/opencode/status")).json()).data.available){this.terminal.writeln("\x1B[1;31m OpenCode CLI not found.\x1B[0m"),this.terminal.writeln("\x1B[90m Install with: curl -fsSL https://opencode.ai/install | bash\x1B[0m");return}const a=this.buildEnvOverrides(this.getCaseSettings(e),this.loadAppSettingsFromStorage()),i=await(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({caseName:e,mode:"opencode",openCodeConfig:{autoAllowTools:!0},...Object.keys(a).length>0?{envOverrides:a}:{}})})).json();if(!i.success)throw new Error(i.error||"Failed to start OpenCode");i.data.sessionId&&await this.selectSession(i.data.sessionId),this.terminal.focus()}catch(t){this.terminal.writeln(`\x1B[1;31m Error: ${t.message}\x1B[0m`)}},async runCodex(){const e=document.getElementById("quickStartCase").value||"testcase";this.terminal.clear(),this.terminal.writeln(`\x1B[1;32m Starting Codex session in ${e}...\x1B[0m`),this.terminal.writeln(""),this.terminal.focus();try{if(!(await(await fetch("/api/codex/status")).json()).data.available){this.terminal.writeln("\x1B[1;31m Codex CLI not found.\x1B[0m"),this.terminal.writeln("\x1B[90m Install with: npm install -g @openai/codex\x1B[0m");return}const a=this.loadAppSettingsFromStorage(),n=this.buildEnvOverrides(this.getCaseSettings(e),a),o=await(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({caseName:e,mode:"codex",codexConfig:{dangerouslyBypassApprovals:a.codexDangerouslyBypassApprovals??!1,renderMode:"hybrid"},...Object.keys(n).length>0?{envOverrides:n}:{}})})).json();if(!o.success)throw new Error(o.error||"Failed to start Codex");o.data.sessionId&&await this.selectSession(o.data.sessionId),this.terminal.focus()}catch(t){this.terminal.writeln(`\x1B[1;31m Error: ${t.message}\x1B[0m`)}},openSessionOptions(e){const t=this.sessions.get(e);if(!t)return;this.editingSessionId=e,this.switchOptionsTab(t.mode==="opencode"||t.mode==="codex"?"summary":"respawn");const s=document.getElementById("sessionRespawnStatus"),a=document.getElementById("modalEnableRespawnBtn"),n=document.getElementById("modalStopRespawnBtn");this.respawnStatus[e]?(s.classList.add("active"),s.querySelector(".respawn-status-text").textContent=this.respawnStatus[e].state||"Active",a.style.display="none",n.style.display=""):(s.classList.remove("active"),s.querySelector(".respawn-status-text").textContent="Not active",a.style.display="",n.style.display="none");const i=document.getElementById("sessionRespawnSection");t.mode==="claude"&&t.pid?i.style.display="":i.style.display="none";const o=t.mode==="opencode"||t.mode==="codex";document.querySelectorAll("[data-claude-only]").forEach(p=>{p.style.display=o?"none":""}),this.selectDurationPreset(""),this.loadSavedRespawnConfig(e),document.getElementById("modalAutoCompactEnabled").checked=t.autoCompactEnabled??!1,document.getElementById("modalAutoCompactThreshold").value=t.autoCompactThreshold??11e4,document.getElementById("modalAutoCompactPrompt").value=t.autoCompactPrompt??"",document.getElementById("modalAutoClearEnabled").checked=t.autoClearEnabled??!1,document.getElementById("modalAutoClearThreshold").value=t.autoClearThreshold??14e4,document.getElementById("modalImageWatcherEnabled").checked=t.imageWatcherEnabled??!0,document.getElementById("modalFlickerFilterEnabled").checked=t.flickerFilterEnabled??!1;const c=parseSessionPrefix(t.name),d=document.getElementById("modalSessionPrefix");c?(d.textContent=c.prefix+": ",d.style.display="",document.getElementById("modalSessionName").value=c.suffix,document.getElementById("modalSessionName").placeholder="Add description..."):(d.style.display="none",d.textContent="",document.getElementById("modalSessionName").value=t.name||"",document.getElementById("modalSessionName").placeholder="Auto (directory name)");const l=t.color||"default";document.getElementById("sessionColorPicker")?.querySelectorAll(".color-swatch").forEach(p=>{p.classList.toggle("selected",p.dataset.color===l)}),this.renderPresetDropdown(),document.getElementById("respawnPresetSelect").value="",document.getElementById("presetDescriptionHint").textContent="";const u=document.querySelector('#sessionOptionsModal .modal-tab-btn[data-tab="ralph"]'),g=document.querySelector('#sessionOptionsModal .modal-tab-btn[data-tab="respawn"]');if(o?(u&&(u.style.display="none"),g&&(g.style.display="none"),this.switchOptionsTab("context")):(u&&(u.style.display=""),g&&(g.style.display="")),!o){const p=this.ralphStates.get(e);this.populateRalphForm({enabled:p?.loop?.enabled??t.ralphLoop?.enabled??!1,completionPhrase:p?.loop?.completionPhrase||t.ralphLoop?.completionPhrase||"",maxIterations:p?.loop?.maxIterations||t.ralphLoop?.maxIterations||0})}const f=document.getElementById("sessionOptionsModal");f.classList.add("active"),this.activeFocusTrap=new FocusTrap(f),this.activeFocusTrap.activate()},async saveSessionName(){if(!this.editingSessionId)return;const e=this.sessions.get(this.editingSessionId),t=e?parseSessionPrefix(e.name):null,s=document.getElementById("modalSessionName").value.trim();let a;t?a=t.prefix+(s?": "+s:""):a=s;try{await this._apiPut(`/api/sessions/${this.editingSessionId}/name`,{name:a})}catch(n){this.showToast("Failed to save session name: "+n.message,"error")}},async autoSaveAutoCompact(){if(this.editingSessionId)try{await this._apiPost(`/api/sessions/${this.editingSessionId}/auto-compact`,{enabled:document.getElementById("modalAutoCompactEnabled").checked,threshold:parseInt(document.getElementById("modalAutoCompactThreshold").value)||11e4,prompt:document.getElementById("modalAutoCompactPrompt").value.trim()||void 0})}catch{}},async autoSaveAutoClear(){if(this.editingSessionId)try{await this._apiPost(`/api/sessions/${this.editingSessionId}/auto-clear`,{enabled:document.getElementById("modalAutoClearEnabled").checked,threshold:parseInt(document.getElementById("modalAutoClearThreshold").value)||14e4})}catch{}},async toggleSessionImageWatcher(){if(!this.editingSessionId)return;const e=document.getElementById("modalImageWatcherEnabled").checked;try{await this._apiPost(`/api/sessions/${this.editingSessionId}/image-watcher`,{enabled:e});const t=this.sessions.get(this.editingSessionId);t&&(t.imageWatcherEnabled=e),this.showToast(`Image watcher ${e?"enabled":"disabled"}`,"success")}catch{this.showToast("Failed to toggle image watcher","error")}},async toggleFlickerFilter(){if(!this.editingSessionId)return;const e=document.getElementById("modalFlickerFilterEnabled").checked;try{await this._apiPost(`/api/sessions/${this.editingSessionId}/flicker-filter`,{enabled:e});const t=this.sessions.get(this.editingSessionId);t&&(t.flickerFilterEnabled=e),this.showToast(`Flicker filter ${e?"enabled":"disabled"}`,"success")}catch{this.showToast("Failed to toggle flicker filter","error")}},async autoSaveRespawnConfig(){if(!this.editingSessionId)return;const e={updatePrompt:document.getElementById("modalRespawnPrompt").value,sendClear:document.getElementById("modalRespawnSendClear").checked,sendInit:document.getElementById("modalRespawnSendInit").checked,kickstartPrompt:document.getElementById("modalRespawnKickstart").value.trim()||void 0,autoAcceptPrompts:document.getElementById("modalRespawnAutoAccept").checked};try{await this._apiPut(`/api/sessions/${this.editingSessionId}/respawn/config`,e)}catch{}},async loadSavedRespawnConfig(e){try{const s=await(await fetch(`/api/sessions/${e}/respawn/config`)).json();if(s.success&&s.data&&s.data.config){const a=s.data.config;document.getElementById("modalRespawnPrompt").value=a.updatePrompt||"update all the docs and CLAUDE.md",document.getElementById("modalRespawnSendClear").checked=a.sendClear??!0,document.getElementById("modalRespawnSendInit").checked=a.sendInit??!0,document.getElementById("modalRespawnKickstart").value=a.kickstartPrompt||"",document.getElementById("modalRespawnAutoAccept").checked=a.autoAcceptPrompts??!0,a.durationMinutes&&(document.querySelector(`.duration-preset-btn[data-minutes="${a.durationMinutes}"]`)?this.selectDurationPreset(String(a.durationMinutes)):(this.selectDurationPreset("custom"),document.getElementById("modalRespawnDuration").value=a.durationMinutes))}}catch{}},selectDurationPreset(e){document.querySelectorAll(".duration-preset-btn").forEach(n=>n.classList.remove("active"));const t=document.querySelector(`.duration-preset-btn[data-minutes="${e}"]`);t&&t.classList.add("active");const s=document.querySelector(".duration-custom-input"),a=document.getElementById("modalRespawnDuration");e==="custom"?(s.classList.add("visible"),a.focus()):(s.classList.remove("visible"),a.value="")},getSelectedDuration(){const e=document.querySelector(".duration-custom-input"),t=document.getElementById("modalRespawnDuration");if(e.classList.contains("visible"))return t.value?parseInt(t.value):null;{const a=document.querySelector(".duration-preset-btn.active")?.dataset.minutes;return a?parseInt(a):null}},switchOptionsTab(e){document.querySelectorAll("#sessionOptionsModal .modal-tab-btn").forEach(t=>{t.classList.toggle("active",t.dataset.tab===e)}),document.getElementById("respawn-tab").classList.toggle("hidden",e!=="respawn"),document.getElementById("context-tab").classList.toggle("hidden",e!=="context"),document.getElementById("ralph-tab").classList.toggle("hidden",e!=="ralph"),document.getElementById("summary-tab").classList.toggle("hidden",e!=="summary"),e==="summary"&&this.editingSessionId&&this.loadRunSummary(this.editingSessionId)},getRalphConfig(){return{enabled:document.getElementById("modalRalphEnabled").checked,completionPhrase:document.getElementById("modalRalphPhrase").value.trim(),maxIterations:parseInt(document.getElementById("modalRalphMaxIterations").value)||0,maxTodos:parseInt(document.getElementById("modalRalphMaxTodos").value)||50,todoExpirationMinutes:parseInt(document.getElementById("modalRalphTodoExpiration").value)||60}},populateRalphForm(e){document.getElementById("modalRalphEnabled").checked=e?.enabled??!1,document.getElementById("modalRalphPhrase").value=e?.completionPhrase||"",document.getElementById("modalRalphMaxIterations").value=e?.maxIterations||0,document.getElementById("modalRalphMaxTodos").value=e?.maxTodos||50,document.getElementById("modalRalphTodoExpiration").value=e?.todoExpirationMinutes||60},async saveRalphConfig(){if(!this.editingSessionId){this.showToast("No session selected","warning");return}const e=this.getRalphConfig();e.enabled&&this.ralphClosedSessions.delete(this.editingSessionId);try{const s=await(await fetch(`/api/sessions/${this.editingSessionId}/ralph-config`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})).json();if(s.error)throw new Error(s.error);this.showToast("Ralph config saved","success")}catch(t){this.showToast("Failed to save Ralph config: "+t.message,"error")}},startInlineRename(e){const t=this.sessions.get(e);if(!t)return;const s=document.querySelector(`.tab-name[data-session-id="${e}"]`);if(!s)return;this._inlineRenameActive=!0;const a=this.getSessionName(t),n=parseSessionPrefix(t.name),i=s.textContent;for(s.textContent="";s.firstChild;)s.removeChild(s.firstChild);if(n){const c=document.createElement("span");c.textContent=n.prefix+": ",c.style.cssText="color: var(--text-muted); font-size: 0.75rem; white-space: nowrap;",s.appendChild(c)}const o=document.createElement("input");o.type="text",o.value=n?n.suffix:t.name||"",o.placeholder=n?"Add description...":a,o.className="tab-rename-input",o.style.cssText="width: 80px; font-size: 0.75rem; padding: 2px 4px; background: var(--bg-input); border: 1px solid var(--accent); border-radius: 3px; color: var(--text); outline: none;",s.appendChild(o),o.focus(),o.select();const m=async({commit:c})=>{if(!this._inlineRenameActive)return;if(this._inlineRenameActive=!1,this._activeRename=null,!c){this.renderSessionTabs();return}const d=o.value.trim(),l=n?n.prefix+(d?": "+d:""):d;if(s.textContent=l||i,this.sessions.has(e)&&l!==t.name)try{await fetch(`/api/sessions/${e}/name`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:l})})}catch{s.textContent=i,this.showToast("Failed to rename","error")}this.renderSessionTabs()};this._activeRename={sessionId:e,cancel:()=>m({commit:!1})},o.addEventListener("blur",()=>m({commit:!0})),o.addEventListener("keydown",c=>{c.isComposing||c.keyCode===229||(c.key==="Enter"?(c.preventDefault(),o.blur()):c.key==="Escape"&&(o.value="",o.blur()))})},toggleCaseSettings(){const e=document.getElementById("caseSettingsPopover");if(e.classList.contains("hidden")){const t=document.getElementById("quickStartCase").value||"testcase",s=this.getCaseSettings(t);document.getElementById("caseAgentTeams").checked=s.agentTeams,document.getElementById("caseOpusContext1m").checked=s.opusContext1m,e.classList.remove("hidden");const a=n=>{!e.contains(n.target)&&!n.target.classList.contains("btn-case-settings")&&(e.classList.add("hidden"),document.removeEventListener("click",a))};setTimeout(()=>document.addEventListener("click",a),0)}else e.classList.add("hidden")},getCaseSettings(e){try{const t=localStorage.getItem("caseSettings_"+e);if(t)return JSON.parse(t)}catch{}return{agentTeams:!1,opusContext1m:!0}},saveCaseSettings(e,t){localStorage.setItem("caseSettings_"+e,JSON.stringify(t))},onCaseSettingChanged(){const e=document.getElementById("quickStartCase").value||"testcase",t=this.getCaseSettings(e);t.agentTeams=document.getElementById("caseAgentTeams").checked,t.opusContext1m=document.getElementById("caseOpusContext1m").checked,this.saveCaseSettings(e,t);const s=document.getElementById("caseAgentTeamsMobile");s&&(s.checked=t.agentTeams);const a=document.getElementById("caseOpusContext1mMobile");a&&(a.checked=t.opusContext1m)},toggleCaseSettingsMobile(){const e=document.getElementById("caseSettingsPopoverMobile");if(e.classList.contains("hidden")){const t=document.getElementById("quickStartCase").value||"testcase",s=this.getCaseSettings(t);document.getElementById("caseAgentTeamsMobile").checked=s.agentTeams,document.getElementById("caseOpusContext1mMobile").checked=s.opusContext1m,e.classList.remove("hidden");const a=n=>{!e.contains(n.target)&&!n.target.classList.contains("btn-case-settings-mobile")&&(e.classList.add("hidden"),document.removeEventListener("click",a))};setTimeout(()=>document.addEventListener("click",a),0)}else e.classList.add("hidden")},onCaseSettingChangedMobile(){const e=document.getElementById("quickStartCase").value||"testcase",t=this.getCaseSettings(e);t.agentTeams=document.getElementById("caseAgentTeamsMobile").checked,t.opusContext1m=document.getElementById("caseOpusContext1mMobile").checked,this.saveCaseSettings(e,t);const s=document.getElementById("caseAgentTeams");s&&(s.checked=t.agentTeams);const a=document.getElementById("caseOpusContext1m");a&&(a.checked=t.opusContext1m)},showCreateCaseModal(){document.getElementById("newCaseName").value="",document.getElementById("newCaseDescription").value="",document.getElementById("linkCaseName").value="",document.getElementById("linkCasePath").value="",this.caseModalTab="case-create",this.switchCaseModalTab("case-create");const e=document.getElementById("createCaseModal");e.querySelectorAll(".modal-tabs .modal-tab-btn").forEach(t=>{t.onclick=()=>this.switchCaseModalTab(t.dataset.tab)}),e.querySelectorAll('input[type="text"]').forEach(t=>{t._mobileScrollWired||(t._mobileScrollWired=!0,t.addEventListener("focus",()=>{window.innerWidth<=430&&setTimeout(()=>t.scrollIntoView({behavior:"smooth",block:"center"}),300)}))}),e.classList.add("active"),document.getElementById("newCaseName").focus()},switchCaseModalTab(e){this.caseModalTab=e;const t=document.getElementById("createCaseModal");t.querySelectorAll(".modal-tabs .modal-tab-btn").forEach(a=>{a.classList.toggle("active",a.dataset.tab===e)}),t.querySelectorAll(".modal-tab-content").forEach(a=>{a.classList.toggle("hidden",a.id!==e)});const s=document.getElementById("caseModalSubmit");e==="case-manage"?(s.style.display="none",this.renderCaseManageList()):(s.style.display="",s.textContent=e==="case-create"?"Create":"Link"),e==="case-create"?document.getElementById("newCaseName").focus():e==="case-link"&&document.getElementById("linkCaseName").focus()},closeCreateCaseModal(){document.getElementById("createCaseModal").classList.remove("active")},async submitCaseModal(){const e=document.getElementById("caseModalSubmit"),t=e.textContent;e.classList.add("loading"),e.textContent=this.caseModalTab==="case-create"?"Creating...":"Linking...";try{this.caseModalTab==="case-create"?await this.createCase():await this.linkCase()}finally{e.classList.remove("loading"),e.textContent=t}},async createCase(){const e=document.getElementById("newCaseName").value.trim(),t=document.getElementById("newCaseDescription").value.trim();if(!e){this.showToast("Please enter a case name","error");return}if(!/^[a-zA-Z0-9_-]+$/.test(e)){this.showToast("Invalid name. Use only letters, numbers, hyphens, underscores.","error");return}try{const a=await(await fetch("/api/cases",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,description:t})})).json();a.success?(this.closeCreateCaseModal(),this.showToast(`Case "${e}" created`,"success"),await this.loadQuickStartCases(e),await this.saveLastUsedCase(e)):this.showToast(a.error||"Failed to create case","error")}catch(s){console.error("Failed to create case:",s),this.showToast("Failed to create case: "+s.message,"error")}},async linkCase(){const e=document.getElementById("linkCaseName").value.trim(),t=document.getElementById("linkCasePath").value.trim();if(!e){this.showToast("Please enter a case name","error");return}if(!/^[a-zA-Z0-9_-]+$/.test(e)){this.showToast("Invalid name. Use only letters, numbers, hyphens, underscores.","error");return}if(!t){this.showToast("Please enter a folder path","error");return}try{const a=await(await fetch("/api/cases/link",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,path:t})})).json();a.success?(this.closeCreateCaseModal(),this.showToast(`Case "${e}" linked to ${t}`,"success"),await this.loadQuickStartCases(e),await this.saveLastUsedCase(e)):this.showToast(a.error||"Failed to link case","error")}catch(s){console.error("Failed to link case:",s),this.showToast("Failed to link case: "+s.message,"error")}},renderCaseManageList(){const e=document.getElementById("caseManageList"),t=this.cases||[];if(t.length===0){e.innerHTML='<div class="form-hint" style="text-align: center; padding: 2rem 0;">No cases yet</div>';return}let s="";t.forEach((a,n)=>{const i=n===0,o=n===t.length-1,m=a.path?a.path.replace(/^\/Users\/[^/]+/,"~"):"";s+=`
2
+ <div class="case-manage-item" data-case="${escapeHtml(a.name)}">
3
+ <div class="case-manage-info">
4
+ <span class="case-manage-name">${escapeHtml(a.name)}</span>
5
+ <span class="case-manage-path">${escapeHtml(m)}</span>
6
+ </div>
7
+ <div class="case-manage-actions">
8
+ <button class="case-manage-btn" onclick="app.moveCaseUp('${escapeHtml(a.name)}')"
9
+ title="Move up" ${i?"disabled":""}>&#x25B2;</button>
10
+ <button class="case-manage-btn" onclick="app.moveCaseDown('${escapeHtml(a.name)}')"
11
+ title="Move down" ${o?"disabled":""}>&#x25BC;</button>
12
+ <button class="case-manage-btn case-manage-btn-delete" onclick="app.deleteCase('${escapeHtml(a.name)}')"
13
+ title="Delete case">&#x2715;</button>
14
+ </div>
15
+ </div>
16
+ `}),e.innerHTML=s},async moveCaseUp(e){const t=this.cases||[],s=t.findIndex(n=>n.name===e);if(s<=0)return;const a=[...t];[a[s-1],a[s]]=[a[s],a[s-1]],this.cases=a,this.renderCaseManageList(),await this.saveCaseOrder(a.map(n=>n.name))},async moveCaseDown(e){const t=this.cases||[],s=t.findIndex(n=>n.name===e);if(s<0||s>=t.length-1)return;const a=[...t];[a[s],a[s+1]]=[a[s+1],a[s]],this.cases=a,this.renderCaseManageList(),await this.saveCaseOrder(a.map(n=>n.name))},async deleteCase(e){if(confirm(`Delete case "${e}"? Linked cases will only be unlinked (folder preserved). Created cases will be permanently deleted.`))try{const s=await(await fetch(`/api/cases/${encodeURIComponent(e)}`,{method:"DELETE"})).json();if(s.success){this.showToast(`Case "${e}" ${s.data?.type==="unlinked"?"unlinked":"deleted"}`,"success"),this.cases=(this.cases||[]).filter(i=>i.name!==e),this.renderCaseManageList();const n=document.getElementById("quickStartCase").value;await this.loadQuickStartCases(n===e?null:n)}else this.showToast(s.error||"Failed to delete case","error")}catch(t){this.showToast("Failed to delete case: "+t.message,"error")}},async saveCaseOrder(e){try{await fetch("/api/cases/order",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({order:e})});const s=document.getElementById("quickStartCase").value;await this.loadQuickStartCases(s)}catch(t){this.showToast("Failed to save case order: "+t.message,"error")}},showMobileCasePicker(){const e=document.getElementById("mobileCasePickerModal"),t=document.getElementById("mobileCaseList"),a=document.getElementById("quickStartCase").value;let n="";const i=this.cases||[],m=i.some(c=>c.name==="testcase")?i:[{name:"testcase"},...i];for(const c of m){const d=c.name===a;n+=`
17
+ <button class="mobile-case-item ${d?"selected":""}"
18
+ onclick="app.selectMobileCase('${escapeHtml(c.name)}')">
19
+ <span class="mobile-case-item-icon">
20
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
21
+ <path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
22
+ </svg>
23
+ </span>
24
+ <span class="mobile-case-item-name">${escapeHtml(c.name)}</span>
25
+ <span class="mobile-case-item-delete" onclick="event.stopPropagation(); app.deleteCaseMobile('${escapeHtml(c.name)}')" title="Delete">
26
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
27
+ <line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
28
+ </svg>
29
+ </span>
30
+ <span class="mobile-case-item-check">
31
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
32
+ <polyline points="20 6 9 17 4 12"/>
33
+ </svg>
34
+ </span>
35
+ </button>
36
+ `}t.innerHTML=n,e.classList.add("active")},closeMobileCasePicker(){document.getElementById("mobileCasePickerModal").classList.remove("active")},selectMobileCase(e){const t=document.getElementById("quickStartCase");t.value=e,this.updateMobileCaseLabel(e),this.updateDirDisplayForCase(e),this.saveLastUsedCase(e),this.closeMobileCasePicker(),this.showToast(`Selected: ${e}`,"success")},updateMobileCaseLabel(e){const t=document.getElementById("mobileCaseName");t&&(t.textContent=e)},async deleteCaseMobile(e){if(confirm(`Delete case "${e}"?`))try{const s=await(await fetch(`/api/cases/${encodeURIComponent(e)}`,{method:"DELETE"})).json();s.success?(this.showToast(`Case "${e}" ${s.data?.type==="unlinked"?"unlinked":"deleted"}`,"success"),this.cases=(this.cases||[]).filter(a=>a.name!==e),this.closeMobileCasePicker(),await this.loadQuickStartCases()):this.showToast(s.error||"Failed to delete case","error")}catch(t){this.showToast("Failed to delete case: "+t.message,"error")}},showCreateCaseFromMobile(){this.closeMobileCasePicker(),this.showCreateCaseModal();const e=document.getElementById("createCaseModal");e.classList.add("from-mobile"),setTimeout(()=>e.classList.remove("from-mobile"),300)}}),Object.defineProperty(CodemanApp.prototype,"runMode",{configurable:!0,enumerable:!0,get(){return this._runMode||"claude"},set(e){this._runMode=e==="opencode"||e==="codex"||e==="claude"?e:"claude"}});