@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,111 @@
1
+ /**
2
+ * @license
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ /**
7
+ * The code surface's type and chrome, in CodeMirror's own theme format.
8
+ *
9
+ * Type is INHERITED, not restated: the host element carries the font family,
10
+ * size and line box (the zoom control and the editor-font setting both write
11
+ * there), and `.cm-editor` inherits them. That is what keeps a zoom step a pure
12
+ * CSS change — a theme extension carrying a pixel size would have to be
13
+ * reconfigured on every step, and every reconfigure re-creates the editor's
14
+ * plugins (including the tokenizer).
15
+ *
16
+ * Colours are read through `--editor-*` custom properties with the app's
17
+ * palette as the FALLBACK:
18
+ *
19
+ * - `auto` (the default) writes none of them, so every declaration resolves to
20
+ * `--theme-*` and an app theme flip repaints the editor with zero JS — the
21
+ * same property the Shiki decorations rely on.
22
+ * - An explicit editor theme writes them on the surface's own host element
23
+ * (`editorThemeVars`), so the editor paints Dracula while the console around
24
+ * it stays on the app palette. Because the values arrive as CSS, switching
25
+ * between two editor themes is also a repaint: nothing here is reconfigured.
26
+ *
27
+ * The one rule that cannot be a variable is the TOKEN colour. Shiki emits both
28
+ * palettes on every token, and which one paints is decided by the app's variant
29
+ * attribute — except an explicit editor theme tokenizes with ITSELF in both
30
+ * slots, so the two custom properties carry identical values and either one is
31
+ * correct. That is why the dark rule below is safe to state unconditionally.
32
+ */
33
+
34
+ import type { Extension } from '@codemirror/state';
35
+ import { EditorView } from '@codemirror/view';
36
+
37
+ /** The surface's own CSS. Everything here is structural, never per-layout. */
38
+ export function surfaceTheme(): Extension {
39
+ return EditorView.theme({
40
+ '&': {
41
+ height: '100%',
42
+ backgroundColor: 'var(--editor-bg, transparent)',
43
+ color: 'var(--editor-fg, var(--theme-ink))',
44
+ fontSize: 'inherit',
45
+ fontFamily: 'inherit',
46
+ lineHeight: 'inherit',
47
+ },
48
+ '&.cm-focused': { outline: 'none' },
49
+ '.cm-scroller': {
50
+ overflow: 'auto',
51
+ fontFamily: 'inherit',
52
+ fontSize: 'inherit',
53
+ lineHeight: 'inherit',
54
+ },
55
+ '.cm-content': {
56
+ // CM6 reads these two back as the document's padding, which is why the
57
+ // breathing room belongs here and not on a wrapper: a scroll container's
58
+ // own padding is not part of its scrollable overflow.
59
+ paddingTop: 'var(--surface-pad, 16px)',
60
+ paddingBottom: 'var(--surface-pad, 16px)',
61
+ caretColor: 'var(--editor-cursor, var(--theme-ink))',
62
+ },
63
+ '.cm-line': { padding: '0 var(--surface-pad, 16px)' },
64
+ // The gutter is CM6's own (lineNumbers()), styled to the panel's band. An
65
+ // explicit editor theme paints it in the theme's own surface, which is what
66
+ // VS Code does for the 46 themes that ship no `editorGutter.background`.
67
+ '.cm-gutters': {
68
+ backgroundColor: 'var(--editor-bg, var(--theme-canvas))',
69
+ color: 'var(--editor-gutter-fg, color-mix(in srgb, var(--theme-ink) 30%, transparent))',
70
+ border: 'none',
71
+ borderRight: '1px solid var(--editor-border, color-mix(in srgb, var(--theme-ink) 10%, transparent))',
72
+ fontFamily: 'inherit',
73
+ fontSize: 'inherit',
74
+ lineHeight: 'inherit',
75
+ },
76
+ '.cm-lineNumbers .cm-gutterElement': {
77
+ padding: '0 12px 0 16px',
78
+ minWidth: '1.5rem',
79
+ textAlign: 'right',
80
+ },
81
+ // The selection is drawn by `drawSelection()`; the native one is left to
82
+ // the browser when the editor is not focused.
83
+ '.cm-selectionBackground, &.cm-focused .cm-selectionBackground': {
84
+ backgroundColor: 'var(--editor-selection, color-mix(in srgb, var(--theme-ink) 20%, transparent))',
85
+ },
86
+ '.cm-cursor, .cm-dropCursor': {
87
+ borderLeftColor: 'var(--editor-cursor, var(--theme-ink))',
88
+ },
89
+ // A find match, painted by the decoration field (`marks.ts`). The geometry
90
+ // rules are the ones the old markup layer needed: horizontal edges only, or
91
+ // a match spanning several tokens paints a seam between them.
92
+ '.cm-content .find-match': {
93
+ color: 'inherit',
94
+ backgroundColor: 'var(--editor-find, color-mix(in srgb, var(--theme-warning) 32%, transparent))',
95
+ },
96
+ '.cm-content .find-match[data-find-current]': {
97
+ backgroundColor:
98
+ 'var(--editor-find-current, color-mix(in srgb, var(--theme-warning) 55%, transparent))',
99
+ borderTop: '1px solid color-mix(in srgb, var(--theme-warning) 75%, transparent)',
100
+ borderBottom: '1px solid color-mix(in srgb, var(--theme-warning) 75%, transparent)',
101
+ },
102
+ // Shiki tokens. The colour itself comes from the custom properties the
103
+ // decoration carries; `tailwind.css` holds the same pair for every other
104
+ // surface, and this block is the editor's copy of it. The `&`-less form
105
+ // below is CM6's base-class prefix, and the second rule is why an app theme
106
+ // flip repaints tokens with no re-tokenize.
107
+ '.cm-content .shiki-tok': { color: 'var(--shiki-light)' },
108
+ 'html[data-theme-variant="dark"] & .cm-content .shiki-tok': { color: 'var(--shiki-dark)' },
109
+ '.cm-placeholder': { color: 'color-mix(in srgb, var(--editor-fg, var(--theme-ink)) 35%, transparent)' },
110
+ });
111
+ }
@@ -0,0 +1,148 @@
1
+ /**
2
+ * @license
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ /**
7
+ * The two width-mode buttons, driven through the REAL session-state provider.
8
+ *
9
+ * Three properties only a mount can see, and each fails silently:
10
+ *
11
+ * - The two toolbars draw the SAME state. `useSessionState` hands every caller
12
+ * a private copy, so a version reading it would leave the diff toolbar
13
+ * showing `normal` after the editor toolbar entered compact view — two
14
+ * buttons disagreeing about one layout.
15
+ * - Each button reports `aria-pressed` for its own mode and nothing else, so
16
+ * the pair reads as a mode selection rather than as two unrelated switches.
17
+ * - Clicking the ACTIVE mode returns to `normal`, which is the only way back to
18
+ * a layout with the chat on screen without hunting for the other button.
19
+ *
20
+ * Rendered with `h()` against happy-dom, over a stubbed `fetch` that answers
21
+ * the session-state blob (the provider loads it before anything is ready).
22
+ */
23
+
24
+ import { afterAll, afterEach, beforeAll, describe, expect, test } from 'bun:test';
25
+ import { Window } from 'happy-dom';
26
+ import { h, render } from 'preact';
27
+ import { act } from 'preact/test-utils';
28
+ import { EditorViewButtons } from '@/client/components/common/editor-view-buttons';
29
+ import { SessionStateProvider } from '@/client/components/common/session-state-provider/index';
30
+ import { forgetSession } from '@/shared/lib/workspace/session-state/store';
31
+ import { installDomGlobals, restoreDomGlobals } from '@/test-support/pristine-globals';
32
+
33
+ const SESSION = 'new-editor-view-buttons';
34
+ /** The runner's own fetch, reached through `Bun` so a leaked stub cannot be mistaken for it. */
35
+ const nativeFetch = Bun.fetch;
36
+
37
+ let container: HTMLElement;
38
+
39
+ beforeAll(() => {
40
+ installDomGlobals(new Window({ url: 'http://localhost' }));
41
+ });
42
+
43
+ afterEach(() => {
44
+ if (container) {
45
+ render(null, container);
46
+ container.remove();
47
+ }
48
+ forgetSession(SESSION);
49
+ (globalThis as unknown as Record<string, unknown>).fetch = nativeFetch;
50
+ });
51
+
52
+ afterAll(() => {
53
+ restoreDomGlobals();
54
+ });
55
+
56
+ /**
57
+ * Mount TWO button pairs under one provider — the editor toolbar's and the diff
58
+ * toolbar's — because "they share one slot" is the claim, and a single pair
59
+ * could pass with private copies.
60
+ */
61
+ async function mount(stored: Record<string, unknown> = {}): Promise<void> {
62
+ const target = globalThis as unknown as Record<string, unknown>;
63
+ target.fetch = async () => {
64
+ const json = (body: unknown) =>
65
+ new Response(JSON.stringify(body), { status: 200, headers: { 'content-type': 'application/json' } });
66
+ return json({ sessionId: SESSION, state: stored });
67
+ };
68
+
69
+ container = document.createElement('div');
70
+ document.body.appendChild(container);
71
+ await act(async () => {
72
+ render(
73
+ h(SessionStateProvider, {
74
+ sessionId: SESSION,
75
+ children: h(
76
+ 'div',
77
+ null,
78
+ h('div', { id: 'editor-toolbar' }, h(EditorViewButtons, { size: 14 })),
79
+ h('div', { id: 'diff-toolbar' }, h(EditorViewButtons, { size: 13 })),
80
+ ),
81
+ }),
82
+ container,
83
+ );
84
+ });
85
+ // The provider's blob load settles across a tick.
86
+ for (let i = 0; i < 10; i += 1) await act(async () => {});
87
+ }
88
+
89
+ /** The named button inside one of the two toolbar hosts. */
90
+ function button(toolbar: 'editor-toolbar' | 'diff-toolbar', name: string): HTMLButtonElement {
91
+ const host = container.querySelector(`#${toolbar}`);
92
+ const found = [...(host?.querySelectorAll('button') ?? [])].find(
93
+ (el) => el.getAttribute('aria-label') === name,
94
+ );
95
+ if (!found) throw new Error(`no "${name}" button in #${toolbar}`);
96
+ return found as HTMLButtonElement;
97
+ }
98
+
99
+ async function click(el: HTMLButtonElement): Promise<void> {
100
+ await act(async () => {
101
+ el.dispatchEvent(new window.MouseEvent('click', { bubbles: true }) as never);
102
+ });
103
+ }
104
+
105
+ describe('EditorViewButtons', () => {
106
+ test('both toolbars offer both modes, and neither is pressed by default', async () => {
107
+ await mount();
108
+
109
+ for (const toolbar of ['editor-toolbar', 'diff-toolbar'] as const) {
110
+ expect(button(toolbar, 'Compact view').getAttribute('aria-pressed')).toBe('false');
111
+ expect(button(toolbar, 'Full view').getAttribute('aria-pressed')).toBe('false');
112
+ }
113
+ });
114
+
115
+ test('a click enters the mode, and the OTHER toolbar shows the same state', async () => {
116
+ await mount();
117
+ await click(button('editor-toolbar', 'Compact view'));
118
+
119
+ expect(button('editor-toolbar', 'Compact view').getAttribute('aria-pressed')).toBe('true');
120
+ expect(button('diff-toolbar', 'Compact view').getAttribute('aria-pressed')).toBe('true');
121
+ expect(button('diff-toolbar', 'Full view').getAttribute('aria-pressed')).toBe('false');
122
+ });
123
+
124
+ test('the two modes are exclusive: entering one clears the other', async () => {
125
+ await mount();
126
+ await click(button('editor-toolbar', 'Compact view'));
127
+ await click(button('diff-toolbar', 'Full view'));
128
+
129
+ expect(button('editor-toolbar', 'Compact view').getAttribute('aria-pressed')).toBe('false');
130
+ expect(button('editor-toolbar', 'Full view').getAttribute('aria-pressed')).toBe('true');
131
+ });
132
+
133
+ test('clicking the active mode returns to the ordinary layout', async () => {
134
+ await mount();
135
+ await click(button('editor-toolbar', 'Full view'));
136
+ await click(button('editor-toolbar', 'Full view'));
137
+
138
+ expect(button('editor-toolbar', 'Full view').getAttribute('aria-pressed')).toBe('false');
139
+ expect(button('editor-toolbar', 'Compact view').getAttribute('aria-pressed')).toBe('false');
140
+ });
141
+
142
+ test('a mode stored for this session is restored on mount', async () => {
143
+ await mount({ 'layout.editorViewMode': 'full' });
144
+
145
+ expect(button('editor-toolbar', 'Full view').getAttribute('aria-pressed')).toBe('true');
146
+ expect(button('diff-toolbar', 'Full view').getAttribute('aria-pressed')).toBe('true');
147
+ });
148
+ });
@@ -0,0 +1,109 @@
1
+ import { MessageSquareOff, PanelLeftClose } from 'lucide-preact';
2
+ import type { LucideIcon } from 'lucide-preact';
3
+ import { useSharedSessionState } from '@/client/hooks/workspace/session-state';
4
+ import type { ToolbarOverflowAction } from '@/client/components/common/toolbar-row';
5
+ import {
6
+ DEFAULT_EDITOR_VIEW_MODE,
7
+ EDITOR_VIEW_MODE_KEY,
8
+ isEditorViewMode,
9
+ toggledEditorViewMode,
10
+ type EditorViewMode,
11
+ type EditorViewModeTarget,
12
+ } from '@/shared/lib/workspace/editor-view-mode';
13
+
14
+ /**
15
+ * The two width modes, as data.
16
+ *
17
+ * Each is a row so the buttons, their labels and their icons cannot drift
18
+ * apart: the label states exactly which columns disappear, because "Compact"
19
+ * and "Full" are words that could mean anything, and the icons are DIFFERENT
20
+ * SHAPES rather than two spellings of the same one — the third width control
21
+ * beside them (full SCREEN, which covers the right panel too) keeps its own
22
+ * `Maximize` glyph.
23
+ */
24
+ export const EDITOR_VIEW_MODES: Array<{
25
+ target: EditorViewModeTarget;
26
+ label: string;
27
+ hint: string;
28
+ Icon: LucideIcon;
29
+ }> = [
30
+ {
31
+ target: 'compact',
32
+ label: 'Compact view',
33
+ hint: 'Editor takes the chat width — chat hidden, sidebar and right panel kept',
34
+ Icon: MessageSquareOff,
35
+ },
36
+ {
37
+ target: 'full',
38
+ label: 'Full view',
39
+ hint: 'Editor takes the chat and sidebar width — chat and sidebar hidden, right panel kept',
40
+ Icon: PanelLeftClose,
41
+ },
42
+ ];
43
+
44
+ /** The mode in force, and the setter the buttons share. */
45
+ export function useEditorViewMode(): { mode: EditorViewMode; setMode: (next: EditorViewMode) => void } {
46
+ const [stored, setMode] = useSharedSessionState<EditorViewMode>(EDITOR_VIEW_MODE_KEY, DEFAULT_EDITOR_VIEW_MODE);
47
+ return { mode: isEditorViewMode(stored) ? stored : DEFAULT_EDITOR_VIEW_MODE, setMode };
48
+ }
49
+
50
+ /**
51
+ * The same two modes as `⋯` menu rows, for a toolbar too narrow to draw them.
52
+ *
53
+ * A hook rather than a plain function because the menu row has to CLICK the same
54
+ * slot the buttons write, and a row built from a stale mode would enter the mode
55
+ * already in force (which the toggle reads as "leave it").
56
+ */
57
+ export function useEditorViewActions(): ToolbarOverflowAction[] {
58
+ const { mode, setMode } = useEditorViewMode();
59
+ return EDITOR_VIEW_MODES.map(({ target, label, Icon }) => ({
60
+ // A menu row has no hover title, so the sentence that explains the mode is
61
+ // part of what the row says rather than lost with the button's tooltip.
62
+ label,
63
+ icon: Icon,
64
+ active: mode === target,
65
+ onSelect: () => setMode(toggledEditorViewMode(mode, target)),
66
+ }));
67
+ }
68
+
69
+ interface EditorViewButtonsProps {
70
+ /** Glyph size: the editor toolbar draws 14px, the diff toolbar 13px. */
71
+ size?: number;
72
+ }
73
+
74
+ /**
75
+ * The two width-mode buttons, drawn by BOTH the editor and the diff toolbar.
76
+ *
77
+ * One component because the two toolbars are the same surface on two tab kinds:
78
+ * a mode entered from the diff and a mode entered from a file must be the same
79
+ * mode, and a second copy of these rules is how the diff ends up offering a
80
+ * width the layout does not implement. The state is the shared session slot
81
+ * (see `useEditorViewLayout`) so the layout collapses the columns on the same
82
+ * render.
83
+ */
84
+ export function EditorViewButtons({ size = 14 }: EditorViewButtonsProps) {
85
+ const { mode, setMode } = useEditorViewMode();
86
+
87
+ return (
88
+ <>
89
+ {EDITOR_VIEW_MODES.map(({ target, label, hint, Icon }) => {
90
+ const active = mode === target;
91
+ return (
92
+ <button
93
+ key={target}
94
+ type="button"
95
+ onClick={() => setMode(toggledEditorViewMode(mode, target))}
96
+ className={`flex items-center justify-center p-1.5 rounded transition-colors cursor-pointer flex-shrink-0 ${
97
+ active ? 'bg-ink/10 text-ink' : 'text-ink/50 hover:text-ink hover:bg-ink/5'
98
+ }`}
99
+ title={hint}
100
+ aria-label={label}
101
+ aria-pressed={active}
102
+ >
103
+ <Icon size={size} />
104
+ </button>
105
+ );
106
+ })}
107
+ </>
108
+ );
109
+ }
@@ -6,6 +6,8 @@ import { ContextUsageButton } from '@/client/components/common/context-usage-but
6
6
  import { HeaderPanelButtons } from '@/client/components/common/header-panel-buttons';
7
7
  import { useHeaderEntries } from '@/client/components/common/header-panel-buttons/entries';
8
8
  import { HeaderMoreMenu } from '@/client/components/common/header-controls/MoreMenu';
9
+ import { WindowControls } from '@/client/components/common/window-controls';
10
+ import { drawsWindowControls } from '@/client/lib/desktop/titlebar';
9
11
  import { useAuthEnabled, useAuthState } from '@/client/hooks/ui/auth';
10
12
  import { useViewportAtLeast } from '@/client/hooks/ui/media-query';
11
13
  import { withChord } from '@/client/lib/ui/with-chord';
@@ -111,7 +113,17 @@ export function HeaderControls({
111
113
  const headerEntries = useHeaderEntries(layout);
112
114
 
113
115
  return (
114
- <div className="flex items-center space-x-1">
116
+ // `titlebar-no-drag` belongs HERE, on the cluster's own root, not on each
117
+ // call site. `app-region` INHERITS (WebView2's own spec says so, and
118
+ // Chromium 152 began enforcing it), so a header that marks itself a drag
119
+ // region makes every descendant a drag handle unless something below it
120
+ // says otherwise — a control inside one is unclickable, silently. The
121
+ // desktop navbar wrapped this cluster in a `titlebar-no-drag` div and the
122
+ // PHONE's header did not, so on Windows every control in a phone-width
123
+ // window (reload, context, sign out, the right panel, `⋯`) swallowed its
124
+ // own click. Stating it on the cluster is what makes the next layout that
125
+ // draws it correct by construction.
126
+ <div className="flex items-center space-x-1 titlebar-no-drag">
115
127
  {layout === 'desktop' && <PWAInstallButton />}
116
128
  <HeaderPanelButtons layout={layout} entries={headerEntries.inline} />
117
129
  {/* The cluster's ORDER is fixed; what changes on a narrow phone is that
@@ -169,6 +181,17 @@ export function HeaderControls({
169
181
  <Smartphone size={size} />
170
182
  </button>
171
183
  )}
184
+ {/* The Windows and Linux shells draw no caption buttons of their own —
185
+ their webview is the whole window — so the PAGE owns minimise,
186
+ maximise and close. This lives in the shared cluster rather than in
187
+ the desktop navbar for a measured reason: a desktop shell window can
188
+ be NARROWER than the layout breakpoint (`App` switches to the mobile
189
+ layout below 768px, and all three shells allow a 720px window), and
190
+ the phone header drew no window controls at all. A user who shrank
191
+ the window lost close/minimise/maximise entirely, with no title bar
192
+ behind them to fall back on. macOS never reaches this branch: its
193
+ traffic lights are the system's, so `drawsWindowControls` is false. */}
194
+ {drawsWindowControls() && <WindowControls />}
172
195
  </div>
173
196
  );
174
197
  }