@kabmo-labs/kabmo 0.7.0 → 0.9.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 (351) hide show
  1. package/README.md +7 -0
  2. package/dist/client/index.html +1 -1
  3. package/dist/client/index.js +403 -384
  4. package/dist/client/static/js/{index-0wvbca0b.js → index-bpptfkz2.js} +905 -875
  5. package/dist/client/static/js/index-k315p1e8.css +1 -0
  6. package/package.json +10 -1
  7. package/src/client/components/common/code-surface/documents.test.ts +120 -0
  8. package/src/client/components/common/code-surface/documents.ts +201 -0
  9. package/src/client/components/common/code-surface/extensions.test.ts +110 -0
  10. package/src/client/components/common/code-surface/extensions.ts +162 -0
  11. package/src/client/components/common/code-surface/handle.ts +102 -0
  12. package/src/client/components/common/code-surface/index.tsx +253 -314
  13. package/src/client/components/common/code-surface/keymap.test.ts +133 -0
  14. package/src/client/components/common/code-surface/keymap.ts +267 -0
  15. package/src/client/components/common/code-surface/marks.ts +74 -0
  16. package/src/client/components/common/code-surface/shiki.ts +234 -0
  17. package/src/client/components/common/code-surface/sync.test.ts +185 -0
  18. package/src/client/components/common/code-surface/sync.ts +58 -0
  19. package/src/client/components/common/code-surface/theme.test.ts +202 -0
  20. package/src/client/components/common/code-surface/theme.ts +111 -0
  21. package/src/client/components/common/editor-view-buttons/index.test.tsx +148 -0
  22. package/src/client/components/common/editor-view-buttons/index.tsx +109 -0
  23. package/src/client/components/common/header-controls/index.tsx +24 -1
  24. package/src/client/components/common/header-controls/shell-chrome.test.tsx +272 -0
  25. package/src/client/components/common/shell-update-button/Modal.tsx +163 -0
  26. package/src/client/components/common/shell-update-button/index.test.tsx +251 -0
  27. package/src/client/components/common/shell-update-button/index.tsx +125 -0
  28. package/src/client/components/common/toolbar-row/index.test.tsx +166 -0
  29. package/src/client/components/common/toolbar-row/index.tsx +189 -0
  30. package/src/client/components/common/workspace-options-menu/index.tsx +19 -1
  31. package/src/client/components/layout/desktop-layout/TopNavbar.tsx +5 -6
  32. package/src/client/components/layout/desktop-layout/WorkspacePanels.tsx +41 -78
  33. package/src/client/components/layout/desktop-layout/index.tsx +55 -69
  34. package/src/client/components/layout/desktop-layout/resizer/index.tsx +6 -3
  35. package/src/client/components/layout/session-sidebar/CategoryHeader.tsx +24 -0
  36. package/src/client/components/layout/session-sidebar/CategoryHeader.worktree.test.tsx +126 -0
  37. package/src/client/components/layout/session-sidebar/CategoryItem.tsx +4 -0
  38. package/src/client/components/layout/session-sidebar/Footer.tsx +8 -15
  39. package/src/client/components/layout/session-sidebar/SessionItem.tsx +12 -0
  40. package/src/client/components/layout/session-sidebar/SessionList.tsx +4 -0
  41. package/src/client/components/layout/session-sidebar/SessionsView.tsx +24 -0
  42. package/src/client/components/layout/session-sidebar/WorktreeBadge.test.ts +48 -0
  43. package/src/client/components/layout/session-sidebar/WorktreeBadge.tsx +55 -0
  44. package/src/client/components/mobile/LayoutWrapper.tsx +23 -16
  45. package/src/client/components/mobile/RightSidebar.tsx +21 -10
  46. package/src/client/components/mobile/SessionSidebar.tsx +8 -0
  47. package/src/client/components/mobile/header-titlebar.test.tsx +172 -0
  48. package/src/client/components/mobile/mobile-main-view/Header.tsx +10 -1
  49. package/src/client/components/mobile/mobile-right-sidebar/FullDiff.tsx +3 -0
  50. package/src/client/components/mobile/mobile-right-sidebar/FullEditor.tsx +63 -38
  51. package/src/client/components/mobile/mobile-right-sidebar/OutlineButton.test.tsx +137 -0
  52. package/src/client/components/mobile/mobile-right-sidebar/OutlineButton.tsx +14 -3
  53. package/src/client/components/mobile/mobile-session-sidebar/Footer.tsx +9 -21
  54. package/src/client/components/mobile/mobile-session-sidebar/Header.tsx +6 -2
  55. package/src/client/components/mobile/mobile-session-sidebar/Item.tsx +16 -0
  56. package/src/client/components/mobile/mobile-session-sidebar/List.tsx +4 -0
  57. package/src/client/components/mobile/mobile-session-sidebar/SessionRow.tsx +5 -0
  58. package/src/client/components/mobile/mobile-session-sidebar/WorktreeDialog.tsx +49 -0
  59. package/src/client/components/mobile/worktree-session.ts +51 -0
  60. package/src/client/components/settings/Modal.tsx +8 -0
  61. package/src/client/components/settings/categories/AppearanceSettings.tsx +3 -0
  62. package/src/client/components/settings/categories/ProjectSettings.tsx +22 -8
  63. package/src/client/components/settings/categories/appearance-settings/EditorThemeSection.test.tsx +167 -0
  64. package/src/client/components/settings/categories/appearance-settings/EditorThemeSection.tsx +184 -0
  65. package/src/client/components/settings/categories/project-settings/WorktreeSettings.tsx +146 -0
  66. package/src/client/components/settings/categories/project-settings/worktree-row.test.ts +83 -0
  67. package/src/client/components/settings/categories/project-settings/worktree-row.ts +47 -0
  68. package/src/client/components/workspace/chat-timeline/EmptyWorkspacePrompt.tsx +36 -55
  69. package/src/client/components/workspace/chat-timeline/PendingChatView.tsx +8 -0
  70. package/src/client/components/workspace/chat-timeline/SystemNotice.tsx +9 -4
  71. package/src/client/components/workspace/chat-timeline/TaskResultContent.tsx +9 -7
  72. package/src/client/components/workspace/chat-timeline/index.tsx +5 -0
  73. package/src/client/components/workspace/chat-timeline/target-picker/WorkspacePicker.test.tsx +132 -0
  74. package/src/client/components/workspace/chat-timeline/target-picker/WorkspacePicker.tsx +120 -0
  75. package/src/client/components/workspace/chat-timeline/target-picker/WorktreePicker.test.tsx +319 -0
  76. package/src/client/components/workspace/chat-timeline/target-picker/WorktreePicker.tsx +285 -0
  77. package/src/client/components/workspace/chat-timeline/tool-renderers/ask-panel/index.test.ts +2 -0
  78. package/src/client/components/workspace/chat-timeline/tool-renderers/hashline-patch/index.tsx +34 -29
  79. package/src/client/components/workspace/chat-timeline/tool-renderers/panels/AstEdit.tsx +11 -9
  80. package/src/client/components/workspace/chat-timeline/tool-renderers/panels/Bash.tsx +10 -7
  81. package/src/client/components/workspace/chat-timeline/tool-renderers/panels/Edit.tsx +25 -8
  82. package/src/client/components/workspace/chat-timeline/tool-renderers/panels/Eval.test.tsx +146 -0
  83. package/src/client/components/workspace/chat-timeline/tool-renderers/panels/Eval.tsx +32 -17
  84. package/src/client/components/workspace/chat-timeline/tool-renderers/panels/FileMatches.test.ts +114 -0
  85. package/src/client/components/workspace/chat-timeline/tool-renderers/panels/FileMatches.tsx +111 -0
  86. package/src/client/components/workspace/chat-timeline/tool-renderers/panels/Read.tsx +7 -7
  87. package/src/client/components/workspace/chat-timeline/tool-renderers/panels/SearchTool.tsx +9 -59
  88. package/src/client/components/workspace/chat-timeline/tool-renderers/shared/ArtifactBody.tsx +2 -7
  89. package/src/client/components/workspace/chat-timeline/tool-renderers/shared/DiffView.test.ts +27 -28
  90. package/src/client/components/workspace/chat-timeline/tool-renderers/shared/DiffView.tsx +39 -26
  91. package/src/client/components/workspace/chat-timeline/tool-renderers/shared/ExcerptCode.tsx +8 -8
  92. package/src/client/components/workspace/chat-timeline/tool-renderers/shared/HighlightedCode.tsx +35 -0
  93. package/src/client/components/workspace/chat-timeline/tool-renderers/shared/JsonCodeBlock.tsx +4 -4
  94. package/src/client/components/workspace/diff-panel/MergeDiffView.tsx +179 -0
  95. package/src/client/components/workspace/diff-panel/ThemedMergeDiffView.tsx +24 -0
  96. package/src/client/components/workspace/diff-panel/Toolbar.tsx +224 -186
  97. package/src/client/components/workspace/diff-panel/ToolbarSegments.tsx +98 -0
  98. package/src/client/components/workspace/diff-panel/index.tsx +76 -145
  99. package/src/client/components/workspace/diff-panel/merge.ts +315 -0
  100. package/src/client/components/workspace/editor/Body.tsx +4 -7
  101. package/src/client/components/workspace/editor/CommandPalette.tsx +2 -2
  102. package/src/client/components/workspace/editor/FileDocument.tsx +58 -63
  103. package/src/client/components/workspace/editor/FilePane.tsx +4 -7
  104. package/src/client/components/workspace/editor/FindWidget.tsx +7 -2
  105. package/src/client/components/workspace/editor/Toolbar.tsx +220 -123
  106. package/src/client/components/workspace/editor/index.tsx +7 -9
  107. package/src/client/components/workspace/file-explorer/ContextMenu.tsx +2 -1
  108. package/src/client/components/workspace/file-explorer/Modals.tsx +40 -12
  109. package/src/client/components/workspace/file-explorer/OutlineSection.test.tsx +18 -0
  110. package/src/client/components/workspace/file-explorer/OutlineSection.tsx +10 -3
  111. package/src/client/components/workspace/file-explorer/RootMenu.tsx +14 -5
  112. package/src/client/components/workspace/file-explorer/Toolbar.tsx +74 -0
  113. package/src/client/components/workspace/file-explorer/TreeItem.tsx +38 -3
  114. package/src/client/components/workspace/file-explorer/filter.ts +47 -0
  115. package/src/client/components/workspace/file-explorer/index.tsx +77 -64
  116. package/src/client/components/workspace/file-explorer/local-edit.test.ts +135 -0
  117. package/src/client/components/workspace/search-panel/Body.tsx +1 -1
  118. package/src/client/components/workspace/worktree-modal/index.test.tsx +118 -0
  119. package/src/client/components/workspace/worktree-modal/index.tsx +261 -0
  120. package/src/client/hooks/chat/omp/reconnect.test.ts +338 -0
  121. package/src/client/hooks/chat/omp/reconnect.ts +187 -0
  122. package/src/client/hooks/chat/omp/run-liveness.test.ts +71 -0
  123. package/src/client/hooks/chat/omp/run-liveness.ts +65 -0
  124. package/src/client/hooks/chat/omp/session-list.activity.test.ts +114 -0
  125. package/src/client/hooks/chat/omp/session-list.test-util.ts +164 -0
  126. package/src/client/hooks/chat/omp/session-list.test.ts +30 -150
  127. package/src/client/hooks/chat/omp/session-list.tsx +69 -9
  128. package/src/client/hooks/chat/timeline/generating.ts +19 -2
  129. package/src/client/hooks/chat/timeline/index.ts +27 -28
  130. package/src/client/hooks/chat/timeline/lost-run.test.ts +329 -0
  131. package/src/client/hooks/chat/timeline/lost-run.ts +177 -0
  132. package/src/client/hooks/chat/timeline/send-execute.test.ts +2 -0
  133. package/src/client/hooks/chat/timeline/send-steer.test.ts +2 -0
  134. package/src/client/hooks/chat/timeline/send.test.ts +2 -0
  135. package/src/client/hooks/chat/timeline/send.ts +11 -1
  136. package/src/client/hooks/chat/timeline/session-load.test-util.ts +205 -0
  137. package/src/client/hooks/chat/timeline/session-load.test.ts +61 -162
  138. package/src/client/hooks/chat/timeline/session-load.ts +69 -37
  139. package/src/client/hooks/chat/timeline/session-switch.test.ts +65 -0
  140. package/src/client/hooks/chat/timeline/session-switch.ts +63 -0
  141. package/src/client/hooks/chat/timeline/spawn-target.ts +52 -0
  142. package/src/client/hooks/chat/timeline/tool-open.test.ts +74 -0
  143. package/src/client/hooks/chat/timeline/tool-open.ts +52 -8
  144. package/src/client/hooks/code/deferred-highlight.test.ts +182 -0
  145. package/src/client/hooks/code/deferred-highlight.ts +129 -0
  146. package/src/client/hooks/editor/find-state.ts +1 -1
  147. package/src/client/hooks/editor/theme.test.ts +206 -0
  148. package/src/client/hooks/editor/theme.ts +82 -0
  149. package/src/client/hooks/editor/use-editor-drop.test.ts +101 -0
  150. package/src/client/hooks/editor/use-editor-drop.ts +67 -14
  151. package/src/client/hooks/editor/use-editor-find.ts +7 -7
  152. package/src/client/hooks/editor/use-outline-view.test.ts +182 -0
  153. package/src/client/hooks/editor/use-outline-view.ts +94 -7
  154. package/src/client/hooks/editor/use-scroll-restore.test.ts +55 -2
  155. package/src/client/hooks/editor/use-scroll-restore.ts +124 -4
  156. package/src/client/hooks/editor/use-search-reveal.ts +1 -1
  157. package/src/client/hooks/ui/long-press.test.ts +174 -0
  158. package/src/client/hooks/ui/long-press.ts +112 -0
  159. package/src/client/hooks/ui/settings-modal.ts +56 -0
  160. package/src/client/hooks/ui/updates/shell-download.test.ts +75 -0
  161. package/src/client/hooks/ui/updates/shell-download.ts +153 -28
  162. package/src/client/hooks/ui/updates/shell-offer.test.ts +213 -0
  163. package/src/client/hooks/ui/updates/shell-update.test.ts +140 -0
  164. package/src/client/hooks/ui/updates/shell-update.ts +119 -0
  165. package/src/client/hooks/workspace/diff-file.ts +29 -10
  166. package/src/client/hooks/workspace/diff-git-actions.ts +147 -0
  167. package/src/client/hooks/workspace/editor-view.ts +61 -0
  168. package/src/client/hooks/workspace/file-create.ts +41 -11
  169. package/src/client/hooks/workspace/file-listing.ts +56 -1
  170. package/src/client/hooks/workspace/file-tabs.test.ts +62 -5
  171. package/src/client/hooks/workspace/file-tabs.ts +24 -11
  172. package/src/client/hooks/workspace/file-tree-actions.ts +37 -5
  173. package/src/client/hooks/workspace/navbar-session-actions.ts +72 -0
  174. package/src/client/hooks/workspace/worktree/index.ts +165 -0
  175. package/src/client/lib/desktop/bridge.ts +45 -0
  176. package/src/client/lib/search-reveal.ts +1 -1
  177. package/src/client/main.tsx +5 -1
  178. package/src/client/tailwind.css +56 -8
  179. package/src/server/index.ts +7 -2
  180. package/src/server/lib/auth/guard.test.ts +28 -0
  181. package/src/server/lib/auth/guard.ts +10 -0
  182. package/src/server/lib/bundler/titlebar-css.test.ts +42 -1
  183. package/src/server/lib/db/schema.ts +4 -0
  184. package/src/server/lib/db/worktree-registry.server.ts +91 -0
  185. package/src/server/lib/fs/canonical.ts +52 -0
  186. package/src/server/lib/fs/relative-path.ts +48 -0
  187. package/src/server/lib/fs/root-worktree.test.ts +116 -0
  188. package/src/server/lib/fs/root.ts +39 -10
  189. package/src/server/lib/fs/worktree/carry.ts +134 -0
  190. package/src/server/lib/fs/worktree/create.test.ts +249 -0
  191. package/src/server/lib/fs/worktree/create.ts +262 -0
  192. package/src/server/lib/fs/worktree/errors.ts +43 -0
  193. package/src/server/lib/fs/worktree/list.test.ts +207 -0
  194. package/src/server/lib/fs/worktree/list.ts +142 -0
  195. package/src/server/lib/fs/worktree/location.test.ts +206 -0
  196. package/src/server/lib/fs/worktree/location.ts +114 -0
  197. package/src/server/lib/fs/worktree/project-id.ts +53 -0
  198. package/src/server/lib/fs/worktree/remove.test.ts +242 -0
  199. package/src/server/lib/fs/worktree/remove.ts +152 -0
  200. package/src/server/lib/fs/worktree/settings.server.ts +56 -0
  201. package/src/server/lib/fs/worktree/topology.ts +142 -0
  202. package/src/server/lib/lifecycle/claim-port.test.ts +156 -0
  203. package/src/server/lib/lifecycle/port-guard.test.ts +20 -164
  204. package/src/server/lib/lifecycle/port-guard.ts +39 -7
  205. package/src/server/lib/lifecycle/probe-health.test.ts +166 -0
  206. package/src/server/lib/lifecycle/probe.test.ts +77 -164
  207. package/src/server/lib/lifecycle/probe.ts +121 -13
  208. package/src/server/lib/omp/rpc/frame-fold-signals.test.ts +112 -0
  209. package/src/server/lib/omp/rpc/frame-fold.ts +10 -5
  210. package/src/server/lib/omp/session/sidebar-activity.server.test.ts +162 -0
  211. package/src/server/lib/omp/session/sidebar-activity.server.ts +70 -0
  212. package/src/server/lib/omp/session/sidebar-data.canonical.test.ts +155 -0
  213. package/src/server/lib/omp/session/sidebar-data.server.ts +28 -1
  214. package/src/server/lib/omp/session/telemetry/compute.ts +13 -1
  215. package/src/server/lib/omp/session/telemetry.test.ts +33 -0
  216. package/src/server/lib/panels/local-edits.server.ts +33 -0
  217. package/src/server/lib/panels/sync.fixture.test-util.ts +92 -0
  218. package/src/server/lib/panels/sync.local-edits.test.ts +150 -0
  219. package/src/server/lib/panels/sync.server.test.ts +125 -147
  220. package/src/server/lib/panels/sync.server.ts +153 -48
  221. package/src/server/lib/realtime/signals.server.ts +10 -0
  222. package/src/server/lib/realtime/topics/catalog.server.ts +4 -1
  223. package/src/server/lib/realtime/topics.server.ts +16 -0
  224. package/src/server/lib/updates/shell-assets.test.ts +114 -0
  225. package/src/server/lib/updates/shell-assets.ts +200 -0
  226. package/src/server/lib/updates/shell-cache.ts +105 -0
  227. package/src/server/lib/updates/shell-pages.test.ts +187 -0
  228. package/src/server/lib/updates/shell-pages.ts +157 -0
  229. package/src/server/lib/updates/shell-release-desktop.test.ts +166 -0
  230. package/src/server/lib/updates/shell-release.test.ts +59 -71
  231. package/src/server/lib/updates/shell-release.ts +178 -198
  232. package/src/server/routes/fs/action-create.test.ts +18 -13
  233. package/src/server/routes/fs/action-nested.test.ts +184 -0
  234. package/src/server/routes/fs/action.ts +47 -16
  235. package/src/server/routes/fs/git-merge.test.ts +11 -1
  236. package/src/server/routes/fs/read.test.ts +24 -7
  237. package/src/server/routes/fs/scope.test.ts +15 -10
  238. package/src/server/routes/fs/scope.ts +3 -14
  239. package/src/server/routes/index.ts +2 -0
  240. package/src/server/routes/updates/android.ts +6 -1
  241. package/src/server/routes/updates/desktop.ts +59 -0
  242. package/src/server/routes/updates/index.ts +2 -0
  243. package/src/server/routes/updates/ios.ts +5 -1
  244. package/src/server/routes/worktrees/create.ts +138 -0
  245. package/src/server/routes/worktrees/index.ts +25 -0
  246. package/src/server/routes/worktrees/list.ts +134 -0
  247. package/src/server/routes/worktrees/remove.ts +102 -0
  248. package/src/server/routes/worktrees/routes.test.ts +326 -0
  249. package/src/server/routes/worktrees/shared.ts +90 -0
  250. package/src/shared/lib/chat/timeline/omp-callbacks.test.ts +36 -0
  251. package/src/shared/lib/chat/timeline/omp-callbacks.ts +18 -0
  252. package/src/shared/lib/chat/timeline/run-signals.test.ts +2 -0
  253. package/src/shared/lib/code/editor/comment-syntax.test.ts +37 -0
  254. package/src/shared/lib/code/editor/comment-syntax.ts +55 -0
  255. package/src/shared/lib/code/editor/find-replace.test.ts +134 -0
  256. package/src/shared/lib/code/editor/{find.ts → find-replace.ts} +23 -107
  257. package/src/shared/lib/code/editor/{find.test.ts → find-scan.test.ts} +19 -73
  258. package/src/shared/lib/code/editor/find-scan.ts +177 -0
  259. package/src/shared/lib/code/editor/keymap.ts +3 -3
  260. package/src/shared/lib/code/editor/lines.test.ts +7 -55
  261. package/src/shared/lib/code/editor/lines.ts +8 -48
  262. package/src/shared/lib/code/editor/marks.test.ts +4 -5
  263. package/src/shared/lib/code/editor/marks.ts +13 -25
  264. package/src/shared/lib/code/editor/theme-loader.ts +97 -0
  265. package/src/shared/lib/code/editor/themes.generated.ts +156 -0
  266. package/src/shared/lib/code/editor/themes.test.ts +165 -0
  267. package/src/shared/lib/code/editor/themes.ts +165 -0
  268. package/src/shared/lib/code/highlight-memo.test.ts +103 -0
  269. package/src/shared/lib/code/highlight-memo.ts +149 -0
  270. package/src/shared/lib/code/highlighter-warmup.ts +112 -0
  271. package/src/shared/lib/code/highlighter.ts +3 -1
  272. package/src/shared/lib/code/idle.ts +37 -0
  273. package/src/shared/lib/code/outline/rows.test.ts +96 -0
  274. package/src/shared/lib/code/outline/rows.ts +94 -0
  275. package/src/shared/lib/code/syntax-highlight.test.ts +1 -79
  276. package/src/shared/lib/code/syntax-highlight.ts +2 -3
  277. package/src/shared/lib/fs/file-kind-classify.test.ts +25 -11
  278. package/src/shared/lib/fs/file-kind.ts +47 -13
  279. package/src/shared/lib/fs/file-tree.test.ts +183 -0
  280. package/src/shared/lib/fs/file-tree.ts +159 -0
  281. package/src/shared/lib/fs/file-url.test.ts +52 -0
  282. package/src/shared/lib/fs/file-url.ts +37 -0
  283. package/src/shared/lib/fs/patch-sides.test.ts +114 -0
  284. package/src/shared/lib/fs/patch-sides.ts +139 -0
  285. package/src/shared/lib/fs/paths.test.ts +24 -1
  286. package/src/shared/lib/fs/paths.ts +17 -0
  287. package/src/shared/lib/fs/worktree/settings-keys.ts +18 -0
  288. package/src/shared/lib/omp/session/prewarm.ts +7 -0
  289. package/src/shared/lib/realtime/protocol.ts +16 -0
  290. package/src/shared/lib/ui/toolbar-overflow.test.ts +83 -0
  291. package/src/shared/lib/ui/toolbar-overflow.ts +86 -0
  292. package/src/shared/lib/updates/shell-arch.ts +54 -0
  293. package/src/shared/lib/updates/shell-ua.ts +24 -4
  294. package/src/shared/lib/workspace/active-project.ts +23 -0
  295. package/src/shared/lib/workspace/editor-view-mode.test.ts +70 -0
  296. package/src/shared/lib/workspace/editor-view-mode.ts +48 -0
  297. package/src/shared/lib/workspace/panel-widths.test.ts +30 -0
  298. package/src/shared/lib/workspace/panel-widths.ts +25 -5
  299. package/src/shared/lib/workspace/session-state/cache.ts +159 -0
  300. package/src/shared/lib/workspace/session-state/harness.test-util.ts +55 -0
  301. package/src/shared/lib/workspace/session-state/merge.ts +100 -0
  302. package/src/shared/lib/workspace/session-state/store-load.test.ts +206 -0
  303. package/src/shared/lib/workspace/session-state/store.test.ts +17 -139
  304. package/src/shared/lib/workspace/session-state/store.ts +92 -130
  305. package/src/shared/lib/workspace/workspace-modules.test.ts +49 -1
  306. package/src/shared/lib/workspace/workspace-panel-widths.ts +124 -0
  307. package/src/shared/lib/workspace/worktree/branch-name.test.ts +95 -0
  308. package/src/shared/lib/workspace/worktree/branch-name.ts +115 -0
  309. package/src/shared/lib/workspace/worktree/types.ts +104 -0
  310. package/src/shared/types/fs.ts +10 -0
  311. package/src/shared/types/realtime.ts +29 -0
  312. package/src/shared/types/settings/state.ts +10 -0
  313. package/src/shared/types/workspace.ts +14 -0
  314. package/src/test-support/file-explorer-disk.test-util.ts +76 -0
  315. package/src/test-support/file-explorer.test-util.ts +71 -40
  316. package/src/test-support/loopback.ts +183 -0
  317. package/dist/client/static/js/index-nzyjf92m.css +0 -1
  318. package/src/client/components/common/code-editor/handler.test.ts +0 -274
  319. package/src/client/components/common/code-editor/handler.ts +0 -175
  320. package/src/client/components/common/code-editor/index.tsx +0 -314
  321. package/src/client/components/common/code-surface/measure.ts +0 -148
  322. package/src/client/components/workspace/diff-panel/SplitView.tsx +0 -209
  323. package/src/client/components/workspace/diff-panel/UnifiedView.tsx +0 -150
  324. package/src/client/hooks/editor/scroll-container.test.ts +0 -102
  325. package/src/client/hooks/editor/scroll-container.ts +0 -26
  326. package/src/client/hooks/editor/use-code-window.ts +0 -100
  327. package/src/client/hooks/editor/use-editor-history.test.ts +0 -216
  328. package/src/client/hooks/editor/use-editor-history.ts +0 -134
  329. package/src/client/hooks/editor/use-occurrences.test.ts +0 -236
  330. package/src/client/hooks/editor/use-occurrences.ts +0 -98
  331. package/src/shared/lib/code/editor/commands.test.ts +0 -155
  332. package/src/shared/lib/code/editor/commands.ts +0 -260
  333. package/src/shared/lib/code/editor/edits.test.ts +0 -100
  334. package/src/shared/lib/code/editor/edits.ts +0 -122
  335. package/src/shared/lib/code/editor/history.test.ts +0 -118
  336. package/src/shared/lib/code/editor/history.ts +0 -101
  337. package/src/shared/lib/code/editor/layers.test.ts +0 -77
  338. package/src/shared/lib/code/editor/layers.ts +0 -67
  339. package/src/shared/lib/code/editor/line-edits.test.ts +0 -131
  340. package/src/shared/lib/code/editor/line-edits.ts +0 -159
  341. package/src/shared/lib/code/editor/occurrences.test.ts +0 -117
  342. package/src/shared/lib/code/editor/occurrences.ts +0 -111
  343. package/src/shared/lib/code/editor/reset-css.test.ts +0 -35
  344. package/src/shared/lib/code/editor/reset-css.ts +0 -32
  345. package/src/shared/lib/code/editor/text-commands.test.ts +0 -178
  346. package/src/shared/lib/code/editor/text-commands.ts +0 -154
  347. package/src/shared/lib/code/lazy-window.test.ts +0 -161
  348. package/src/shared/lib/code/lazy-window.ts +0 -156
  349. package/src/shared/lib/code/windowed-highlight.ts +0 -114
  350. package/src/shared/lib/fs/diff-parser.test.ts +0 -116
  351. package/src/shared/lib/fs/diff-parser.ts +0 -213
@@ -0,0 +1,118 @@
1
+ /**
2
+ * @license
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ /**
7
+ * The New worktree dialog, rendered.
8
+ *
9
+ * Three rules here are the ones a reader would otherwise discover in a browser
10
+ * and nowhere else:
11
+ *
12
+ * - the generated branch is in the FIRST paint (before any effect flushes),
13
+ * because the window between paint and the effect is exactly when a user
14
+ * types — measured: a field filled with `main` was replaced by the generated
15
+ * name and the dialog created `wt/calm-iguana` instead of reporting that
16
+ * `main` is taken;
17
+ * - the folder follows the branch, so it is never named after a previous one;
18
+ * - a closed dialog renders nothing at all, so no field is focusable while it
19
+ * is shut.
20
+ */
21
+
22
+ import { afterAll, afterEach, beforeAll, describe, expect, test } from 'bun:test';
23
+ import { Window } from 'happy-dom';
24
+ import { h, render } from 'preact';
25
+ import { act } from 'preact/test-utils';
26
+ import { installDomGlobals, restoreDomGlobals } from '@/test-support/pristine-globals';
27
+ import { NewWorktreeModal } from '@/client/components/workspace/worktree-modal/index';
28
+
29
+ const nativeFetch = Bun.fetch;
30
+ const mounted: HTMLElement[] = [];
31
+
32
+ beforeAll(() => {
33
+ installDomGlobals(new Window({ url: 'http://localhost' }));
34
+ });
35
+
36
+ afterEach(() => {
37
+ for (const host of mounted.splice(0)) {
38
+ render(null, host);
39
+ host.remove();
40
+ }
41
+ (globalThis as unknown as Record<string, unknown>).fetch = nativeFetch;
42
+ });
43
+
44
+ afterAll(() => {
45
+ restoreDomGlobals();
46
+ });
47
+
48
+ /**
49
+ * Mount, with the first render FLUSHED. Preact batches a state update into a
50
+ * microtask, so a `useEffect` that seeds a field has not run when `render`
51
+ * returns — reading the inputs then reads empty ones, which is a property of the
52
+ * test harness and not of the dialog.
53
+ */
54
+ async function mount(node: h.JSX.Element): Promise<HTMLElement> {
55
+ const host = document.createElement('div');
56
+ document.body.appendChild(host);
57
+ await act(async () => {
58
+ render(node, host);
59
+ });
60
+ mounted.push(host);
61
+ return host;
62
+ }
63
+
64
+ function textOf(host: HTMLElement): string {
65
+ return (host.textContent ?? '').replace(/\s+/g, ' ').trim();
66
+ }
67
+
68
+ describe('NewWorktreeModal', () => {
69
+ test('opens on a generated branch, with the folder following it', async () => {
70
+ const host = await mount(
71
+ h(NewWorktreeModal, { isOpen: true, onClose: () => {}, root: '/repo', folderId: 1, folderName: 'kabmo', onCreated: () => {} }),
72
+ );
73
+
74
+ const inputs = [...host.querySelectorAll('input')] as HTMLInputElement[];
75
+ const branch = inputs[0]?.value ?? '';
76
+ const folderName = inputs[2]?.value ?? '';
77
+ expect(branch).toMatch(/^wt\/[a-z]+-[a-z]+$/);
78
+ // The folder is the branch without our namespace, so it is never `wt-x`.
79
+ expect(folderName).toBe(branch.replace(/^wt\//, ''));
80
+ expect(folderName.startsWith('wt-')).toBe(false);
81
+ });
82
+
83
+ test('the generated branch is in the FIRST paint, before any effect flushes', () => {
84
+ const host = document.createElement('div');
85
+ document.body.appendChild(host);
86
+ // Deliberately NOT `mount`: no `act`, so no effect has run. A field seeded
87
+ // by an effect reads empty here, and the browser's own window between paint
88
+ // and the effect flush is exactly when a user types into it.
89
+ render(
90
+ h(NewWorktreeModal, { isOpen: true, onClose: () => {}, root: '/repo', folderId: 1, folderName: 'kabmo', onCreated: () => {} }),
91
+ host,
92
+ );
93
+ mounted.push(host);
94
+
95
+ const inputs = [...host.querySelectorAll('input')] as HTMLInputElement[];
96
+ const branch = inputs[0]?.value ?? '';
97
+ expect(branch).toMatch(/^wt\/[a-z]+-[a-z]+$/);
98
+ // One generated name for both fields: deriving the folder from a SECOND
99
+ // call would show a folder the branch does not have.
100
+ expect(inputs[2]?.value ?? '').toBe(branch.replace(/^wt\//, ''));
101
+ });
102
+
103
+ test('states what happens to uncommitted changes, and on which project', async () => {
104
+ const host = await mount(
105
+ h(NewWorktreeModal, { isOpen: true, onClose: () => {}, root: '/repo', folderId: 1, folderName: 'kabmo', onCreated: () => {} }),
106
+ );
107
+ const text = textOf(host);
108
+ expect(text).toContain('Uncommitted changes stay in');
109
+ expect(text).toContain('kabmo');
110
+ });
111
+
112
+ test('renders nothing while closed', async () => {
113
+ const host = await mount(
114
+ h(NewWorktreeModal, { isOpen: false, onClose: () => {}, root: '/repo', folderId: 1, onCreated: () => {} }),
115
+ );
116
+ expect(host.textContent).toBe('');
117
+ });
118
+ });
@@ -0,0 +1,261 @@
1
+ /**
2
+ * @license
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ /**
7
+ * New worktree — the dialog, modelled on OpenChamber's.
8
+ *
9
+ * Two columns on a wide screen and one on a phone, and the split is the point:
10
+ * the left column is the CHOICE (which branch this checkout is), the right is the
11
+ * NAMES (branch, base, folder). A single column put the choice above a wall of
12
+ * fields and the "Create worktree" button below the fold.
13
+ *
14
+ * Three rules the form carries, each from a measured failure:
15
+ *
16
+ * - **The folder follows the branch until the user edits it.** A dialog that
17
+ * asked for both independently produced a folder named after the previous
18
+ * branch the moment the branch was retyped; the reset button puts it back in
19
+ * sync and is disabled while it already is.
20
+ * - **Validation is the SERVER's.** A branch collision, a branch already checked
21
+ * out, an unborn repository — the client cannot know any of them without git,
22
+ * so the debounced call runs the same plan the create will run.
23
+ * - **The dirty notice states what stays behind.** Uncommitted changes are not
24
+ * carried into a new worktree, and a user told that afterwards has already
25
+ * lost the chance to commit them.
26
+ */
27
+
28
+ import { useEffect, useMemo, useState } from 'preact/hooks';
29
+ import { Modal } from '@/client/components/common/Modal';
30
+ import { createWorktree, validateWorktree } from '@/client/hooks/workspace/worktree/index';
31
+ import { WorktreeRequestError } from '@/client/hooks/workspace/worktree/index';
32
+ import { defaultWorktreeBranch, folderNameForBranch } from '@/shared/lib/workspace/worktree/branch-name';
33
+ import type { WorktreeFailure } from '@/shared/lib/workspace/worktree/types';
34
+
35
+ export interface NewWorktreeModalProps {
36
+ isOpen: boolean;
37
+ onClose: () => void;
38
+ /** The project the worktree belongs to. */
39
+ root: string | null;
40
+ folderId: number | null;
41
+ folderName?: string;
42
+ /** Called with the created path — the caller selects it as the chat's target. */
43
+ onCreated: (worktree: { path: string; branch: string }) => void;
44
+ }
45
+
46
+ type Mode = 'new' | 'existing';
47
+
48
+ /** A thrown request, narrowed to the failure the dialog reports. */
49
+ function asFailure(error: unknown): WorktreeFailure {
50
+ if (error instanceof WorktreeRequestError) return error.failure;
51
+ return { code: 'create_failed', message: error instanceof Error ? error.message : String(error) };
52
+ }
53
+
54
+ const FIELD_CLASS = 'w-full h-8 rounded-md border border-ink/15 bg-paper px-2 text-xs text-ink outline-none focus:border-ink/40';
55
+
56
+ export function NewWorktreeModal(props: NewWorktreeModalProps) {
57
+ // Mounted fresh on every open, never reset by an effect. Preact runs effects
58
+ // AFTER paint, so an effect-seeded form is on screen and focusable while its
59
+ // fields still hold the previous open's values — and anything typed in that
60
+ // window is discarded. Measured in the browser: the branch field was filled
61
+ // with `main`, the effect then restored the generated name, and the dialog
62
+ // created `wt/calm-iguana` instead of reporting that `main` is taken.
63
+ if (!props.isOpen) return null;
64
+ return <WorktreeForm {...props} />;
65
+ }
66
+
67
+ function WorktreeForm({ onClose, root, folderId, folderName, onCreated }: NewWorktreeModalProps) {
68
+ const [mode, setMode] = useState<Mode>('new');
69
+ // Seeded by the initial state, so the very first paint carries a usable form.
70
+ const [branch, setBranch] = useState(defaultWorktreeBranch);
71
+ const [basedOn, setBasedOn] = useState('');
72
+ const [folder, setFolder] = useState(() => folderNameForBranch(branch));
73
+ const [folderFollowsBranch, setFolderFollowsBranch] = useState(true);
74
+ const [failure, setFailure] = useState<WorktreeFailure | null>(null);
75
+ const [creating, setCreating] = useState(false);
76
+
77
+ // The folder mirrors the branch while the user has not renamed it.
78
+ useEffect(() => {
79
+ if (!folderFollowsBranch) return;
80
+ setFolder(folderNameForBranch(branch));
81
+ }, [branch, folderFollowsBranch]);
82
+
83
+ const request = useMemo(
84
+ () => ({
85
+ mode,
86
+ branch: branch.trim(),
87
+ worktreeName: folder.trim(),
88
+ ...(basedOn.trim() ? { startRef: basedOn.trim() } : {}),
89
+ }),
90
+ [mode, branch, folder, basedOn],
91
+ );
92
+
93
+ // Debounced server validation, after the first edit — the answers depend on
94
+ // git state a keystroke cannot know.
95
+ useEffect(() => {
96
+ if (!root) return;
97
+ const timer = setTimeout(() => {
98
+ void validateWorktree({ root, folderId }, request).then((result) => {
99
+ setFailure(result.ok ? null : result.failures[0] ?? null);
100
+ }).catch(() => {
101
+ // A validation request that failed says nothing about the form; the
102
+ // create reports what it finds.
103
+ });
104
+ }, 300);
105
+ return () => clearTimeout(timer);
106
+ }, [root, folderId, request]);
107
+
108
+ const canCreate = Boolean(root) && branch.trim().length > 0 && folder.trim().length > 0 && !creating && !failure;
109
+
110
+ const submit = async () => {
111
+ if (!root || !canCreate) return;
112
+ setCreating(true);
113
+ try {
114
+ const created = await createWorktree({ root, folderId }, request);
115
+ onCreated({ path: created.path, branch: created.branch });
116
+ onClose();
117
+ } catch (error) {
118
+ // `WorktreeRequestError` carries the server's field/code; anything else is
119
+ // reported as a create failure with its own message.
120
+ setFailure(asFailure(error));
121
+ } finally {
122
+ setCreating(false);
123
+ }
124
+ };
125
+
126
+ const choiceColumn = (
127
+ <div className="flex flex-col gap-4">
128
+ <div className="flex flex-col gap-2">
129
+ <span className="text-[11px] font-semibold text-ink">Start from</span>
130
+ {([
131
+ { id: 'new' as const, label: 'New branch', hint: 'A fresh branch, based on the branch you pick.' },
132
+ { id: 'existing' as const, label: 'Existing branch', hint: 'Check out a branch you already have.' },
133
+ ]).map((option) => (
134
+ <button
135
+ key={option.id}
136
+ type="button"
137
+ role="radio"
138
+ aria-checked={mode === option.id}
139
+ onClick={() => setMode(option.id)}
140
+ className={`rounded-md border px-3 py-2 text-left transition-colors ${
141
+ mode === option.id ? 'border-ink/40 bg-ink/5' : 'border-ink/15 hover:bg-ink/5'
142
+ }`}
143
+ >
144
+ <span className="block text-xs font-medium text-ink">{option.label}</span>
145
+ <span className="block text-[10.5px] text-ink/50">{option.hint}</span>
146
+ </button>
147
+ ))}
148
+ </div>
149
+ </div>
150
+ );
151
+
152
+ const fieldColumn = (
153
+ <div className="flex flex-col gap-4">
154
+ <label className="flex flex-col gap-1.5">
155
+ <span className="text-[11px] font-semibold text-ink">{mode === 'existing' ? 'Branch' : 'Branch name'}</span>
156
+ <input
157
+ value={branch}
158
+ onChange={(event) => setBranch(event.currentTarget.value)}
159
+ onKeyDown={(event) => {
160
+ if (event.key === 'Enter') {
161
+ event.preventDefault();
162
+ void submit();
163
+ }
164
+ }}
165
+ placeholder="feature/my-awesome-feature"
166
+ className={FIELD_CLASS}
167
+ aria-invalid={failure?.field === 'branch'}
168
+ />
169
+ </label>
170
+
171
+ <label className="flex flex-col gap-1.5">
172
+ <span className="text-[11px] font-semibold text-ink">Based on</span>
173
+ <input
174
+ value={basedOn}
175
+ onChange={(event) => setBasedOn(event.currentTarget.value)}
176
+ placeholder="origin/main"
177
+ disabled={mode === 'existing'}
178
+ className={`${FIELD_CLASS} disabled:opacity-50`}
179
+ />
180
+ <span className="text-[10px] text-ink/40">
181
+ Defaults to the repository's default branch. Left empty means its current HEAD.
182
+ </span>
183
+ </label>
184
+
185
+ <label className="flex flex-col gap-1.5">
186
+ <span className="text-[11px] font-semibold text-ink">Folder name</span>
187
+ <span className="flex items-center gap-1.5">
188
+ <input
189
+ value={folder}
190
+ onChange={(event) => {
191
+ setFolder(event.currentTarget.value);
192
+ setFolderFollowsBranch(false);
193
+ }}
194
+ placeholder="my-worktree-directory"
195
+ className={`${FIELD_CLASS} min-w-0 flex-1`}
196
+ aria-invalid={failure?.field === 'folder'}
197
+ />
198
+ <button
199
+ type="button"
200
+ title="Reset to match branch name"
201
+ aria-label="Reset to match branch name"
202
+ disabled={folderFollowsBranch}
203
+ onClick={() => {
204
+ setFolderFollowsBranch(true);
205
+ setFolder(folderNameForBranch(branch));
206
+ }}
207
+ className="h-8 shrink-0 rounded-md border border-ink/15 px-2 text-[11px] text-ink/70 hover:bg-ink/5 disabled:opacity-40"
208
+ >
209
+ ⟳
210
+ </button>
211
+ </span>
212
+ </label>
213
+
214
+ <p className="text-[10px] text-ink/45">
215
+ Uncommitted changes stay in {folderName ? `“${folderName}”` : 'the project'}. A new worktree starts from a
216
+ commit, so nothing half-written comes with it.
217
+ </p>
218
+ </div>
219
+ );
220
+
221
+ return (
222
+ <Modal
223
+ onClose={onClose}
224
+ maxWidthClass="max-w-[min(96vw,880px)]"
225
+ header={<h2 className="text-sm font-semibold text-ink">New worktree</h2>}
226
+ footer={
227
+ <div className="flex w-full items-center gap-2 border-t border-ink/10 px-5 py-3">
228
+ {/* One line that never grows the dialog; the full message is its title. */}
229
+ <p
230
+ className="min-w-0 flex-1 truncate text-[11px] text-error"
231
+ title={failure?.message}
232
+ role={failure ? 'alert' : undefined}
233
+ >
234
+ {failure?.message ?? ''}
235
+ </p>
236
+ <button
237
+ type="button"
238
+ onClick={onClose}
239
+ disabled={creating}
240
+ className="h-8 shrink-0 rounded-md border border-ink/15 px-3 text-xs text-ink/80 hover:bg-ink/5"
241
+ >
242
+ Cancel
243
+ </button>
244
+ <button
245
+ type="button"
246
+ onClick={() => void submit()}
247
+ disabled={!canCreate}
248
+ className="h-8 shrink-0 rounded-md bg-ink px-3 text-xs font-medium text-paper hover:bg-ink/90 disabled:opacity-40"
249
+ >
250
+ {creating ? 'Creating…' : 'Create worktree'}
251
+ </button>
252
+ </div>
253
+ }
254
+ >
255
+ <div className="grid grid-cols-1 gap-6 px-5 py-4 md:grid-cols-2">
256
+ {choiceColumn}
257
+ {fieldColumn}
258
+ </div>
259
+ </Modal>
260
+ );
261
+ }