aicodeman 0.2.9 → 0.3.1

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