aicodeman 0.2.8 → 0.3.0

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 (348) hide show
  1. package/README.md +91 -0
  2. package/dist/ai-idle-checker.d.ts.map +1 -1
  3. package/dist/ai-idle-checker.js +3 -2
  4. package/dist/ai-idle-checker.js.map +1 -1
  5. package/dist/ai-plan-checker.d.ts.map +1 -1
  6. package/dist/ai-plan-checker.js +3 -2
  7. package/dist/ai-plan-checker.js.map +1 -1
  8. package/dist/bash-tool-parser.d.ts +2 -3
  9. package/dist/bash-tool-parser.d.ts.map +1 -1
  10. package/dist/bash-tool-parser.js +14 -31
  11. package/dist/bash-tool-parser.js.map +1 -1
  12. package/dist/config/ai-defaults.d.ts +16 -0
  13. package/dist/config/ai-defaults.d.ts.map +1 -0
  14. package/dist/config/ai-defaults.js +16 -0
  15. package/dist/config/ai-defaults.js.map +1 -0
  16. package/dist/config/auth-config.d.ts +19 -0
  17. package/dist/config/auth-config.d.ts.map +1 -0
  18. package/dist/config/auth-config.js +28 -0
  19. package/dist/config/auth-config.js.map +1 -0
  20. package/dist/config/exec-timeout.d.ts +10 -0
  21. package/dist/config/exec-timeout.d.ts.map +1 -0
  22. package/dist/config/exec-timeout.js +10 -0
  23. package/dist/config/exec-timeout.js.map +1 -0
  24. package/dist/config/map-limits.d.ts +4 -0
  25. package/dist/config/map-limits.d.ts.map +1 -1
  26. package/dist/config/map-limits.js +7 -0
  27. package/dist/config/map-limits.js.map +1 -1
  28. package/dist/config/server-timing.d.ts +36 -0
  29. package/dist/config/server-timing.d.ts.map +1 -0
  30. package/dist/config/server-timing.js +51 -0
  31. package/dist/config/server-timing.js.map +1 -0
  32. package/dist/config/team-config.d.ts +16 -0
  33. package/dist/config/team-config.d.ts.map +1 -0
  34. package/dist/config/team-config.js +16 -0
  35. package/dist/config/team-config.js.map +1 -0
  36. package/dist/config/terminal-limits.d.ts +18 -0
  37. package/dist/config/terminal-limits.d.ts.map +1 -0
  38. package/dist/config/terminal-limits.js +18 -0
  39. package/dist/config/terminal-limits.js.map +1 -0
  40. package/dist/config/tunnel-config.d.ts +27 -0
  41. package/dist/config/tunnel-config.d.ts.map +1 -0
  42. package/dist/config/tunnel-config.js +36 -0
  43. package/dist/config/tunnel-config.js.map +1 -0
  44. package/dist/hooks-config.d.ts.map +1 -1
  45. package/dist/hooks-config.js +7 -6
  46. package/dist/hooks-config.js.map +1 -1
  47. package/dist/image-watcher.d.ts +4 -4
  48. package/dist/image-watcher.d.ts.map +1 -1
  49. package/dist/image-watcher.js +17 -30
  50. package/dist/image-watcher.js.map +1 -1
  51. package/dist/index.js +1 -2
  52. package/dist/index.js.map +1 -1
  53. package/dist/plan-orchestrator.d.ts +2 -24
  54. package/dist/plan-orchestrator.d.ts.map +1 -1
  55. package/dist/plan-orchestrator.js.map +1 -1
  56. package/dist/push-store.d.ts +1 -1
  57. package/dist/push-store.d.ts.map +1 -1
  58. package/dist/push-store.js +4 -12
  59. package/dist/push-store.js.map +1 -1
  60. package/dist/ralph-fix-plan-watcher.d.ts +91 -0
  61. package/dist/ralph-fix-plan-watcher.d.ts.map +1 -0
  62. package/dist/ralph-fix-plan-watcher.js +326 -0
  63. package/dist/ralph-fix-plan-watcher.js.map +1 -0
  64. package/dist/ralph-plan-tracker.d.ts +201 -0
  65. package/dist/ralph-plan-tracker.d.ts.map +1 -0
  66. package/dist/ralph-plan-tracker.js +325 -0
  67. package/dist/ralph-plan-tracker.js.map +1 -0
  68. package/dist/ralph-stall-detector.d.ts +84 -0
  69. package/dist/ralph-stall-detector.d.ts.map +1 -0
  70. package/dist/ralph-stall-detector.js +139 -0
  71. package/dist/ralph-stall-detector.js.map +1 -0
  72. package/dist/ralph-status-parser.d.ts +141 -0
  73. package/dist/ralph-status-parser.d.ts.map +1 -0
  74. package/dist/ralph-status-parser.js +478 -0
  75. package/dist/ralph-status-parser.js.map +1 -0
  76. package/dist/ralph-tracker.d.ts +194 -685
  77. package/dist/ralph-tracker.d.ts.map +1 -1
  78. package/dist/ralph-tracker.js +349 -1713
  79. package/dist/ralph-tracker.js.map +1 -1
  80. package/dist/respawn-adaptive-timing.d.ts +61 -0
  81. package/dist/respawn-adaptive-timing.d.ts.map +1 -0
  82. package/dist/respawn-adaptive-timing.js +105 -0
  83. package/dist/respawn-adaptive-timing.js.map +1 -0
  84. package/dist/respawn-controller.d.ts +14 -101
  85. package/dist/respawn-controller.d.ts.map +1 -1
  86. package/dist/respawn-controller.js +155 -594
  87. package/dist/respawn-controller.js.map +1 -1
  88. package/dist/respawn-health.d.ts +54 -0
  89. package/dist/respawn-health.d.ts.map +1 -0
  90. package/dist/respawn-health.js +183 -0
  91. package/dist/respawn-health.js.map +1 -0
  92. package/dist/respawn-metrics.d.ts +81 -0
  93. package/dist/respawn-metrics.d.ts.map +1 -0
  94. package/dist/respawn-metrics.js +198 -0
  95. package/dist/respawn-metrics.js.map +1 -0
  96. package/dist/respawn-patterns.d.ts +45 -0
  97. package/dist/respawn-patterns.d.ts.map +1 -0
  98. package/dist/respawn-patterns.js +125 -0
  99. package/dist/respawn-patterns.js.map +1 -0
  100. package/dist/session-auto-ops.d.ts +89 -0
  101. package/dist/session-auto-ops.d.ts.map +1 -0
  102. package/dist/session-auto-ops.js +224 -0
  103. package/dist/session-auto-ops.js.map +1 -0
  104. package/dist/session-cli-builder.d.ts +62 -0
  105. package/dist/session-cli-builder.d.ts.map +1 -0
  106. package/dist/session-cli-builder.js +121 -0
  107. package/dist/session-cli-builder.js.map +1 -0
  108. package/dist/session-task-cache.d.ts +52 -0
  109. package/dist/session-task-cache.d.ts.map +1 -0
  110. package/dist/session-task-cache.js +90 -0
  111. package/dist/session-task-cache.js.map +1 -0
  112. package/dist/session.d.ts +2 -33
  113. package/dist/session.d.ts.map +1 -1
  114. package/dist/session.js +58 -309
  115. package/dist/session.js.map +1 -1
  116. package/dist/state-store.d.ts +9 -2
  117. package/dist/state-store.d.ts.map +1 -1
  118. package/dist/state-store.js +112 -39
  119. package/dist/state-store.js.map +1 -1
  120. package/dist/subagent-watcher.d.ts +16 -9
  121. package/dist/subagent-watcher.d.ts.map +1 -1
  122. package/dist/subagent-watcher.js +126 -147
  123. package/dist/subagent-watcher.js.map +1 -1
  124. package/dist/team-watcher.d.ts +3 -0
  125. package/dist/team-watcher.d.ts.map +1 -1
  126. package/dist/team-watcher.js +54 -5
  127. package/dist/team-watcher.js.map +1 -1
  128. package/dist/tmux-manager.d.ts.map +1 -1
  129. package/dist/tmux-manager.js +1 -2
  130. package/dist/tmux-manager.js.map +1 -1
  131. package/dist/tunnel-manager.d.ts +26 -0
  132. package/dist/tunnel-manager.d.ts.map +1 -1
  133. package/dist/tunnel-manager.js +127 -7
  134. package/dist/tunnel-manager.js.map +1 -1
  135. package/dist/types/api.d.ts +93 -0
  136. package/dist/types/api.d.ts.map +1 -0
  137. package/dist/types/api.js +83 -0
  138. package/dist/types/api.js.map +1 -0
  139. package/dist/types/app-state.d.ts +100 -0
  140. package/dist/types/app-state.d.ts.map +1 -0
  141. package/dist/types/app-state.js +59 -0
  142. package/dist/types/app-state.js.map +1 -0
  143. package/dist/types/common.d.ts +70 -0
  144. package/dist/types/common.d.ts.map +1 -0
  145. package/dist/types/common.js +8 -0
  146. package/dist/types/common.js.map +1 -0
  147. package/dist/types/index.d.ts +18 -0
  148. package/dist/types/index.d.ts.map +1 -0
  149. package/dist/types/index.js +18 -0
  150. package/dist/types/index.js.map +1 -0
  151. package/dist/types/lifecycle.d.ts +17 -0
  152. package/dist/types/lifecycle.d.ts.map +1 -0
  153. package/dist/types/lifecycle.js +5 -0
  154. package/dist/types/lifecycle.js.map +1 -0
  155. package/dist/types/plan.d.ts +32 -0
  156. package/dist/types/plan.d.ts.map +1 -0
  157. package/dist/types/plan.js +5 -0
  158. package/dist/types/plan.js.map +1 -0
  159. package/dist/types/push.d.ts +23 -0
  160. package/dist/types/push.d.ts.map +1 -0
  161. package/dist/types/push.js +5 -0
  162. package/dist/types/push.js.map +1 -0
  163. package/dist/types/ralph.d.ts +241 -0
  164. package/dist/types/ralph.d.ts.map +1 -0
  165. package/dist/types/ralph.js +49 -0
  166. package/dist/types/ralph.js.map +1 -0
  167. package/dist/types/respawn.d.ts +250 -0
  168. package/dist/types/respawn.d.ts.map +1 -0
  169. package/dist/types/respawn.js +5 -0
  170. package/dist/types/respawn.js.map +1 -0
  171. package/dist/types/run-summary.d.ts +81 -0
  172. package/dist/types/run-summary.d.ts.map +1 -0
  173. package/dist/types/run-summary.js +22 -0
  174. package/dist/types/run-summary.js.map +1 -0
  175. package/dist/types/session.d.ts +130 -0
  176. package/dist/types/session.d.ts.map +1 -0
  177. package/dist/types/session.js +5 -0
  178. package/dist/types/session.js.map +1 -0
  179. package/dist/types/task.d.ts +58 -0
  180. package/dist/types/task.d.ts.map +1 -0
  181. package/dist/types/task.js +5 -0
  182. package/dist/types/task.js.map +1 -0
  183. package/dist/types/teams.d.ts +55 -0
  184. package/dist/types/teams.d.ts.map +1 -0
  185. package/dist/types/teams.js +5 -0
  186. package/dist/types/teams.js.map +1 -0
  187. package/dist/types/tools.d.ts +46 -0
  188. package/dist/types/tools.d.ts.map +1 -0
  189. package/dist/types/tools.js +5 -0
  190. package/dist/types/tools.js.map +1 -0
  191. package/dist/types.d.ts +1 -1138
  192. package/dist/types.d.ts.map +1 -1
  193. package/dist/types.js +1 -214
  194. package/dist/types.js.map +1 -1
  195. package/dist/utils/claude-cli-resolver.d.ts.map +1 -1
  196. package/dist/utils/claude-cli-resolver.js +1 -2
  197. package/dist/utils/claude-cli-resolver.js.map +1 -1
  198. package/dist/utils/debouncer.d.ts +111 -0
  199. package/dist/utils/debouncer.d.ts.map +1 -0
  200. package/dist/utils/debouncer.js +162 -0
  201. package/dist/utils/debouncer.js.map +1 -0
  202. package/dist/utils/index.d.ts +3 -2
  203. package/dist/utils/index.d.ts.map +1 -1
  204. package/dist/utils/index.js +3 -2
  205. package/dist/utils/index.js.map +1 -1
  206. package/dist/utils/opencode-cli-resolver.d.ts.map +1 -1
  207. package/dist/utils/opencode-cli-resolver.js +1 -2
  208. package/dist/utils/opencode-cli-resolver.js.map +1 -1
  209. package/dist/utils/string-similarity.d.ts +0 -57
  210. package/dist/utils/string-similarity.d.ts.map +1 -1
  211. package/dist/utils/string-similarity.js +3 -18
  212. package/dist/utils/string-similarity.js.map +1 -1
  213. package/dist/web/middleware/auth.d.ts +31 -0
  214. package/dist/web/middleware/auth.d.ts.map +1 -0
  215. package/dist/web/middleware/auth.js +154 -0
  216. package/dist/web/middleware/auth.js.map +1 -0
  217. package/dist/web/ports/auth-port.d.ts +18 -0
  218. package/dist/web/ports/auth-port.d.ts.map +1 -0
  219. package/dist/web/ports/auth-port.js +6 -0
  220. package/dist/web/ports/auth-port.js.map +1 -0
  221. package/dist/web/ports/config-port.d.ts +28 -0
  222. package/dist/web/ports/config-port.d.ts.map +1 -0
  223. package/dist/web/ports/config-port.js +6 -0
  224. package/dist/web/ports/config-port.js.map +1 -0
  225. package/dist/web/ports/event-port.d.ts +13 -0
  226. package/dist/web/ports/event-port.d.ts.map +1 -0
  227. package/dist/web/ports/event-port.js +6 -0
  228. package/dist/web/ports/event-port.js.map +1 -0
  229. package/dist/web/ports/index.d.ts +14 -0
  230. package/dist/web/ports/index.d.ts.map +1 -0
  231. package/dist/web/ports/index.js +9 -0
  232. package/dist/web/ports/index.js.map +1 -0
  233. package/dist/web/ports/infra-port.d.ts +36 -0
  234. package/dist/web/ports/infra-port.d.ts.map +1 -0
  235. package/dist/web/ports/infra-port.js +6 -0
  236. package/dist/web/ports/infra-port.js.map +1 -0
  237. package/dist/web/ports/respawn-port.d.ts +20 -0
  238. package/dist/web/ports/respawn-port.d.ts.map +1 -0
  239. package/dist/web/ports/respawn-port.js +6 -0
  240. package/dist/web/ports/respawn-port.js.map +1 -0
  241. package/dist/web/ports/session-port.d.ts +15 -0
  242. package/dist/web/ports/session-port.d.ts.map +1 -0
  243. package/dist/web/ports/session-port.js +6 -0
  244. package/dist/web/ports/session-port.js.map +1 -0
  245. package/dist/web/public/api-client.js +70 -0
  246. package/dist/web/public/api-client.js.br +0 -0
  247. package/dist/web/public/api-client.js.gz +0 -0
  248. package/dist/web/public/app.js +152 -236
  249. package/dist/web/public/app.js.br +0 -0
  250. package/dist/web/public/app.js.gz +0 -0
  251. package/dist/web/public/constants.js +238 -0
  252. package/dist/web/public/constants.js.br +0 -0
  253. package/dist/web/public/constants.js.gz +0 -0
  254. package/dist/web/public/index.html +11 -3
  255. package/dist/web/public/index.html.br +0 -0
  256. package/dist/web/public/index.html.gz +0 -0
  257. package/dist/web/public/keyboard-accessory.js +279 -0
  258. package/dist/web/public/keyboard-accessory.js.br +0 -0
  259. package/dist/web/public/keyboard-accessory.js.gz +0 -0
  260. package/dist/web/public/mobile-handlers.js +467 -0
  261. package/dist/web/public/mobile-handlers.js.br +0 -0
  262. package/dist/web/public/mobile-handlers.js.gz +0 -0
  263. package/dist/web/public/mobile.css.gz +0 -0
  264. package/dist/web/public/notification-manager.js +445 -0
  265. package/dist/web/public/notification-manager.js.br +0 -0
  266. package/dist/web/public/notification-manager.js.gz +0 -0
  267. package/dist/web/public/ralph-wizard.js +3 -3
  268. package/dist/web/public/ralph-wizard.js.br +0 -0
  269. package/dist/web/public/ralph-wizard.js.gz +0 -0
  270. package/dist/web/public/styles.css.gz +0 -0
  271. package/dist/web/public/subagent-windows.js +1115 -0
  272. package/dist/web/public/subagent-windows.js.br +0 -0
  273. package/dist/web/public/subagent-windows.js.gz +0 -0
  274. package/dist/web/public/sw.js.gz +0 -0
  275. package/dist/web/public/upload.html.gz +0 -0
  276. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  277. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  278. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  279. package/dist/web/public/vendor/xterm.css.gz +0 -0
  280. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  281. package/dist/web/public/voice-input.js +858 -0
  282. package/dist/web/public/voice-input.js.br +0 -0
  283. package/dist/web/public/voice-input.js.gz +0 -0
  284. package/dist/web/route-helpers.d.ts +38 -0
  285. package/dist/web/route-helpers.d.ts.map +1 -0
  286. package/dist/web/route-helpers.js +143 -0
  287. package/dist/web/route-helpers.js.map +1 -0
  288. package/dist/web/routes/case-routes.d.ts +9 -0
  289. package/dist/web/routes/case-routes.d.ts.map +1 -0
  290. package/dist/web/routes/case-routes.js +419 -0
  291. package/dist/web/routes/case-routes.js.map +1 -0
  292. package/dist/web/routes/file-routes.d.ts +8 -0
  293. package/dist/web/routes/file-routes.d.ts.map +1 -0
  294. package/dist/web/routes/file-routes.js +337 -0
  295. package/dist/web/routes/file-routes.js.map +1 -0
  296. package/dist/web/routes/hook-event-routes.d.ts +9 -0
  297. package/dist/web/routes/hook-event-routes.d.ts.map +1 -0
  298. package/dist/web/routes/hook-event-routes.js +57 -0
  299. package/dist/web/routes/hook-event-routes.js.map +1 -0
  300. package/dist/web/routes/index.d.ts +16 -0
  301. package/dist/web/routes/index.d.ts.map +1 -0
  302. package/dist/web/routes/index.js +16 -0
  303. package/dist/web/routes/index.js.map +1 -0
  304. package/dist/web/routes/mux-routes.d.ts +8 -0
  305. package/dist/web/routes/mux-routes.d.ts.map +1 -0
  306. package/dist/web/routes/mux-routes.js +32 -0
  307. package/dist/web/routes/mux-routes.js.map +1 -0
  308. package/dist/web/routes/plan-routes.d.ts +9 -0
  309. package/dist/web/routes/plan-routes.d.ts.map +1 -0
  310. package/dist/web/routes/plan-routes.js +381 -0
  311. package/dist/web/routes/plan-routes.js.map +1 -0
  312. package/dist/web/routes/push-routes.d.ts +8 -0
  313. package/dist/web/routes/push-routes.d.ts.map +1 -0
  314. package/dist/web/routes/push-routes.js +49 -0
  315. package/dist/web/routes/push-routes.js.map +1 -0
  316. package/dist/web/routes/ralph-routes.d.ts +9 -0
  317. package/dist/web/routes/ralph-routes.d.ts.map +1 -0
  318. package/dist/web/routes/ralph-routes.js +475 -0
  319. package/dist/web/routes/ralph-routes.js.map +1 -0
  320. package/dist/web/routes/respawn-routes.d.ts +8 -0
  321. package/dist/web/routes/respawn-routes.d.ts.map +1 -0
  322. package/dist/web/routes/respawn-routes.js +260 -0
  323. package/dist/web/routes/respawn-routes.js.map +1 -0
  324. package/dist/web/routes/scheduled-routes.d.ts +8 -0
  325. package/dist/web/routes/scheduled-routes.d.ts.map +1 -0
  326. package/dist/web/routes/scheduled-routes.js +51 -0
  327. package/dist/web/routes/scheduled-routes.js.map +1 -0
  328. package/dist/web/routes/session-routes.d.ts +9 -0
  329. package/dist/web/routes/session-routes.d.ts.map +1 -0
  330. package/dist/web/routes/session-routes.js +729 -0
  331. package/dist/web/routes/session-routes.js.map +1 -0
  332. package/dist/web/routes/system-routes.d.ts +9 -0
  333. package/dist/web/routes/system-routes.d.ts.map +1 -0
  334. package/dist/web/routes/system-routes.js +678 -0
  335. package/dist/web/routes/system-routes.js.map +1 -0
  336. package/dist/web/routes/team-routes.d.ts +8 -0
  337. package/dist/web/routes/team-routes.d.ts.map +1 -0
  338. package/dist/web/routes/team-routes.js +14 -0
  339. package/dist/web/routes/team-routes.js.map +1 -0
  340. package/dist/web/schemas.d.ts +43 -3
  341. package/dist/web/schemas.d.ts.map +1 -1
  342. package/dist/web/schemas.js +6 -2
  343. package/dist/web/schemas.js.map +1 -1
  344. package/dist/web/server.d.ts +10 -9
  345. package/dist/web/server.d.ts.map +1 -1
  346. package/dist/web/server.js +342 -3829
  347. package/dist/web/server.js.map +1 -1
  348. package/package.json +1 -1
@@ -0,0 +1,467 @@
1
+ // Codeman — Mobile detection, keyboard handling, and swipe navigation
2
+ // Loaded after constants.js, before app.js
3
+
4
+ // ============================================================================
5
+ // Mobile Detection
6
+ // ============================================================================
7
+
8
+ /**
9
+ * MobileDetection - Detects device type and touch capability.
10
+ * Updates body classes for CSS targeting.
11
+ */
12
+ const MobileDetection = {
13
+ /** Check if device supports touch input */
14
+ isTouchDevice() {
15
+ return 'ontouchstart' in window ||
16
+ navigator.maxTouchPoints > 0 ||
17
+ (window.matchMedia && window.matchMedia('(pointer: coarse)').matches);
18
+ },
19
+
20
+ /** Check if device is iOS (iPhone, iPad, iPod) */
21
+ isIOS() {
22
+ return /iPad|iPhone|iPod/.test(navigator.userAgent) ||
23
+ (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
24
+ },
25
+
26
+ /** Check if browser is Safari */
27
+ isSafari() {
28
+ return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
29
+ },
30
+
31
+ /** Check if screen is small (phone-sized, <430px) */
32
+ isSmallScreen() {
33
+ return window.innerWidth < 430;
34
+ },
35
+
36
+ /** Check if screen is medium (tablet-sized, 430-768px) */
37
+ isMediumScreen() {
38
+ return window.innerWidth >= 430 && window.innerWidth < 768;
39
+ },
40
+
41
+ /** Get device type based on screen width */
42
+ getDeviceType() {
43
+ const width = window.innerWidth;
44
+ if (width < 430) return 'mobile';
45
+ if (width < 768) return 'tablet';
46
+ return 'desktop';
47
+ },
48
+
49
+ /** Update body classes based on device detection */
50
+ updateBodyClass() {
51
+ const body = document.body;
52
+ const deviceType = this.getDeviceType();
53
+ const isTouch = this.isTouchDevice();
54
+
55
+ // Remove existing device classes
56
+ body.classList.remove('device-mobile', 'device-tablet', 'device-desktop', 'touch-device', 'ios-device', 'safari-browser');
57
+
58
+ // Add current device class
59
+ body.classList.add(`device-${deviceType}`);
60
+
61
+ // Add touch device class if applicable
62
+ if (isTouch) {
63
+ body.classList.add('touch-device');
64
+ }
65
+
66
+ // Add iOS-specific class for safe area handling
67
+ if (this.isIOS()) {
68
+ body.classList.add('ios-device');
69
+ }
70
+
71
+ // Add Safari class for browser-specific fixes
72
+ if (this.isSafari()) {
73
+ body.classList.add('safari-browser');
74
+ }
75
+ },
76
+
77
+ /** Initialize mobile detection and set up resize listener */
78
+ init() {
79
+ this.updateBodyClass();
80
+ // Debounced resize handler
81
+ let resizeTimeout;
82
+ this._resizeHandler = () => {
83
+ clearTimeout(resizeTimeout);
84
+ resizeTimeout = setTimeout(() => this.updateBodyClass(), 100);
85
+ };
86
+ window.addEventListener('resize', this._resizeHandler);
87
+
88
+ // iOS: prevent pinch-to-zoom (Safari ignores user-scalable=no since iOS 10)
89
+ if (this.isIOS()) {
90
+ this._gestureStartHandler = (e) => e.preventDefault();
91
+ this._gestureChangeHandler = (e) => e.preventDefault();
92
+ document.addEventListener('gesturestart', this._gestureStartHandler);
93
+ document.addEventListener('gesturechange', this._gestureChangeHandler);
94
+ }
95
+ },
96
+
97
+ /** Remove event listeners */
98
+ cleanup() {
99
+ if (this._resizeHandler) {
100
+ window.removeEventListener('resize', this._resizeHandler);
101
+ this._resizeHandler = null;
102
+ }
103
+ if (this._gestureStartHandler) {
104
+ document.removeEventListener('gesturestart', this._gestureStartHandler);
105
+ document.removeEventListener('gesturechange', this._gestureChangeHandler);
106
+ this._gestureStartHandler = null;
107
+ this._gestureChangeHandler = null;
108
+ }
109
+ }
110
+ };
111
+
112
+ // ============================================================================
113
+ // Mobile Keyboard Handler
114
+ // ============================================================================
115
+
116
+ /**
117
+ * KeyboardHandler - Simple handler to scroll inputs into view when keyboard appears.
118
+ * Uses focusin event and scrollIntoView - keeps it simple and reliable.
119
+ * Also handles terminal scrolling and toolbar repositioning via visualViewport API.
120
+ */
121
+ const KeyboardHandler = {
122
+ lastViewportHeight: 0,
123
+ keyboardVisible: false,
124
+ initialViewportHeight: 0,
125
+
126
+ /** Initialize keyboard handling */
127
+ init() {
128
+ // Only initialize on touch devices
129
+ if (!MobileDetection.isTouchDevice()) return;
130
+
131
+ this.initialViewportHeight = window.visualViewport?.height || window.innerHeight;
132
+ this.lastViewportHeight = this.initialViewportHeight;
133
+
134
+ // Simple focus handler - scroll input into view after keyboard appears
135
+ this._focusinHandler = (e) => {
136
+ const target = e.target;
137
+ if (!this.isInputElement(target)) return;
138
+
139
+ // Wait for keyboard animation, then scroll input into view
140
+ setTimeout(() => {
141
+ this.scrollInputIntoView(target);
142
+ }, 400);
143
+ };
144
+ document.addEventListener('focusin', this._focusinHandler);
145
+
146
+ // Use visualViewport to detect keyboard and reposition toolbar
147
+ if (window.visualViewport) {
148
+ this._viewportResizeHandler = () => {
149
+ this.handleViewportResize();
150
+ };
151
+ this._viewportScrollHandler = () => {
152
+ this.updateLayoutForKeyboard();
153
+ };
154
+ window.visualViewport.addEventListener('resize', this._viewportResizeHandler);
155
+ // Also handle scroll (iOS scrolls viewport when keyboard appears)
156
+ window.visualViewport.addEventListener('scroll', this._viewportScrollHandler);
157
+ }
158
+ },
159
+
160
+ /** Remove event listeners */
161
+ cleanup() {
162
+ if (this._focusinHandler) {
163
+ document.removeEventListener('focusin', this._focusinHandler);
164
+ this._focusinHandler = null;
165
+ }
166
+ if (this._viewportResizeHandler && window.visualViewport) {
167
+ window.visualViewport.removeEventListener('resize', this._viewportResizeHandler);
168
+ this._viewportResizeHandler = null;
169
+ }
170
+ if (this._viewportScrollHandler && window.visualViewport) {
171
+ window.visualViewport.removeEventListener('scroll', this._viewportScrollHandler);
172
+ this._viewportScrollHandler = null;
173
+ }
174
+ },
175
+
176
+ /** Handle viewport resize (keyboard show/hide) */
177
+ handleViewportResize() {
178
+ const currentHeight = window.visualViewport?.height || window.innerHeight;
179
+ const heightDiff = this.initialViewportHeight - currentHeight;
180
+
181
+ // Keyboard appeared (viewport shrunk by more than 150px)
182
+ if (heightDiff > 150 && !this.keyboardVisible) {
183
+ this.keyboardVisible = true;
184
+ document.body.classList.add('keyboard-visible');
185
+ this.onKeyboardShow();
186
+ }
187
+ // Keyboard hidden (viewport grew back close to initial)
188
+ // Use 100px threshold (not 50) to handle iOS address bar drift,
189
+ // iOS 26's persistent 24px discrepancy, and Safari bottom bar changes
190
+ else if (heightDiff < 100 && this.keyboardVisible) {
191
+ this.keyboardVisible = false;
192
+ document.body.classList.remove('keyboard-visible');
193
+ this.onKeyboardHide();
194
+ }
195
+
196
+ // Update baseline when keyboard is not visible — adapts to address bar
197
+ // state changes, orientation changes, and other viewport shifts
198
+ if (!this.keyboardVisible) {
199
+ this.initialViewportHeight = currentHeight;
200
+ }
201
+
202
+ this.updateLayoutForKeyboard();
203
+ this.lastViewportHeight = currentHeight;
204
+ },
205
+
206
+ /** Update layout when keyboard shows/hides */
207
+ updateLayoutForKeyboard() {
208
+ if (!window.visualViewport) return;
209
+
210
+ // Only adjust on mobile
211
+ if (!MobileDetection.isSmallScreen() && !MobileDetection.isMediumScreen()) {
212
+ this.resetLayout();
213
+ return;
214
+ }
215
+
216
+ const toolbar = document.querySelector('.toolbar');
217
+ const accessoryBar = document.querySelector('.keyboard-accessory-bar');
218
+ const main = document.querySelector('.main');
219
+
220
+ if (this.keyboardVisible) {
221
+ // Calculate keyboard offset
222
+ const layoutHeight = window.innerHeight;
223
+ const visualBottom = window.visualViewport.offsetTop + window.visualViewport.height;
224
+ const keyboardOffset = layoutHeight - visualBottom;
225
+
226
+ // Safety: if keyboard is supposedly visible but offset is 0 or negative,
227
+ // the keyboard is actually gone — force dismiss. This catches cases where
228
+ // visualViewport.resize fires late or with intermediate values on iOS.
229
+ if (keyboardOffset <= 0) {
230
+ this.keyboardVisible = false;
231
+ document.body.classList.remove('keyboard-visible');
232
+ this.onKeyboardHide();
233
+ return;
234
+ }
235
+
236
+ // Move toolbar up above keyboard
237
+ if (toolbar) {
238
+ toolbar.style.transform = `translateY(${-keyboardOffset}px)`;
239
+ }
240
+
241
+ // Move accessory bar up (it sits above toolbar)
242
+ if (accessoryBar) {
243
+ accessoryBar.style.transform = `translateY(${-keyboardOffset}px)`;
244
+ }
245
+
246
+ // Shrink main content area so terminal doesn't extend behind keyboard
247
+ // Account for keyboard height + toolbar height (40px) + accessory bar (44px)
248
+ if (main) {
249
+ main.style.paddingBottom = `${keyboardOffset + 94}px`;
250
+ }
251
+ } else {
252
+ this.resetLayout();
253
+ }
254
+ },
255
+
256
+ /** Reset layout to normal (no keyboard) */
257
+ resetLayout() {
258
+ const toolbar = document.querySelector('.toolbar');
259
+ const accessoryBar = document.querySelector('.keyboard-accessory-bar');
260
+ const main = document.querySelector('.main');
261
+
262
+ if (toolbar) {
263
+ toolbar.style.transform = '';
264
+ }
265
+ if (accessoryBar) {
266
+ accessoryBar.style.transform = '';
267
+ }
268
+ if (main) {
269
+ main.style.paddingBottom = '';
270
+ }
271
+ },
272
+
273
+ /** Called when keyboard appears */
274
+ onKeyboardShow() {
275
+ // Show keyboard accessory bar
276
+ if (typeof KeyboardAccessoryBar !== 'undefined') {
277
+ KeyboardAccessoryBar.show();
278
+ }
279
+
280
+ // Refit terminal locally AND send resize to server so Claude Code (Ink)
281
+ // knows the actual terminal dimensions. Without this, Ink redraws at the
282
+ // old (larger) row count when the user types, causing content to scroll
283
+ // off the visible area with each keystroke.
284
+ // Note: the throttledResize handler still suppresses ongoing resize events
285
+ // while keyboard is up — this one-shot resize on open/close is sufficient.
286
+ setTimeout(() => {
287
+ if (typeof app !== 'undefined' && app.terminal) {
288
+ if (app.fitAddon) try { app.fitAddon.fit(); } catch {}
289
+ app.terminal.scrollToBottom();
290
+ // Send resize to server so PTY dimensions match xterm
291
+ this._sendTerminalResize();
292
+ }
293
+ }, 150);
294
+
295
+ // Reposition subagent windows to stack from bottom (above keyboard)
296
+ if (typeof app !== 'undefined') app.relayoutMobileSubagentWindows();
297
+ },
298
+
299
+ /** Called when keyboard hides */
300
+ onKeyboardHide() {
301
+ // Hide keyboard accessory bar
302
+ if (typeof KeyboardAccessoryBar !== 'undefined') {
303
+ KeyboardAccessoryBar.hide();
304
+ }
305
+
306
+ this.resetLayout();
307
+
308
+ // Refit terminal, scroll to bottom, and send resize to restore original dimensions
309
+ setTimeout(() => {
310
+ if (typeof app !== 'undefined' && app.fitAddon) {
311
+ try { app.fitAddon.fit(); } catch {}
312
+ if (app.terminal) app.terminal.scrollToBottom();
313
+ // Send resize to server to restore full terminal size
314
+ this._sendTerminalResize();
315
+ }
316
+ }, 100);
317
+
318
+ // Reposition subagent windows to stack from top (below header)
319
+ if (typeof app !== 'undefined') app.relayoutMobileSubagentWindows();
320
+ },
321
+
322
+ /** Send current terminal dimensions to the server (one-shot, for keyboard open/close) */
323
+ _sendTerminalResize() {
324
+ if (typeof app === 'undefined' || !app.activeSessionId || !app.fitAddon) return;
325
+ try {
326
+ const dims = app.fitAddon.proposeDimensions();
327
+ if (dims) {
328
+ const cols = Math.max(dims.cols, 40);
329
+ const rows = Math.max(dims.rows, 10);
330
+ app._lastResizeDims = { cols, rows };
331
+ fetch(`/api/sessions/${app.activeSessionId}/resize`, {
332
+ method: 'POST',
333
+ headers: { 'Content-Type': 'application/json' },
334
+ body: JSON.stringify({ cols, rows })
335
+ }).catch(() => {});
336
+ }
337
+ } catch {}
338
+ },
339
+
340
+ /** Check if element is an input that triggers keyboard (excludes terminal) */
341
+ isInputElement(el) {
342
+ if (!el) return false;
343
+
344
+ // Exclude xterm.js terminal inputs (they handle their own scroll)
345
+ if (el.closest('.xterm') || el.closest('.terminal-container')) {
346
+ return false;
347
+ }
348
+
349
+ const tagName = el.tagName?.toLowerCase();
350
+ // Exclude type=range, type=checkbox, type=radio (don't trigger keyboard)
351
+ if (tagName === 'input') {
352
+ const type = el.type?.toLowerCase();
353
+ if (type === 'checkbox' || type === 'radio' || type === 'range' || type === 'file') {
354
+ return false;
355
+ }
356
+ }
357
+ return (
358
+ tagName === 'input' ||
359
+ tagName === 'textarea' ||
360
+ el.isContentEditable
361
+ );
362
+ },
363
+
364
+ /** Scroll input into view above the keyboard */
365
+ scrollInputIntoView(input) {
366
+ // Check if input is still focused (user might have tapped away)
367
+ if (document.activeElement !== input) return;
368
+
369
+ // Find if we're in a modal
370
+ const modal = input.closest('.modal.active');
371
+ const modalBody = modal?.querySelector('.modal-body');
372
+
373
+ if (modalBody) {
374
+ // For modals - scroll within the modal body
375
+ const inputRect = input.getBoundingClientRect();
376
+ const modalRect = modalBody.getBoundingClientRect();
377
+
378
+ // If input is below middle of modal, scroll it up
379
+ if (inputRect.top > modalRect.top + modalRect.height * 0.4) {
380
+ const scrollAmount = inputRect.top - modalRect.top - 100;
381
+ modalBody.scrollBy({ top: scrollAmount, behavior: 'smooth' });
382
+ }
383
+ } else {
384
+ // For page-level - use scrollIntoView
385
+ input.scrollIntoView({ block: 'center', behavior: 'smooth' });
386
+ }
387
+ }
388
+ };
389
+
390
+ // ============================================================================
391
+ // Mobile Swipe Handler
392
+ // ============================================================================
393
+
394
+ /**
395
+ * SwipeHandler - Detects horizontal swipes on terminal to switch sessions.
396
+ * Only active on mobile/touch devices.
397
+ */
398
+ const SwipeHandler = {
399
+ startX: 0,
400
+ startY: 0,
401
+ startTime: 0,
402
+ minSwipeDistance: 80, // Minimum pixels for a valid swipe
403
+ maxSwipeTime: 300, // Maximum ms for a swipe gesture
404
+ maxVerticalDrift: 100, // Max vertical movement allowed
405
+
406
+ _touchStartHandler: null,
407
+ _touchEndHandler: null,
408
+ _element: null,
409
+
410
+ /** Initialize swipe handling */
411
+ init() {
412
+ // Only on touch devices
413
+ if (!MobileDetection.isTouchDevice()) return;
414
+
415
+ const terminal = document.querySelector('.main');
416
+ if (!terminal) return;
417
+
418
+ this._element = terminal;
419
+ this._touchStartHandler = (e) => this.onTouchStart(e);
420
+ this._touchEndHandler = (e) => this.onTouchEnd(e);
421
+ terminal.addEventListener('touchstart', this._touchStartHandler, { passive: true });
422
+ terminal.addEventListener('touchend', this._touchEndHandler, { passive: true });
423
+ },
424
+
425
+ /** Remove swipe listeners */
426
+ cleanup() {
427
+ if (this._element && this._touchStartHandler) {
428
+ this._element.removeEventListener('touchstart', this._touchStartHandler);
429
+ this._element.removeEventListener('touchend', this._touchEndHandler);
430
+ }
431
+ this._touchStartHandler = null;
432
+ this._touchEndHandler = null;
433
+ this._element = null;
434
+ },
435
+
436
+ onTouchStart(e) {
437
+ if (!e.touches || e.touches.length !== 1) return;
438
+ this.startX = e.touches[0].clientX;
439
+ this.startY = e.touches[0].clientY;
440
+ this.startTime = Date.now();
441
+ },
442
+
443
+ onTouchEnd(e) {
444
+ if (!e.changedTouches || e.changedTouches.length !== 1) return;
445
+
446
+ const endX = e.changedTouches[0].clientX;
447
+ const endY = e.changedTouches[0].clientY;
448
+ const elapsed = Date.now() - this.startTime;
449
+
450
+ // Check if it's a valid swipe
451
+ const deltaX = endX - this.startX;
452
+ const deltaY = Math.abs(endY - this.startY);
453
+
454
+ if (elapsed > this.maxSwipeTime) return; // Too slow
455
+ if (deltaY > this.maxVerticalDrift) return; // Too much vertical movement
456
+ if (Math.abs(deltaX) < this.minSwipeDistance) return; // Too short
457
+
458
+ // Valid swipe detected
459
+ if (deltaX > 0) {
460
+ // Swipe right -> previous session
461
+ if (typeof app !== 'undefined') app.prevSession();
462
+ } else {
463
+ // Swipe left -> next session
464
+ if (typeof app !== 'undefined') app.nextSession();
465
+ }
466
+ }
467
+ };
Binary file