aicodeman 1.41.0 → 1.41.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 (287) hide show
  1. package/README.md +58 -8
  2. package/dist/attachment-registry.d.ts +2 -0
  3. package/dist/attachment-registry.d.ts.map +1 -1
  4. package/dist/attachment-registry.js +14 -3
  5. package/dist/attachment-registry.js.map +1 -1
  6. package/dist/cli-agent.d.ts +2 -0
  7. package/dist/cli-agent.d.ts.map +1 -1
  8. package/dist/cli-agent.js +12 -8
  9. package/dist/cli-agent.js.map +1 -1
  10. package/dist/cli.d.ts +25 -1
  11. package/dist/cli.d.ts.map +1 -1
  12. package/dist/cli.js +97 -11
  13. package/dist/cli.js.map +1 -1
  14. package/dist/config/buffer-limits.d.ts +9 -0
  15. package/dist/config/buffer-limits.d.ts.map +1 -1
  16. package/dist/config/buffer-limits.js +9 -0
  17. package/dist/config/buffer-limits.js.map +1 -1
  18. package/dist/config/cli-registry/schema.d.ts +8 -0
  19. package/dist/config/cli-registry/schema.d.ts.map +1 -1
  20. package/dist/config/cli-registry/schema.js +12 -0
  21. package/dist/config/cli-registry/schema.js.map +1 -1
  22. package/dist/config/cli-registry/stock.d.ts.map +1 -1
  23. package/dist/config/cli-registry/stock.js +42 -0
  24. package/dist/config/cli-registry/stock.js.map +1 -1
  25. package/dist/config/cli-registry/types.d.ts +35 -0
  26. package/dist/config/cli-registry/types.d.ts.map +1 -1
  27. package/dist/folder-listing.d.ts +94 -0
  28. package/dist/folder-listing.d.ts.map +1 -0
  29. package/dist/folder-listing.js +241 -0
  30. package/dist/folder-listing.js.map +1 -0
  31. package/dist/hooks-config.d.ts +36 -16
  32. package/dist/hooks-config.d.ts.map +1 -1
  33. package/dist/hooks-config.js +45 -20
  34. package/dist/hooks-config.js.map +1 -1
  35. package/dist/html-view-capabilities.d.ts +54 -0
  36. package/dist/html-view-capabilities.d.ts.map +1 -0
  37. package/dist/html-view-capabilities.js +103 -0
  38. package/dist/html-view-capabilities.js.map +1 -0
  39. package/dist/mcp-server.d.ts +114 -0
  40. package/dist/mcp-server.d.ts.map +1 -0
  41. package/dist/mcp-server.js +917 -0
  42. package/dist/mcp-server.js.map +1 -0
  43. package/dist/mcp-sync.d.ts +5 -0
  44. package/dist/mcp-sync.d.ts.map +1 -1
  45. package/dist/mcp-sync.js +51 -5
  46. package/dist/mcp-sync.js.map +1 -1
  47. package/dist/mux-interface.d.ts +36 -1
  48. package/dist/mux-interface.d.ts.map +1 -1
  49. package/dist/remote-files.d.ts +37 -0
  50. package/dist/remote-files.d.ts.map +1 -1
  51. package/dist/remote-files.js +103 -0
  52. package/dist/remote-files.js.map +1 -1
  53. package/dist/remote-pane-history.d.ts +103 -0
  54. package/dist/remote-pane-history.d.ts.map +1 -0
  55. package/dist/remote-pane-history.js +258 -0
  56. package/dist/remote-pane-history.js.map +1 -0
  57. package/dist/remote-ssh-limiter.d.ts +16 -3
  58. package/dist/remote-ssh-limiter.d.ts.map +1 -1
  59. package/dist/remote-ssh-limiter.js +37 -6
  60. package/dist/remote-ssh-limiter.js.map +1 -1
  61. package/dist/session.d.ts +29 -1
  62. package/dist/session.d.ts.map +1 -1
  63. package/dist/session.js +53 -0
  64. package/dist/session.js.map +1 -1
  65. package/dist/tab-layout-legacy-order.d.ts.map +1 -1
  66. package/dist/tab-layout-legacy-order.js +72 -13
  67. package/dist/tab-layout-legacy-order.js.map +1 -1
  68. package/dist/tab-layout-service.d.ts +33 -2
  69. package/dist/tab-layout-service.d.ts.map +1 -1
  70. package/dist/tab-layout-service.js +102 -7
  71. package/dist/tab-layout-service.js.map +1 -1
  72. package/dist/tab-layout.d.ts +9 -0
  73. package/dist/tab-layout.d.ts.map +1 -1
  74. package/dist/tab-layout.js +15 -2
  75. package/dist/tab-layout.js.map +1 -1
  76. package/dist/tmux-manager.d.ts +71 -2
  77. package/dist/tmux-manager.d.ts.map +1 -1
  78. package/dist/tmux-manager.js +169 -18
  79. package/dist/tmux-manager.js.map +1 -1
  80. package/dist/types/session.d.ts +53 -0
  81. package/dist/types/session.d.ts.map +1 -1
  82. package/dist/types/session.js.map +1 -1
  83. package/dist/usage-telemetry.d.ts +44 -7
  84. package/dist/usage-telemetry.d.ts.map +1 -1
  85. package/dist/usage-telemetry.js +72 -7
  86. package/dist/usage-telemetry.js.map +1 -1
  87. package/dist/web/approval-inbox.d.ts +12 -0
  88. package/dist/web/approval-inbox.d.ts.map +1 -1
  89. package/dist/web/approval-inbox.js +1 -0
  90. package/dist/web/approval-inbox.js.map +1 -1
  91. package/dist/web/middleware/auth.d.ts.map +1 -1
  92. package/dist/web/middleware/auth.js +33 -0
  93. package/dist/web/middleware/auth.js.map +1 -1
  94. package/dist/web/ports/session-port.d.ts +8 -1
  95. package/dist/web/ports/session-port.d.ts.map +1 -1
  96. package/dist/web/public/admin-ui.js.gz +0 -0
  97. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  98. package/dist/web/public/app.24689381.js +43 -0
  99. package/dist/web/public/app.24689381.js.br +0 -0
  100. package/dist/web/public/app.24689381.js.gz +0 -0
  101. package/dist/web/public/approvals-ui.js +4 -0
  102. package/dist/web/public/approvals-ui.js.br +0 -0
  103. package/dist/web/public/approvals-ui.js.gz +0 -0
  104. package/dist/web/public/{constants.bc9c1f63.js → constants.17d77b65.js} +395 -7
  105. package/dist/web/public/constants.17d77b65.js.br +0 -0
  106. package/dist/web/public/constants.17d77b65.js.gz +0 -0
  107. package/dist/web/public/cron-ui.js.gz +0 -0
  108. package/dist/web/public/entrance-animations.js.gz +0 -0
  109. package/dist/web/public/gesture/gesture-codeman.js +112 -6
  110. package/dist/web/public/git-status-ui.js.gz +0 -0
  111. package/dist/web/public/home-sessions.js.gz +0 -0
  112. package/dist/web/public/host-wake-ui.js.gz +0 -0
  113. package/dist/web/public/i18n.9afd8e4d.js +2 -0
  114. package/dist/web/public/i18n.9afd8e4d.js.br +0 -0
  115. package/dist/web/public/i18n.9afd8e4d.js.gz +0 -0
  116. package/dist/web/public/{image-input.e00471c6.js → image-input.9ed1d0d2.js} +186 -16
  117. package/dist/web/public/image-input.9ed1d0d2.js.br +0 -0
  118. package/dist/web/public/image-input.9ed1d0d2.js.gz +0 -0
  119. package/dist/web/public/index.html +105 -23
  120. package/dist/web/public/index.html.br +0 -0
  121. package/dist/web/public/index.html.gz +0 -0
  122. package/dist/web/public/input-cjk.8bc46081.js.gz +0 -0
  123. package/dist/web/public/{keyboard-accessory.c1e7c7b7.js → keyboard-accessory.c458eea8.js} +115 -35
  124. package/dist/web/public/keyboard-accessory.c458eea8.js.br +0 -0
  125. package/dist/web/public/keyboard-accessory.c458eea8.js.gz +0 -0
  126. package/dist/web/public/mobile-handlers.0ef9c311.js.gz +0 -0
  127. package/dist/web/public/mobile-ime-preview.2dbaf262.js.gz +0 -0
  128. package/dist/web/public/mobile-overview.js +11 -2
  129. package/dist/web/public/mobile-overview.js.br +0 -0
  130. package/dist/web/public/mobile-overview.js.gz +0 -0
  131. package/dist/web/public/mobile.2dff1580.css +1 -0
  132. package/dist/web/public/mobile.2dff1580.css.br +0 -0
  133. package/dist/web/public/mobile.2dff1580.css.gz +0 -0
  134. package/dist/web/public/notification-manager.feb82c04.js.gz +0 -0
  135. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  136. package/dist/web/public/{panels-ui.71846fcb.js → panels-ui.73ec0137.js} +87 -87
  137. package/dist/web/public/panels-ui.73ec0137.js.br +0 -0
  138. package/dist/web/public/panels-ui.73ec0137.js.gz +0 -0
  139. package/dist/web/public/ralph-panel.b350e2ef.js.gz +0 -0
  140. package/dist/web/public/ralph-wizard.b6c0dd19.js.gz +0 -0
  141. package/dist/web/public/readmymind-ui.js.gz +0 -0
  142. package/dist/web/public/reboot-restore-ui.js.gz +0 -0
  143. package/dist/web/public/respawn-ui.ff0dae4c.js.gz +0 -0
  144. package/dist/web/public/sanitize-html.e7147e76.js.gz +0 -0
  145. package/dist/web/public/session-lineage.js.gz +0 -0
  146. package/dist/web/public/{session-ui.f731602c.js → session-ui.dbc6819b.js} +33 -33
  147. package/dist/web/public/session-ui.dbc6819b.js.br +0 -0
  148. package/dist/web/public/session-ui.dbc6819b.js.gz +0 -0
  149. package/dist/web/public/{settings-ui.4e33390f.js → settings-ui.81ac264e.js} +9 -9
  150. package/dist/web/public/settings-ui.81ac264e.js.br +0 -0
  151. package/dist/web/public/settings-ui.81ac264e.js.gz +0 -0
  152. package/dist/web/public/spreadsheet-preview-worker.js +11 -0
  153. package/dist/web/public/spreadsheet-preview-worker.js.br +0 -0
  154. package/dist/web/public/spreadsheet-preview-worker.js.gz +0 -0
  155. package/dist/web/public/{spreadsheet-preview.af82fa2f.js → spreadsheet-preview.75c751de.js} +1 -1
  156. package/dist/web/public/spreadsheet-preview.75c751de.js.br +0 -0
  157. package/dist/web/public/spreadsheet-preview.75c751de.js.gz +0 -0
  158. package/dist/web/public/spreadsheet-xlsx-core.js +110 -0
  159. package/dist/web/public/spreadsheet-xlsx-core.js.br +0 -0
  160. package/dist/web/public/spreadsheet-xlsx-core.js.gz +0 -0
  161. package/dist/web/public/styles.900a28e4.css +1 -0
  162. package/dist/web/public/styles.900a28e4.css.br +0 -0
  163. package/dist/web/public/styles.900a28e4.css.gz +0 -0
  164. package/dist/web/public/subagent-windows.07277a58.js.gz +0 -0
  165. package/dist/web/public/sw.js +6 -2
  166. package/dist/web/public/sw.js.br +0 -0
  167. package/dist/web/public/sw.js.gz +0 -0
  168. package/dist/web/public/tab-layout-browser.2d6189f7.js +1 -0
  169. package/dist/web/public/tab-layout-browser.2d6189f7.js.br +0 -0
  170. package/dist/web/public/tab-layout-browser.2d6189f7.js.gz +0 -0
  171. package/dist/web/public/{tab-rail-resize.a5867793.js → tab-rail-resize.7157e0bd.js} +1 -1
  172. package/dist/web/public/tab-rail-resize.7157e0bd.js.br +0 -0
  173. package/dist/web/public/tab-rail-resize.7157e0bd.js.gz +0 -0
  174. package/dist/web/public/terminal-keycode229-recovery.af09f99a.js.gz +0 -0
  175. package/dist/web/public/terminal-split.js.gz +0 -0
  176. package/dist/web/public/terminal-tile.js +44 -24
  177. package/dist/web/public/terminal-tile.js.br +0 -0
  178. package/dist/web/public/terminal-tile.js.gz +0 -0
  179. package/dist/web/public/terminal-ui.858f5da5.js +2 -0
  180. package/dist/web/public/terminal-ui.858f5da5.js.br +0 -0
  181. package/dist/web/public/terminal-ui.858f5da5.js.gz +0 -0
  182. package/dist/web/public/tile-grid.js +439 -18
  183. package/dist/web/public/tile-grid.js.br +0 -0
  184. package/dist/web/public/tile-grid.js.gz +0 -0
  185. package/dist/web/public/ultracode-panel.js.gz +0 -0
  186. package/dist/web/public/ultracode-windows.js.gz +0 -0
  187. package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
  188. package/dist/web/public/vendor/exceljs.min.js.gz +0 -0
  189. package/dist/web/public/vendor/fflate.min.js.gz +0 -0
  190. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  191. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  192. package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
  193. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  194. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  195. package/dist/web/public/vendor/xterm-predictive-echo.ae5e3d5d.js.gz +0 -0
  196. package/dist/web/public/vendor/xterm-zerolag-input.0b69623c.js.gz +0 -0
  197. package/dist/web/public/vendor/xterm.css.gz +0 -0
  198. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  199. package/dist/web/public/voice-input.2342c37b.js.gz +0 -0
  200. package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
  201. package/dist/web/public/webview-tabs.js +12 -0
  202. package/dist/web/public/webview-tabs.js.br +0 -0
  203. package/dist/web/public/webview-tabs.js.gz +0 -0
  204. package/dist/web/route-helpers.d.ts +8 -0
  205. package/dist/web/route-helpers.d.ts.map +1 -1
  206. package/dist/web/route-helpers.js +8 -0
  207. package/dist/web/route-helpers.js.map +1 -1
  208. package/dist/web/routes/admin-routes.d.ts.map +1 -1
  209. package/dist/web/routes/admin-routes.js +4 -1
  210. package/dist/web/routes/admin-routes.js.map +1 -1
  211. package/dist/web/routes/approval-routes.d.ts.map +1 -1
  212. package/dist/web/routes/approval-routes.js +6 -8
  213. package/dist/web/routes/approval-routes.js.map +1 -1
  214. package/dist/web/routes/case-routes.js +1 -1
  215. package/dist/web/routes/case-routes.js.map +1 -1
  216. package/dist/web/routes/file-routes.d.ts +46 -1
  217. package/dist/web/routes/file-routes.d.ts.map +1 -1
  218. package/dist/web/routes/file-routes.js +214 -27
  219. package/dist/web/routes/file-routes.js.map +1 -1
  220. package/dist/web/routes/hook-event-routes.d.ts.map +1 -1
  221. package/dist/web/routes/hook-event-routes.js +8 -1
  222. package/dist/web/routes/hook-event-routes.js.map +1 -1
  223. package/dist/web/routes/html-view-routes.d.ts +60 -0
  224. package/dist/web/routes/html-view-routes.d.ts.map +1 -0
  225. package/dist/web/routes/html-view-routes.js +288 -0
  226. package/dist/web/routes/html-view-routes.js.map +1 -0
  227. package/dist/web/routes/index.d.ts +1 -0
  228. package/dist/web/routes/index.d.ts.map +1 -1
  229. package/dist/web/routes/index.js +1 -0
  230. package/dist/web/routes/index.js.map +1 -1
  231. package/dist/web/routes/reboot-restore-routes.js +2 -2
  232. package/dist/web/routes/reboot-restore-routes.js.map +1 -1
  233. package/dist/web/routes/session-routes.d.ts +1 -0
  234. package/dist/web/routes/session-routes.d.ts.map +1 -1
  235. package/dist/web/routes/session-routes.js +278 -56
  236. package/dist/web/routes/session-routes.js.map +1 -1
  237. package/dist/web/routes/status-telemetry-routes.d.ts.map +1 -1
  238. package/dist/web/routes/status-telemetry-routes.js +16 -2
  239. package/dist/web/routes/status-telemetry-routes.js.map +1 -1
  240. package/dist/web/schemas.d.ts +29 -0
  241. package/dist/web/schemas.d.ts.map +1 -1
  242. package/dist/web/schemas.js +65 -0
  243. package/dist/web/schemas.js.map +1 -1
  244. package/dist/web/server.d.ts +6 -1
  245. package/dist/web/server.d.ts.map +1 -1
  246. package/dist/web/server.js +62 -14
  247. package/dist/web/server.js.map +1 -1
  248. package/dist/web/sse-events.d.ts +5 -2
  249. package/dist/web/sse-events.d.ts.map +1 -1
  250. package/dist/web/sse-events.js +5 -2
  251. package/dist/web/sse-events.js.map +1 -1
  252. package/package.json +2 -1
  253. package/skills/codeman/SKILL.md +29 -12
  254. package/dist/web/public/app.6f8552e1.js +0 -43
  255. package/dist/web/public/app.6f8552e1.js.br +0 -0
  256. package/dist/web/public/app.6f8552e1.js.gz +0 -0
  257. package/dist/web/public/constants.bc9c1f63.js.br +0 -0
  258. package/dist/web/public/constants.bc9c1f63.js.gz +0 -0
  259. package/dist/web/public/i18n.a9840ea2.js +0 -2
  260. package/dist/web/public/i18n.a9840ea2.js.br +0 -0
  261. package/dist/web/public/i18n.a9840ea2.js.gz +0 -0
  262. package/dist/web/public/image-input.e00471c6.js.br +0 -0
  263. package/dist/web/public/image-input.e00471c6.js.gz +0 -0
  264. package/dist/web/public/keyboard-accessory.c1e7c7b7.js.br +0 -0
  265. package/dist/web/public/keyboard-accessory.c1e7c7b7.js.gz +0 -0
  266. package/dist/web/public/mobile.111b187d.css +0 -1
  267. package/dist/web/public/mobile.111b187d.css.br +0 -0
  268. package/dist/web/public/mobile.111b187d.css.gz +0 -0
  269. package/dist/web/public/panels-ui.71846fcb.js.br +0 -0
  270. package/dist/web/public/panels-ui.71846fcb.js.gz +0 -0
  271. package/dist/web/public/session-ui.f731602c.js.br +0 -0
  272. package/dist/web/public/session-ui.f731602c.js.gz +0 -0
  273. package/dist/web/public/settings-ui.4e33390f.js.br +0 -0
  274. package/dist/web/public/settings-ui.4e33390f.js.gz +0 -0
  275. package/dist/web/public/spreadsheet-preview.af82fa2f.js.br +0 -0
  276. package/dist/web/public/spreadsheet-preview.af82fa2f.js.gz +0 -0
  277. package/dist/web/public/styles.81a72906.css +0 -1
  278. package/dist/web/public/styles.81a72906.css.br +0 -0
  279. package/dist/web/public/styles.81a72906.css.gz +0 -0
  280. package/dist/web/public/tab-layout-browser.065fcba4.js +0 -1
  281. package/dist/web/public/tab-layout-browser.065fcba4.js.br +0 -0
  282. package/dist/web/public/tab-layout-browser.065fcba4.js.gz +0 -0
  283. package/dist/web/public/tab-rail-resize.a5867793.js.br +0 -0
  284. package/dist/web/public/tab-rail-resize.a5867793.js.gz +0 -0
  285. package/dist/web/public/terminal-ui.b7267005.js +0 -2
  286. package/dist/web/public/terminal-ui.b7267005.js.br +0 -0
  287. package/dist/web/public/terminal-ui.b7267005.js.gz +0 -0
@@ -56,6 +56,26 @@ const TILE_GRID_REFIT_MS = 150;
56
56
  // the grid section's padding (styles.css .tile-grid), for the drag math.
57
57
  const TILE_DIVIDER_PX = 6;
58
58
  const TILE_GRID_PADDING_PX = 4;
59
+ // What a tile's header drag (and a popped-out window's title drag) carries:
60
+ // the session id under a type of its own, never text, so no text field or
61
+ // terminal takes it as typing. Another Codeman window reads it on drop.
62
+ const TILE_DRAG_TYPE = 'application/x-codeman-tile';
63
+ // Detach Tiles: a tile dropped outside every window waits this long before it
64
+ // opens as a window of its own, so another window that took it (a
65
+ // 'tile-adopted' message on the window channel) can still say so first.
66
+ const TILE_TEAR_OFF_SETTLE_MS = 120;
67
+ // How long after its drag ended a tile can still be claimed by another
68
+ // window's drop (the claim and the dragend come from two windows).
69
+ const TILE_ADOPT_WINDOW_MS = 5000;
70
+ // A session docked here from another window: its old window's 'detached'
71
+ // announcements this soon after are stale (it is closing), not a new pop-out.
72
+ const TILE_DOCK_GRACE_MS = 2000;
73
+ // The smallest window a detached tile opens as, and roughly how much browser
74
+ // chrome sits above a pop-out's content (its title and address bars), so the
75
+ // window's own header lands near where the tile was let go.
76
+ const TILE_WINDOW_MIN_W = 480;
77
+ const TILE_WINDOW_MIN_H = 320;
78
+ const TILE_WINDOW_CHROME_PX = 60;
59
79
 
60
80
  /** `minmax(0, 1fr) 6px minmax(0, 2fr) ...`: tracks with a divider track between each. */
61
81
  function tileGridTracks(fr) {
@@ -324,12 +344,14 @@ Object.assign(CodemanApp.prototype, {
324
344
  const sessionId = tileAt(e.target);
325
345
  const files = Array.from(e.dataTransfer?.files || []);
326
346
  if (!sessionId || files.length === 0) return;
327
- const images = files.filter((f) => String(f?.type || '').startsWith('image/'));
328
- if (images.length === 0) {
329
- this.showToast?.('Only image files are supported', 'error');
347
+ // The one classifier every pick and drop filter reads (image-input.js):
348
+ // MIME first, then the name's extension, so documents and videos count too.
349
+ const media = files.filter((f) => this._promptAttachKind?.(f));
350
+ if (media.length === 0) {
351
+ this.showToast?.('Unsupported file type', 'error');
330
352
  return;
331
353
  }
332
- this._uploadAndInsertImages?.(images, { sessionId });
354
+ this._uploadAndInsertImages?.(media, { sessionId });
333
355
  });
334
356
  },
335
357
 
@@ -1552,8 +1574,10 @@ Object.assign(CodemanApp.prototype, {
1552
1574
  * `accepts(id)` is the target's own rule (a tile takes any session but its
1553
1575
  * own; an empty cell takes any). A session it does not accept
1554
1576
  * is held there too, but refused (`dropEffect: 'none'`, no highlight, so no
1555
- * drop follows). Any other drag (a file) is left to the grid section's own
1556
- * guard (_installTileFileDrop).
1577
+ * drop follows). With Detach Tiles on, a tile dragged from ANOTHER Codeman
1578
+ * window (no drag of this page's own, the tile type in the drag) is taken
1579
+ * too and docks here (_dockForeignTile). Any other drag (a file) is left to
1580
+ * the grid section's own guard (_installTileFileDrop).
1557
1581
  */
1558
1582
  _acceptTabDrops(el, onDrop, { accepts = () => true } = {}) {
1559
1583
  const dragged = () => (this._tileGrid?.open ? this.draggedTabId || this._draggedTileId || null : null);
@@ -1561,7 +1585,16 @@ Object.assign(CodemanApp.prototype, {
1561
1585
  'dragover',
1562
1586
  (e) => {
1563
1587
  const id = dragged();
1564
- if (!id) return;
1588
+ if (!id) {
1589
+ // A tile from another Codeman window (Detach Tiles). Its id can
1590
+ // only be read on drop, so it is held here whatever it is.
1591
+ if (!this._isForeignTileDrag(e)) return;
1592
+ e.preventDefault?.();
1593
+ e.stopPropagation?.();
1594
+ if (e.dataTransfer) e.dataTransfer.dropEffect = 'move';
1595
+ el.classList.add('tile--drop-target');
1596
+ return;
1597
+ }
1565
1598
  e.preventDefault?.();
1566
1599
  e.stopPropagation?.();
1567
1600
  const ok = accepts(id);
@@ -1578,7 +1611,13 @@ Object.assign(CodemanApp.prototype, {
1578
1611
  (e) => {
1579
1612
  el.classList.remove('tile--drop-target');
1580
1613
  const id = dragged();
1581
- if (!id) return;
1614
+ if (!id) {
1615
+ if (!this._isForeignTileDrag(e)) return;
1616
+ e.preventDefault?.();
1617
+ e.stopPropagation?.();
1618
+ this._dockForeignTile(e.dataTransfer?.getData?.(TILE_DRAG_TYPE), onDrop);
1619
+ return;
1620
+ }
1582
1621
  e.preventDefault?.();
1583
1622
  e.stopPropagation?.();
1584
1623
  if (accepts(id)) onDrop(id);
@@ -2080,7 +2119,10 @@ Object.assign(CodemanApp.prototype, {
2080
2119
  * input starts no drag. dragstart's target is the header whatever was
2081
2120
  * pressed, so where the press landed is noted in the capture phase, ahead of
2082
2121
  * the buttons' own stopPropagation. `draggable` is off while moving is
2083
- * (_paintTileHandle: a zoomed grid, a single tile, a rename in progress).
2122
+ * (_paintTileHandle: a zoomed grid, a single tile, a rename in progress),
2123
+ * unless Detach Tiles is on: then a drop outside the window pops the tile
2124
+ * out, and a drop on another Codeman window's tiles moves it there (see the
2125
+ * Detach Tiles section); a rename still turns it off.
2084
2126
  */
2085
2127
  _installTileMoveDrag(sessionId, header, actions) {
2086
2128
  let pressedOnControl = false;
@@ -2094,7 +2136,10 @@ Object.assign(CodemanApp.prototype, {
2094
2136
  );
2095
2137
  header.addEventListener('dragstart', (e) => {
2096
2138
  const entry = this._tileGrid?.open ? this._tileGrid.tiles.get(sessionId) : null;
2097
- if (!entry || pressedOnControl || entry.renaming || !this._tilesMovable()) {
2139
+ // Detach Tiles lets a tile that cannot move (alone, or zoomed) still be
2140
+ // dragged, out of the window; no drop target here takes it then.
2141
+ const detachable = this._tileDetachAllowed();
2142
+ if (!entry || pressedOnControl || entry.renaming || !(this._tilesMovable() || detachable)) {
2098
2143
  e.preventDefault?.();
2099
2144
  return;
2100
2145
  }
@@ -2102,11 +2147,16 @@ Object.assign(CodemanApp.prototype, {
2102
2147
  entry.el.classList.add('tile--dragging');
2103
2148
  if (e.dataTransfer) {
2104
2149
  e.dataTransfer.effectAllowed = 'move';
2105
- // Firefox starts no drag without data.
2106
- e.dataTransfer.setData('application/x-codeman-tile', sessionId);
2150
+ // Firefox starts no drag without data. Another Codeman window reads
2151
+ // the id from it (Detach Tiles).
2152
+ e.dataTransfer.setData(TILE_DRAG_TYPE, sessionId);
2107
2153
  }
2154
+ if (detachable) this._beginTileTearOff(sessionId, entry, e);
2155
+ });
2156
+ header.addEventListener('dragend', (e) => {
2157
+ this._settleTileTearOff(sessionId, e);
2158
+ this._endTileMoveDrag();
2108
2159
  });
2109
- header.addEventListener('dragend', () => this._endTileMoveDrag());
2110
2160
  },
2111
2161
 
2112
2162
  /**
@@ -2117,6 +2167,7 @@ Object.assign(CodemanApp.prototype, {
2117
2167
  */
2118
2168
  _endTileMoveDrag() {
2119
2169
  this._draggedTileId = null;
2170
+ this._stopTileTearOffTracking();
2120
2171
  const grid = this._tileGrid;
2121
2172
  for (const { el } of grid?.tiles.values() || []) el.classList.remove('tile--dragging', 'tile--drop-target');
2122
2173
  for (const slot of grid?.slots || []) slot.classList.remove('tile--drop-target');
@@ -2124,19 +2175,25 @@ Object.assign(CodemanApp.prototype, {
2124
2175
 
2125
2176
  /**
2126
2177
  * The header as a handle: `draggable` while the tile can move (not while a
2127
- * tile is zoomed, alone, or being renamed), and its tooltip, the state and
2128
- * how long ("working 3m") plus, while it can move, that it drags. Diffs on
2129
- * the last English text, never the DOM (translated in zh-CN; see
2178
+ * tile is zoomed, alone, or being renamed) or, with Detach Tiles on, be
2179
+ * dragged out of the window (then also alone or zoomed), and its tooltip,
2180
+ * the state and how long ("working 3m") plus what a drag does. Diffs on the
2181
+ * last English text, never the DOM (translated in zh-CN; see
2130
2182
  * _renderTileOverlay).
2131
2183
  */
2132
2184
  _paintTileHandle(entry) {
2133
2185
  const movable = this._tilesMovable();
2134
- const title = [entry.stateLabel, movable ? 'Drag to move the tile' : ''].filter(Boolean).join('\n');
2186
+ const detachable = this._tileDetachAllowed();
2187
+ let hint = '';
2188
+ if (movable && detachable) hint = 'Drag to move the tile, or out of the window to open it on its own';
2189
+ else if (movable) hint = 'Drag to move the tile';
2190
+ else if (detachable) hint = 'Drag out of the window to open the tile on its own';
2191
+ const title = [entry.stateLabel, hint].filter(Boolean).join('\n');
2135
2192
  if (entry.headerLabel !== title) {
2136
2193
  entry.headerLabel = title;
2137
2194
  entry.header.title = title;
2138
2195
  }
2139
- const draggable = movable && !entry.renaming ? 'true' : 'false';
2196
+ const draggable = (movable || detachable) && !entry.renaming ? 'true' : 'false';
2140
2197
  if (entry.header.getAttribute('draggable') !== draggable) entry.header.setAttribute('draggable', draggable);
2141
2198
  },
2142
2199
 
@@ -2859,6 +2916,370 @@ Object.assign(CodemanApp.prototype, {
2859
2916
  for (const { tile } of grid.tiles.values()) tile.reconnectNow();
2860
2917
  return true;
2861
2918
  },
2919
+
2920
+ // ── Detach Tiles (per-device `tileDetachEnabled`, default OFF) ───────────
2921
+ //
2922
+ // A tile leaves the grid for a window of its own, and comes back into any
2923
+ // Codeman window's grid, by dragging:
2924
+ //
2925
+ // - its header dropped OUTSIDE every window opens it as a pop-out window
2926
+ // (detachSession, the tab's own "Open in a new window"), at the drop point
2927
+ // and the tile's size;
2928
+ // - its header dropped on ANOTHER Codeman window's tile or empty cell moves
2929
+ // it there: that window docks it and says so on the window channel
2930
+ // ('tile-adopted'), and this one lets the tile go;
2931
+ // - a pop-out window's title dropped on a window's tile or empty cell docks
2932
+ // the session there, and the pop-out closes.
2933
+ //
2934
+ // Nothing new on the server: a pop-out is another client of the same session
2935
+ // (see detachSession), and a tile is a TerminalTile in whichever window has it.
2936
+ // A session stays in ONE place in this browser, as with every pop-out.
2937
+
2938
+ /** Whether Detach Tiles is on for this device. */
2939
+ tileDetachEnabled() {
2940
+ return this.loadAppSettingsFromStorage?.()?.tileDetachEnabled === true;
2941
+ },
2942
+
2943
+ /** Detach Tiles applies here: on, and a dashboard (a pop-out has no grid to drag from). */
2944
+ _tileDetachAllowed() {
2945
+ return !this.isSoloWindow && this.tileDetachEnabled();
2946
+ },
2947
+
2948
+ /**
2949
+ * A drag over this page that is a tile from another Codeman window: none of
2950
+ * this page's own drags is in flight, Detach Tiles is on and the drag
2951
+ * carries the tile type (its id is only readable on drop).
2952
+ */
2953
+ _isForeignTileDrag(e) {
2954
+ if (this.draggedTabId || this._draggedTileId || !this._tileGrid?.open || !this._tileDetachAllowed()) return false;
2955
+ const types = e?.dataTransfer?.types;
2956
+ return !!types && Array.from(types).includes(TILE_DRAG_TYPE);
2957
+ },
2958
+
2959
+ /**
2960
+ * A tile from another window dropped on one of this grid's targets: it docks
2961
+ * through the target's own drop (`place`: a tile it replaces or trades
2962
+ * places with, an empty cell it fills), and takes focus there (a human
2963
+ * selection: the user just put it there). A session that was popped out comes
2964
+ * back first (its window is asked to close). Once it is a tile here, every
2965
+ * other window hears 'tile-adopted', and the one it was dragged from lets
2966
+ * its tile go (_onTileAdoptedElsewhere). An id this page does not know (a
2967
+ * session closed meanwhile, another user's) docks nowhere.
2968
+ *
2969
+ * @returns {boolean} whether the session is a tile here afterwards
2970
+ */
2971
+ _dockForeignTile(sessionId, place) {
2972
+ const grid = this._tileGrid;
2973
+ if (!grid?.open || typeof sessionId !== 'string' || !this.sessions.has(sessionId)) return false;
2974
+ const poppedOut = !!this.detachedSessions?.has(sessionId);
2975
+ // A pop-out window owns its session's place: take it back before mounting
2976
+ // (a detached session never mounts).
2977
+ if (poppedOut) this._redock?.(sessionId);
2978
+ // Already a tile here too (two windows showed it): it moves to where it
2979
+ // was dropped, as a drag inside this window would move it.
2980
+ place(sessionId);
2981
+ if (!grid.has(sessionId)) return false;
2982
+ (this._tileDockedAt ||= new Map()).set(sessionId, Date.now());
2983
+ // Asked unconditionally: a pop-out this window never heard of (opened from
2984
+ // another dashboard) closes too. Dashboards ignore it; only that id's
2985
+ // pop-out reacts.
2986
+ this._postWindowMessage?.({ type: 'close-request', id: sessionId });
2987
+ this._postWindowMessage?.({ type: 'tile-adopted', id: sessionId, by: this._wsTabNonce });
2988
+ return true;
2989
+ },
2990
+
2991
+ /**
2992
+ * True for a session this window docked from another one a moment ago: a
2993
+ * 'detached' announcement from its old pop-out is that window on its way
2994
+ * out, never a new pop-out (it would take the tile straight back off).
2995
+ */
2996
+ _tileDockedRecently(sessionId) {
2997
+ const at = this._tileDockedAt?.get(sessionId);
2998
+ return typeof at === 'number' && Date.now() - at < TILE_DOCK_GRACE_MS;
2999
+ },
3000
+
3001
+ /**
3002
+ * Another window docked `sessionId` ('tile-adopted'). Only the window it
3003
+ * was dragged FROM acts: its drag is still settling or ended a moment ago.
3004
+ * The tile goes without taking the keyboard anywhere (the user is in the
3005
+ * other window now). The last tile leaving closes the grid onto the welcome
3006
+ * screen, never onto that session: two windows sizing one PTY garble it.
3007
+ */
3008
+ _onTileAdoptedElsewhere(sessionId) {
3009
+ const recent = this._lastTileDrag;
3010
+ // A header drag counts even without a tear-off (Detach Tiles off here): the
3011
+ // other window can take the tile before this drag ends.
3012
+ const dragging = this._tileTearOff?.id === sessionId || this._draggedTileId === sessionId;
3013
+ const pending = this._tileTearOffPending?.id === sessionId;
3014
+ const justEnded = recent?.id === sessionId && Date.now() - recent.at < TILE_ADOPT_WINDOW_MS;
3015
+ if (!dragging && !pending && !justEnded) return false;
3016
+ this._cancelTileTearOff();
3017
+ this._lastTileDrag = null;
3018
+ const grid = this._tileGrid;
3019
+ if (!grid?.has(sessionId)) return false;
3020
+ // One stored grid per browser, and the window that docked the tile has
3021
+ // just written it: this window losing the tile is no change of the
3022
+ // user's to remember over that one (nor to forget it).
3023
+ this._tilePersistHold = true;
3024
+ try {
3025
+ if (grid.ids.length > 1) return this.removeTile(sessionId, { focus: false });
3026
+ this.closeTileGrid({ keepStored: true, reselect: false });
3027
+ } finally {
3028
+ this._tilePersistHold = false;
3029
+ }
3030
+ this.activeSessionId = null;
3031
+ try {
3032
+ localStorage.removeItem('codeman-active-session');
3033
+ } catch {
3034
+ /* Nothing stored. */
3035
+ }
3036
+ this.terminal?.clear();
3037
+ this.showWelcome?.();
3038
+ return true;
3039
+ },
3040
+
3041
+ /**
3042
+ * A header drag starts with Detach Tiles on: note the tile's size and where
3043
+ * in it the press landed (the pop-out opens that size, under the pointer),
3044
+ * and watch whether the pointer leaves the page. Two signals, since
3045
+ * browsers differ in what dragend reports for a drop outside the window:
3046
+ * its coordinates, and the page's own dragleave at the viewport's edge
3047
+ * with nothing entered (cleared again by any dragover inside).
3048
+ */
3049
+ _beginTileTearOff(sessionId, entry, e) {
3050
+ this._stopTileTearOffTracking();
3051
+ const rect = entry.el.getBoundingClientRect();
3052
+ const within = (v, max) => (Number.isFinite(v) ? Math.max(0, Math.min(v, max)) : 0);
3053
+ const drag = {
3054
+ id: sessionId,
3055
+ width: rect.width,
3056
+ height: rect.height,
3057
+ offsetX: within(e?.clientX - rect.left, rect.width),
3058
+ offsetY: within(e?.clientY - rect.top, rect.height),
3059
+ outside: false,
3060
+ stop: null,
3061
+ };
3062
+ const onOver = () => {
3063
+ drag.outside = false;
3064
+ };
3065
+ const onLeave = (ev) => {
3066
+ if (ev.relatedTarget) return;
3067
+ const x = ev.clientX;
3068
+ const y = ev.clientY;
3069
+ if (x <= 0 || y <= 0 || x >= window.innerWidth - 1 || y >= window.innerHeight - 1) drag.outside = true;
3070
+ };
3071
+ document.addEventListener('dragover', onOver, true);
3072
+ document.addEventListener('dragleave', onLeave, true);
3073
+ drag.stop = () => {
3074
+ document.removeEventListener('dragover', onOver, true);
3075
+ document.removeEventListener('dragleave', onLeave, true);
3076
+ };
3077
+ this._tileTearOff = drag;
3078
+ },
3079
+
3080
+ /** The header drag is over (or the grid or tile went mid-drag): stop watching the pointer. */
3081
+ _stopTileTearOffTracking() {
3082
+ const drag = this._tileTearOff;
3083
+ if (!drag) return;
3084
+ this._tileTearOff = null;
3085
+ drag.stop?.();
3086
+ },
3087
+
3088
+ /**
3089
+ * dragend of a tile's header with Detach Tiles on. Taken by a drop target
3090
+ * (here or in another window: `dropEffect` is not 'none'), it stays where
3091
+ * that put it. Let go OUTSIDE the window (the dragend point, or the page's
3092
+ * own record of the pointer leaving), it opens as a window of its own after
3093
+ * TILE_TEAR_OFF_SETTLE_MS, unless another window claims it first. Let go
3094
+ * inside, on nothing, nothing happens, as before.
3095
+ */
3096
+ _settleTileTearOff(sessionId, e) {
3097
+ const drag = this._tileTearOff?.id === sessionId ? this._tileTearOff : null;
3098
+ this._lastTileDrag = { id: sessionId, at: Date.now() };
3099
+ if (!drag) return false;
3100
+ this._stopTileTearOffTracking();
3101
+ const effect = e?.dataTransfer?.dropEffect;
3102
+ if (effect && effect !== 'none') return false;
3103
+ const cx = e?.clientX;
3104
+ const cy = e?.clientY;
3105
+ // (0, 0) is what a browser reports when it does not know where the drop was.
3106
+ const known = Number.isFinite(cx) && Number.isFinite(cy) && !(cx === 0 && cy === 0);
3107
+ const outside = known ? cx < 0 || cy < 0 || cx >= window.innerWidth || cy >= window.innerHeight : drag.outside;
3108
+ if (!outside) return false;
3109
+ const sx = e?.screenX;
3110
+ const sy = e?.screenY;
3111
+ const placed = Number.isFinite(sx) && Number.isFinite(sy) && !(sx === 0 && sy === 0);
3112
+ const placement = {
3113
+ width: drag.width,
3114
+ height: drag.height,
3115
+ ...(placed ? { screenX: sx, screenY: sy, offsetX: drag.offsetX, offsetY: drag.offsetY } : {}),
3116
+ };
3117
+ this._cancelTileTearOff();
3118
+ const pending = { id: sessionId, timer: null };
3119
+ pending.timer = setTimeout(() => {
3120
+ if (this._tileTearOffPending !== pending) return;
3121
+ this._tileTearOffPending = null;
3122
+ this.detachTile(sessionId, placement);
3123
+ }, TILE_TEAR_OFF_SETTLE_MS);
3124
+ this._tileTearOffPending = pending;
3125
+ return true;
3126
+ },
3127
+
3128
+ _cancelTileTearOff() {
3129
+ const pending = this._tileTearOffPending;
3130
+ if (!pending) return;
3131
+ this._tileTearOffPending = null;
3132
+ clearTimeout(pending.timer);
3133
+ },
3134
+
3135
+ /**
3136
+ * Opens a tile as a window of its own (detachSession, which takes it out of
3137
+ * the grid), the tile's size (at least TILE_WINDOW_MIN_W x MIN_H). Placed
3138
+ * with its header under a screen point when given one (`screenX`/`screenY`
3139
+ * and where in the tile it was held, `offsetX`/`offsetY`: a drop outside the
3140
+ * window, a hand let go), otherwise over the tile itself (the ⋯ menu).
3141
+ *
3142
+ * @returns {boolean} whether the session is popped out afterwards
3143
+ */
3144
+ detachTile(sessionId, { screenX, screenY, offsetX = 0, offsetY = 0, width, height } = {}) {
3145
+ const entry = this._tileGrid?.open ? this._tileGrid.tiles.get(sessionId) : null;
3146
+ if (!entry || !this._tileDetachAllowed()) return false;
3147
+ const rect = entry.el.getBoundingClientRect();
3148
+ const availW = window.screen?.availWidth || Infinity;
3149
+ const availH = window.screen?.availHeight || Infinity;
3150
+ const size = (v, min, max) => Math.round(Math.min(Math.max(Number.isFinite(v) ? v : min, min), max));
3151
+ const placement = {
3152
+ width: size(width ?? rect.width, TILE_WINDOW_MIN_W, availW),
3153
+ height: size(height ?? rect.height, TILE_WINDOW_MIN_H, availH),
3154
+ };
3155
+ if (Number.isFinite(screenX) && Number.isFinite(screenY)) {
3156
+ placement.left = Math.round(screenX - offsetX);
3157
+ placement.top = Math.round(screenY - offsetY - TILE_WINDOW_CHROME_PX);
3158
+ } else if (Number.isFinite(window.screenX) && Number.isFinite(window.screenY)) {
3159
+ // Over the tile: the page's own chrome sits above its viewport.
3160
+ const chrome = Math.max(0, (window.outerHeight || 0) - (window.innerHeight || 0));
3161
+ placement.left = Math.round(window.screenX + rect.left);
3162
+ placement.top = Math.round(window.screenY + chrome + rect.top - TILE_WINDOW_CHROME_PX);
3163
+ }
3164
+ this.detachSession?.(sessionId, placement);
3165
+ return !!this.detachedSessions?.has(sessionId);
3166
+ },
3167
+
3168
+ // ── For the gesture overlay (packages/gesture-control, src/codeman/entry.ts)
3169
+ //
3170
+ // A hand is no native drag: the overlay asks where it is and what is under
3171
+ // it, and every move still goes through the paths a mouse drag takes
3172
+ // (dropSessionOnTile / dropSessionOnSlot, and so _reorderTiles), the
3173
+ // pop-out through detachTile.
3174
+
3175
+ /** The tiled session whose tile is at viewport point (x, y), or null. */
3176
+ tileAtPoint(x, y) {
3177
+ const grid = this._tileGrid;
3178
+ if (!grid?.open) return null;
3179
+ const hit = document.elementFromPoint?.(x, y);
3180
+ if (!hit) return null;
3181
+ for (const [id, entry] of grid.tiles) if (entry.el.contains?.(hit)) return id;
3182
+ return null;
3183
+ },
3184
+
3185
+ /** Whether a hand may pick up this tile: it can move, or (Detach Tiles) leave the window. */
3186
+ canGrabTile(sessionId) {
3187
+ return !!this._tileGrid?.has(sessionId) && (this._tilesMovable() || this._tileDetachAllowed());
3188
+ },
3189
+
3190
+ /**
3191
+ * What a session dragged to viewport point (x, y) would drop onto, by the
3192
+ * mouse drag's own rules: another tile (it takes that tile's place, or the
3193
+ * two trade places), an empty cell (it moves or joins there). A tiled
3194
+ * session only while tiles can move (not zoomed). Null for anything else,
3195
+ * a popped-out session included.
3196
+ *
3197
+ * @returns {{kind: 'tile', id: string, el: HTMLElement}|{kind: 'cell', cell: number, el: HTMLElement}|null}
3198
+ */
3199
+ tileDropTargetAt(x, y, draggedId) {
3200
+ const grid = this._tileGrid;
3201
+ if (!grid?.open || !this.sessions.has(draggedId) || this.detachedSessions?.has(draggedId)) return null;
3202
+ if (grid.tiles.has(draggedId) && !this._tilesMovable()) return null;
3203
+ const hit = document.elementFromPoint?.(x, y);
3204
+ if (!hit) return null;
3205
+ for (const [id, entry] of grid.tiles) {
3206
+ if (entry.el.contains?.(hit)) return id === draggedId ? null : { kind: 'tile', id, el: entry.el };
3207
+ }
3208
+ for (const slot of grid.slots || []) {
3209
+ if (slot.contains?.(hit)) return { kind: 'cell', cell: Number(slot.dataset.cell), el: slot };
3210
+ }
3211
+ return null;
3212
+ },
3213
+
3214
+ /**
3215
+ * Drops a session on a target tileDropTargetAt returned (the hand let go).
3216
+ *
3217
+ * @returns {boolean} whether the session is a tile afterwards
3218
+ */
3219
+ dropOnTileTarget(draggedId, target) {
3220
+ if (!target || !this._tileGrid?.open) return false;
3221
+ if (target.kind === 'tile') this.dropSessionOnTile(draggedId, target.id);
3222
+ else if (target.kind === 'cell') this.dropSessionOnSlot(draggedId, target.cell);
3223
+ return !!this._tileGrid?.has(draggedId);
3224
+ },
3225
+
3226
+ /** Whether viewport point (x, y) is over the open grid (a hand let go outside it pops the tile out). */
3227
+ isOverTileGrid(x, y) {
3228
+ if (!this._tileGrid?.open) return false;
3229
+ const rect = document.getElementById('tileGrid')?.getBoundingClientRect?.();
3230
+ return !!rect && x >= rect.left && x < rect.right && y >= rect.top && y < rect.bottom;
3231
+ },
3232
+
3233
+ /**
3234
+ * A hand let a tile go outside the grid (Detach Tiles): it opens as a window
3235
+ * of its own with its header near that point. A script opening a window
3236
+ * with no click behind it is what popup blockers stop; detachSession then
3237
+ * says so and the tile stays.
3238
+ */
3239
+ detachTileAtPoint(sessionId, x, y, { offsetX = 0, offsetY = 0 } = {}) {
3240
+ const chrome = Math.max(0, (window.outerHeight || 0) - (window.innerHeight || 0));
3241
+ const sx = (window.screenX || 0) + x;
3242
+ const sy = (window.screenY || 0) + chrome + y;
3243
+ return this.detachTile(sessionId, { screenX: sx, screenY: sy, offsetX, offsetY });
3244
+ },
3245
+
3246
+ /**
3247
+ * A pop-out window's title, as a handle (Detach Tiles): dragged onto a
3248
+ * Codeman window's tile or empty cell, the session docks there as a tile
3249
+ * and this window closes (_dockForeignTile, which asks it to). The same
3250
+ * drag type as a tile's header; on or off follows the setting, also when it
3251
+ * changes in another window.
3252
+ */
3253
+ _installSoloTileHandle() {
3254
+ const title = document.getElementById('soloSessionTitle');
3255
+ if (!this.isSoloWindow || !title || title._codemanTileHandle) return;
3256
+ title._codemanTileHandle = true;
3257
+ const sync = () => {
3258
+ this._cachedAppSettings = null;
3259
+ const on = this.tileDetachEnabled();
3260
+ title.setAttribute('draggable', on ? 'true' : 'false');
3261
+ title.classList.toggle('solo-session-title--handle', on);
3262
+ const label = on ? "Drag onto a Codeman window's tiles to dock this session there" : '';
3263
+ if (title._codemanTileHandleLabel !== label) {
3264
+ title._codemanTileHandleLabel = label;
3265
+ title.title = label;
3266
+ }
3267
+ };
3268
+ sync();
3269
+ window.addEventListener('storage', (e) => {
3270
+ if (!e.key || e.key === this.getSettingsStorageKey?.()) sync();
3271
+ });
3272
+ title.addEventListener('dragstart', (e) => {
3273
+ if (!this.tileDetachEnabled() || !this.soloSessionId) {
3274
+ e.preventDefault?.();
3275
+ return;
3276
+ }
3277
+ if (e.dataTransfer) {
3278
+ e.dataTransfer.effectAllowed = 'move';
3279
+ e.dataTransfer.setData(TILE_DRAG_TYPE, this.soloSessionId);
3280
+ }
3281
+ });
3282
+ },
2862
3283
  });
2863
3284
 
2864
3285
  // A tiled session deleted (here or elsewhere) loses its tile; if it held focus,
Binary file
Binary file
Binary file
@@ -325,6 +325,7 @@ Object.assign(CodemanApp.prototype, {
325
325
 
326
326
  return `<div class="session-tab session-tab--web ${isActive ? 'active' : ''}" data-webview-id="${escapeHtml(id)}"
327
327
  onclick="app.handleWebviewTabClick(event, ${jsonId})"
328
+ oncontextmenu="app.handleWebviewTabContextMenu(event, ${jsonId})"
328
329
  tabindex="0" role="tab" aria-selected="${isActive ? 'true' : 'false'}"
329
330
  aria-label="${escapeHtml(webview.name)} web tab" title="${escapeHtml(webview.url)}">
330
331
  ${idx < 9 ? '<span class="tab-number">' + (idx + 1) + '</span>' : ''}
@@ -347,6 +348,16 @@ Object.assign(CodemanApp.prototype, {
347
348
  return this.openWebview(id);
348
349
  },
349
350
 
351
+ /**
352
+ * Right-click (or the menu key) on a web tab in the vertical rail opens its
353
+ * actions (Focus, group moves, settings), the flat rail included, where it is
354
+ * the only way in. Everywhere else the browser keeps its own menu.
355
+ */
356
+ handleWebviewTabContextMenu(event, id) {
357
+ if (!this._tabLayoutEditable?.()) return false;
358
+ return this.openTabWebviewMenu?.(event, id);
359
+ },
360
+
350
361
  /** Mark exactly one tab active across BOTH tab kinds. */
351
362
  _updateActiveWebviewTab() {
352
363
  const container = this.$('sessionTabs');
@@ -359,6 +370,7 @@ Object.assign(CodemanApp.prototype, {
359
370
  for (const tab of container.querySelectorAll('.session-tab[data-id]')) tab.classList.remove('active');
360
371
  }
361
372
  this._syncTabTreeSelection?.(container);
373
+ this._renderTabFocus?.();
362
374
  },
363
375
 
364
376
  // ── Opening / closing ─────────────────────────────────────────────────────
Binary file
Binary file
@@ -10,6 +10,14 @@ import { Session } from '../session.js';
10
10
  import { type AuthUser, type SessionState } from '../types.js';
11
11
  import type { SessionPort } from './ports/session-port.js';
12
12
  import type { EventPort } from './ports/event-port.js';
13
+ /**
14
+ * Longest path PARAMETER the router accepts (Fastify's default is 100). A file
15
+ * name goes up to 255 characters and arrives percent-encoded, so the folder
16
+ * grid's `…/files/:name/raw` answered 404 "Route not found" for a name the
17
+ * listing happily showed. Raw length, not decoded: 255 four-byte characters are
18
+ * 3060 characters of `%XX`.
19
+ */
20
+ export declare const MAX_ROUTE_PARAM_LENGTH = 4096;
13
21
  import type { AuthSessionRecord } from './ports/auth-port.js';
14
22
  import type { StaleExpirationMap } from '../utils/index.js';
15
23
  export declare const CASES_DIR: string;
@@ -1 +1 @@
1
- {"version":3,"file":"route-helpers.d.ts","sourceRoot":"","sources":["../../src/web/route-helpers.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"route-helpers.d.ts","sourceRoot":"","sources":["../../src/web/route-helpers.ts"],"names":[],"mappings":"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"}