@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.
- package/README.md +7 -0
- package/dist/client/index.html +1 -1
- package/dist/client/index.js +403 -384
- package/dist/client/static/js/{index-0wvbca0b.js → index-bpptfkz2.js} +905 -875
- package/dist/client/static/js/index-k315p1e8.css +1 -0
- package/package.json +10 -1
- package/src/client/components/common/code-surface/documents.test.ts +120 -0
- package/src/client/components/common/code-surface/documents.ts +201 -0
- package/src/client/components/common/code-surface/extensions.test.ts +110 -0
- package/src/client/components/common/code-surface/extensions.ts +162 -0
- package/src/client/components/common/code-surface/handle.ts +102 -0
- package/src/client/components/common/code-surface/index.tsx +253 -314
- package/src/client/components/common/code-surface/keymap.test.ts +133 -0
- package/src/client/components/common/code-surface/keymap.ts +267 -0
- package/src/client/components/common/code-surface/marks.ts +74 -0
- package/src/client/components/common/code-surface/shiki.ts +234 -0
- package/src/client/components/common/code-surface/sync.test.ts +185 -0
- package/src/client/components/common/code-surface/sync.ts +58 -0
- package/src/client/components/common/code-surface/theme.test.ts +202 -0
- package/src/client/components/common/code-surface/theme.ts +111 -0
- package/src/client/components/common/editor-view-buttons/index.test.tsx +148 -0
- package/src/client/components/common/editor-view-buttons/index.tsx +109 -0
- package/src/client/components/common/header-controls/index.tsx +24 -1
- package/src/client/components/common/header-controls/shell-chrome.test.tsx +272 -0
- package/src/client/components/common/shell-update-button/Modal.tsx +163 -0
- package/src/client/components/common/shell-update-button/index.test.tsx +251 -0
- package/src/client/components/common/shell-update-button/index.tsx +125 -0
- package/src/client/components/common/toolbar-row/index.test.tsx +166 -0
- package/src/client/components/common/toolbar-row/index.tsx +189 -0
- package/src/client/components/common/workspace-options-menu/index.tsx +19 -1
- package/src/client/components/layout/desktop-layout/TopNavbar.tsx +5 -6
- package/src/client/components/layout/desktop-layout/WorkspacePanels.tsx +41 -78
- package/src/client/components/layout/desktop-layout/index.tsx +55 -69
- package/src/client/components/layout/desktop-layout/resizer/index.tsx +6 -3
- package/src/client/components/layout/session-sidebar/CategoryHeader.tsx +24 -0
- package/src/client/components/layout/session-sidebar/CategoryHeader.worktree.test.tsx +126 -0
- package/src/client/components/layout/session-sidebar/CategoryItem.tsx +4 -0
- package/src/client/components/layout/session-sidebar/Footer.tsx +8 -15
- package/src/client/components/layout/session-sidebar/SessionItem.tsx +12 -0
- package/src/client/components/layout/session-sidebar/SessionList.tsx +4 -0
- package/src/client/components/layout/session-sidebar/SessionsView.tsx +24 -0
- package/src/client/components/layout/session-sidebar/WorktreeBadge.test.ts +48 -0
- package/src/client/components/layout/session-sidebar/WorktreeBadge.tsx +55 -0
- package/src/client/components/mobile/LayoutWrapper.tsx +23 -16
- package/src/client/components/mobile/RightSidebar.tsx +21 -10
- package/src/client/components/mobile/SessionSidebar.tsx +8 -0
- package/src/client/components/mobile/header-titlebar.test.tsx +172 -0
- package/src/client/components/mobile/mobile-main-view/Header.tsx +10 -1
- package/src/client/components/mobile/mobile-right-sidebar/FullDiff.tsx +3 -0
- package/src/client/components/mobile/mobile-right-sidebar/FullEditor.tsx +63 -38
- package/src/client/components/mobile/mobile-right-sidebar/OutlineButton.test.tsx +137 -0
- package/src/client/components/mobile/mobile-right-sidebar/OutlineButton.tsx +14 -3
- package/src/client/components/mobile/mobile-session-sidebar/Footer.tsx +9 -21
- package/src/client/components/mobile/mobile-session-sidebar/Header.tsx +6 -2
- package/src/client/components/mobile/mobile-session-sidebar/Item.tsx +16 -0
- package/src/client/components/mobile/mobile-session-sidebar/List.tsx +4 -0
- package/src/client/components/mobile/mobile-session-sidebar/SessionRow.tsx +5 -0
- package/src/client/components/mobile/mobile-session-sidebar/WorktreeDialog.tsx +49 -0
- package/src/client/components/mobile/worktree-session.ts +51 -0
- package/src/client/components/settings/Modal.tsx +8 -0
- package/src/client/components/settings/categories/AppearanceSettings.tsx +3 -0
- package/src/client/components/settings/categories/ProjectSettings.tsx +22 -8
- package/src/client/components/settings/categories/appearance-settings/EditorThemeSection.test.tsx +167 -0
- package/src/client/components/settings/categories/appearance-settings/EditorThemeSection.tsx +184 -0
- package/src/client/components/settings/categories/project-settings/WorktreeSettings.tsx +146 -0
- package/src/client/components/settings/categories/project-settings/worktree-row.test.ts +83 -0
- package/src/client/components/settings/categories/project-settings/worktree-row.ts +47 -0
- package/src/client/components/workspace/chat-timeline/EmptyWorkspacePrompt.tsx +36 -55
- package/src/client/components/workspace/chat-timeline/PendingChatView.tsx +8 -0
- package/src/client/components/workspace/chat-timeline/SystemNotice.tsx +9 -4
- package/src/client/components/workspace/chat-timeline/TaskResultContent.tsx +9 -7
- package/src/client/components/workspace/chat-timeline/index.tsx +5 -0
- package/src/client/components/workspace/chat-timeline/target-picker/WorkspacePicker.test.tsx +132 -0
- package/src/client/components/workspace/chat-timeline/target-picker/WorkspacePicker.tsx +120 -0
- package/src/client/components/workspace/chat-timeline/target-picker/WorktreePicker.test.tsx +319 -0
- package/src/client/components/workspace/chat-timeline/target-picker/WorktreePicker.tsx +285 -0
- package/src/client/components/workspace/chat-timeline/tool-renderers/ask-panel/index.test.ts +2 -0
- package/src/client/components/workspace/chat-timeline/tool-renderers/hashline-patch/index.tsx +34 -29
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/AstEdit.tsx +11 -9
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/Bash.tsx +10 -7
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/Edit.tsx +25 -8
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/Eval.test.tsx +146 -0
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/Eval.tsx +32 -17
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/FileMatches.test.ts +114 -0
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/FileMatches.tsx +111 -0
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/Read.tsx +7 -7
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/SearchTool.tsx +9 -59
- package/src/client/components/workspace/chat-timeline/tool-renderers/shared/ArtifactBody.tsx +2 -7
- package/src/client/components/workspace/chat-timeline/tool-renderers/shared/DiffView.test.ts +27 -28
- package/src/client/components/workspace/chat-timeline/tool-renderers/shared/DiffView.tsx +39 -26
- package/src/client/components/workspace/chat-timeline/tool-renderers/shared/ExcerptCode.tsx +8 -8
- package/src/client/components/workspace/chat-timeline/tool-renderers/shared/HighlightedCode.tsx +35 -0
- package/src/client/components/workspace/chat-timeline/tool-renderers/shared/JsonCodeBlock.tsx +4 -4
- package/src/client/components/workspace/diff-panel/MergeDiffView.tsx +179 -0
- package/src/client/components/workspace/diff-panel/ThemedMergeDiffView.tsx +24 -0
- package/src/client/components/workspace/diff-panel/Toolbar.tsx +224 -186
- package/src/client/components/workspace/diff-panel/ToolbarSegments.tsx +98 -0
- package/src/client/components/workspace/diff-panel/index.tsx +76 -145
- package/src/client/components/workspace/diff-panel/merge.ts +315 -0
- package/src/client/components/workspace/editor/Body.tsx +4 -7
- package/src/client/components/workspace/editor/CommandPalette.tsx +2 -2
- package/src/client/components/workspace/editor/FileDocument.tsx +58 -63
- package/src/client/components/workspace/editor/FilePane.tsx +4 -7
- package/src/client/components/workspace/editor/FindWidget.tsx +7 -2
- package/src/client/components/workspace/editor/Toolbar.tsx +220 -123
- package/src/client/components/workspace/editor/index.tsx +7 -9
- package/src/client/components/workspace/file-explorer/ContextMenu.tsx +2 -1
- package/src/client/components/workspace/file-explorer/Modals.tsx +40 -12
- package/src/client/components/workspace/file-explorer/OutlineSection.test.tsx +18 -0
- package/src/client/components/workspace/file-explorer/OutlineSection.tsx +10 -3
- package/src/client/components/workspace/file-explorer/RootMenu.tsx +14 -5
- package/src/client/components/workspace/file-explorer/Toolbar.tsx +74 -0
- package/src/client/components/workspace/file-explorer/TreeItem.tsx +38 -3
- package/src/client/components/workspace/file-explorer/filter.ts +47 -0
- package/src/client/components/workspace/file-explorer/index.tsx +77 -64
- package/src/client/components/workspace/file-explorer/local-edit.test.ts +135 -0
- package/src/client/components/workspace/search-panel/Body.tsx +1 -1
- package/src/client/components/workspace/worktree-modal/index.test.tsx +118 -0
- package/src/client/components/workspace/worktree-modal/index.tsx +261 -0
- package/src/client/hooks/chat/omp/reconnect.test.ts +338 -0
- package/src/client/hooks/chat/omp/reconnect.ts +187 -0
- package/src/client/hooks/chat/omp/run-liveness.test.ts +71 -0
- package/src/client/hooks/chat/omp/run-liveness.ts +65 -0
- package/src/client/hooks/chat/omp/session-list.activity.test.ts +114 -0
- package/src/client/hooks/chat/omp/session-list.test-util.ts +164 -0
- package/src/client/hooks/chat/omp/session-list.test.ts +30 -150
- package/src/client/hooks/chat/omp/session-list.tsx +69 -9
- package/src/client/hooks/chat/timeline/generating.ts +19 -2
- package/src/client/hooks/chat/timeline/index.ts +27 -28
- package/src/client/hooks/chat/timeline/lost-run.test.ts +329 -0
- package/src/client/hooks/chat/timeline/lost-run.ts +177 -0
- package/src/client/hooks/chat/timeline/send-execute.test.ts +2 -0
- package/src/client/hooks/chat/timeline/send-steer.test.ts +2 -0
- package/src/client/hooks/chat/timeline/send.test.ts +2 -0
- package/src/client/hooks/chat/timeline/send.ts +11 -1
- package/src/client/hooks/chat/timeline/session-load.test-util.ts +205 -0
- package/src/client/hooks/chat/timeline/session-load.test.ts +61 -162
- package/src/client/hooks/chat/timeline/session-load.ts +69 -37
- package/src/client/hooks/chat/timeline/session-switch.test.ts +65 -0
- package/src/client/hooks/chat/timeline/session-switch.ts +63 -0
- package/src/client/hooks/chat/timeline/spawn-target.ts +52 -0
- package/src/client/hooks/chat/timeline/tool-open.test.ts +74 -0
- package/src/client/hooks/chat/timeline/tool-open.ts +52 -8
- package/src/client/hooks/code/deferred-highlight.test.ts +182 -0
- package/src/client/hooks/code/deferred-highlight.ts +129 -0
- package/src/client/hooks/editor/find-state.ts +1 -1
- package/src/client/hooks/editor/theme.test.ts +206 -0
- package/src/client/hooks/editor/theme.ts +82 -0
- package/src/client/hooks/editor/use-editor-drop.test.ts +101 -0
- package/src/client/hooks/editor/use-editor-drop.ts +67 -14
- package/src/client/hooks/editor/use-editor-find.ts +7 -7
- package/src/client/hooks/editor/use-outline-view.test.ts +182 -0
- package/src/client/hooks/editor/use-outline-view.ts +94 -7
- package/src/client/hooks/editor/use-scroll-restore.test.ts +55 -2
- package/src/client/hooks/editor/use-scroll-restore.ts +124 -4
- package/src/client/hooks/editor/use-search-reveal.ts +1 -1
- package/src/client/hooks/ui/long-press.test.ts +174 -0
- package/src/client/hooks/ui/long-press.ts +112 -0
- package/src/client/hooks/ui/settings-modal.ts +56 -0
- package/src/client/hooks/ui/updates/shell-download.test.ts +75 -0
- package/src/client/hooks/ui/updates/shell-download.ts +153 -28
- package/src/client/hooks/ui/updates/shell-offer.test.ts +213 -0
- package/src/client/hooks/ui/updates/shell-update.test.ts +140 -0
- package/src/client/hooks/ui/updates/shell-update.ts +119 -0
- package/src/client/hooks/workspace/diff-file.ts +29 -10
- package/src/client/hooks/workspace/diff-git-actions.ts +147 -0
- package/src/client/hooks/workspace/editor-view.ts +61 -0
- package/src/client/hooks/workspace/file-create.ts +41 -11
- package/src/client/hooks/workspace/file-listing.ts +56 -1
- package/src/client/hooks/workspace/file-tabs.test.ts +62 -5
- package/src/client/hooks/workspace/file-tabs.ts +24 -11
- package/src/client/hooks/workspace/file-tree-actions.ts +37 -5
- package/src/client/hooks/workspace/navbar-session-actions.ts +72 -0
- package/src/client/hooks/workspace/worktree/index.ts +165 -0
- package/src/client/lib/desktop/bridge.ts +45 -0
- package/src/client/lib/search-reveal.ts +1 -1
- package/src/client/main.tsx +5 -1
- package/src/client/tailwind.css +56 -8
- package/src/server/index.ts +7 -2
- package/src/server/lib/auth/guard.test.ts +28 -0
- package/src/server/lib/auth/guard.ts +10 -0
- package/src/server/lib/bundler/titlebar-css.test.ts +42 -1
- package/src/server/lib/db/schema.ts +4 -0
- package/src/server/lib/db/worktree-registry.server.ts +91 -0
- package/src/server/lib/fs/canonical.ts +52 -0
- package/src/server/lib/fs/relative-path.ts +48 -0
- package/src/server/lib/fs/root-worktree.test.ts +116 -0
- package/src/server/lib/fs/root.ts +39 -10
- package/src/server/lib/fs/worktree/carry.ts +134 -0
- package/src/server/lib/fs/worktree/create.test.ts +249 -0
- package/src/server/lib/fs/worktree/create.ts +262 -0
- package/src/server/lib/fs/worktree/errors.ts +43 -0
- package/src/server/lib/fs/worktree/list.test.ts +207 -0
- package/src/server/lib/fs/worktree/list.ts +142 -0
- package/src/server/lib/fs/worktree/location.test.ts +206 -0
- package/src/server/lib/fs/worktree/location.ts +114 -0
- package/src/server/lib/fs/worktree/project-id.ts +53 -0
- package/src/server/lib/fs/worktree/remove.test.ts +242 -0
- package/src/server/lib/fs/worktree/remove.ts +152 -0
- package/src/server/lib/fs/worktree/settings.server.ts +56 -0
- package/src/server/lib/fs/worktree/topology.ts +142 -0
- package/src/server/lib/lifecycle/claim-port.test.ts +156 -0
- package/src/server/lib/lifecycle/port-guard.test.ts +20 -164
- package/src/server/lib/lifecycle/port-guard.ts +39 -7
- package/src/server/lib/lifecycle/probe-health.test.ts +166 -0
- package/src/server/lib/lifecycle/probe.test.ts +77 -164
- package/src/server/lib/lifecycle/probe.ts +121 -13
- package/src/server/lib/omp/rpc/frame-fold-signals.test.ts +112 -0
- package/src/server/lib/omp/rpc/frame-fold.ts +10 -5
- package/src/server/lib/omp/session/sidebar-activity.server.test.ts +162 -0
- package/src/server/lib/omp/session/sidebar-activity.server.ts +70 -0
- package/src/server/lib/omp/session/sidebar-data.canonical.test.ts +155 -0
- package/src/server/lib/omp/session/sidebar-data.server.ts +28 -1
- package/src/server/lib/omp/session/telemetry/compute.ts +13 -1
- package/src/server/lib/omp/session/telemetry.test.ts +33 -0
- package/src/server/lib/panels/local-edits.server.ts +33 -0
- package/src/server/lib/panels/sync.fixture.test-util.ts +92 -0
- package/src/server/lib/panels/sync.local-edits.test.ts +150 -0
- package/src/server/lib/panels/sync.server.test.ts +125 -147
- package/src/server/lib/panels/sync.server.ts +153 -48
- package/src/server/lib/realtime/signals.server.ts +10 -0
- package/src/server/lib/realtime/topics/catalog.server.ts +4 -1
- package/src/server/lib/realtime/topics.server.ts +16 -0
- package/src/server/lib/updates/shell-assets.test.ts +114 -0
- package/src/server/lib/updates/shell-assets.ts +200 -0
- package/src/server/lib/updates/shell-cache.ts +105 -0
- package/src/server/lib/updates/shell-pages.test.ts +187 -0
- package/src/server/lib/updates/shell-pages.ts +157 -0
- package/src/server/lib/updates/shell-release-desktop.test.ts +166 -0
- package/src/server/lib/updates/shell-release.test.ts +59 -71
- package/src/server/lib/updates/shell-release.ts +178 -198
- package/src/server/routes/fs/action-create.test.ts +18 -13
- package/src/server/routes/fs/action-nested.test.ts +184 -0
- package/src/server/routes/fs/action.ts +47 -16
- package/src/server/routes/fs/git-merge.test.ts +11 -1
- package/src/server/routes/fs/read.test.ts +24 -7
- package/src/server/routes/fs/scope.test.ts +15 -10
- package/src/server/routes/fs/scope.ts +3 -14
- package/src/server/routes/index.ts +2 -0
- package/src/server/routes/updates/android.ts +6 -1
- package/src/server/routes/updates/desktop.ts +59 -0
- package/src/server/routes/updates/index.ts +2 -0
- package/src/server/routes/updates/ios.ts +5 -1
- package/src/server/routes/worktrees/create.ts +138 -0
- package/src/server/routes/worktrees/index.ts +25 -0
- package/src/server/routes/worktrees/list.ts +134 -0
- package/src/server/routes/worktrees/remove.ts +102 -0
- package/src/server/routes/worktrees/routes.test.ts +326 -0
- package/src/server/routes/worktrees/shared.ts +90 -0
- package/src/shared/lib/chat/timeline/omp-callbacks.test.ts +36 -0
- package/src/shared/lib/chat/timeline/omp-callbacks.ts +18 -0
- package/src/shared/lib/chat/timeline/run-signals.test.ts +2 -0
- package/src/shared/lib/code/editor/comment-syntax.test.ts +37 -0
- package/src/shared/lib/code/editor/comment-syntax.ts +55 -0
- package/src/shared/lib/code/editor/find-replace.test.ts +134 -0
- package/src/shared/lib/code/editor/{find.ts → find-replace.ts} +23 -107
- package/src/shared/lib/code/editor/{find.test.ts → find-scan.test.ts} +19 -73
- package/src/shared/lib/code/editor/find-scan.ts +177 -0
- package/src/shared/lib/code/editor/keymap.ts +3 -3
- package/src/shared/lib/code/editor/lines.test.ts +7 -55
- package/src/shared/lib/code/editor/lines.ts +8 -48
- package/src/shared/lib/code/editor/marks.test.ts +4 -5
- package/src/shared/lib/code/editor/marks.ts +13 -25
- package/src/shared/lib/code/editor/theme-loader.ts +97 -0
- package/src/shared/lib/code/editor/themes.generated.ts +156 -0
- package/src/shared/lib/code/editor/themes.test.ts +165 -0
- package/src/shared/lib/code/editor/themes.ts +165 -0
- package/src/shared/lib/code/highlight-memo.test.ts +103 -0
- package/src/shared/lib/code/highlight-memo.ts +149 -0
- package/src/shared/lib/code/highlighter-warmup.ts +112 -0
- package/src/shared/lib/code/highlighter.ts +3 -1
- package/src/shared/lib/code/idle.ts +37 -0
- package/src/shared/lib/code/outline/rows.test.ts +96 -0
- package/src/shared/lib/code/outline/rows.ts +94 -0
- package/src/shared/lib/code/syntax-highlight.test.ts +1 -79
- package/src/shared/lib/code/syntax-highlight.ts +2 -3
- package/src/shared/lib/fs/file-kind-classify.test.ts +25 -11
- package/src/shared/lib/fs/file-kind.ts +47 -13
- package/src/shared/lib/fs/file-tree.test.ts +183 -0
- package/src/shared/lib/fs/file-tree.ts +159 -0
- package/src/shared/lib/fs/file-url.test.ts +52 -0
- package/src/shared/lib/fs/file-url.ts +37 -0
- package/src/shared/lib/fs/patch-sides.test.ts +114 -0
- package/src/shared/lib/fs/patch-sides.ts +139 -0
- package/src/shared/lib/fs/paths.test.ts +24 -1
- package/src/shared/lib/fs/paths.ts +17 -0
- package/src/shared/lib/fs/worktree/settings-keys.ts +18 -0
- package/src/shared/lib/omp/session/prewarm.ts +7 -0
- package/src/shared/lib/realtime/protocol.ts +16 -0
- package/src/shared/lib/ui/toolbar-overflow.test.ts +83 -0
- package/src/shared/lib/ui/toolbar-overflow.ts +86 -0
- package/src/shared/lib/updates/shell-arch.ts +54 -0
- package/src/shared/lib/updates/shell-ua.ts +24 -4
- package/src/shared/lib/workspace/active-project.ts +23 -0
- package/src/shared/lib/workspace/editor-view-mode.test.ts +70 -0
- package/src/shared/lib/workspace/editor-view-mode.ts +48 -0
- package/src/shared/lib/workspace/panel-widths.test.ts +30 -0
- package/src/shared/lib/workspace/panel-widths.ts +25 -5
- package/src/shared/lib/workspace/session-state/cache.ts +159 -0
- package/src/shared/lib/workspace/session-state/harness.test-util.ts +55 -0
- package/src/shared/lib/workspace/session-state/merge.ts +100 -0
- package/src/shared/lib/workspace/session-state/store-load.test.ts +206 -0
- package/src/shared/lib/workspace/session-state/store.test.ts +17 -139
- package/src/shared/lib/workspace/session-state/store.ts +92 -130
- package/src/shared/lib/workspace/workspace-modules.test.ts +49 -1
- package/src/shared/lib/workspace/workspace-panel-widths.ts +124 -0
- package/src/shared/lib/workspace/worktree/branch-name.test.ts +95 -0
- package/src/shared/lib/workspace/worktree/branch-name.ts +115 -0
- package/src/shared/lib/workspace/worktree/types.ts +104 -0
- package/src/shared/types/fs.ts +10 -0
- package/src/shared/types/realtime.ts +29 -0
- package/src/shared/types/settings/state.ts +10 -0
- package/src/shared/types/workspace.ts +14 -0
- package/src/test-support/file-explorer-disk.test-util.ts +76 -0
- package/src/test-support/file-explorer.test-util.ts +71 -40
- package/src/test-support/loopback.ts +183 -0
- package/dist/client/static/js/index-nzyjf92m.css +0 -1
- package/src/client/components/common/code-editor/handler.test.ts +0 -274
- package/src/client/components/common/code-editor/handler.ts +0 -175
- package/src/client/components/common/code-editor/index.tsx +0 -314
- package/src/client/components/common/code-surface/measure.ts +0 -148
- package/src/client/components/workspace/diff-panel/SplitView.tsx +0 -209
- package/src/client/components/workspace/diff-panel/UnifiedView.tsx +0 -150
- package/src/client/hooks/editor/scroll-container.test.ts +0 -102
- package/src/client/hooks/editor/scroll-container.ts +0 -26
- package/src/client/hooks/editor/use-code-window.ts +0 -100
- package/src/client/hooks/editor/use-editor-history.test.ts +0 -216
- package/src/client/hooks/editor/use-editor-history.ts +0 -134
- package/src/client/hooks/editor/use-occurrences.test.ts +0 -236
- package/src/client/hooks/editor/use-occurrences.ts +0 -98
- package/src/shared/lib/code/editor/commands.test.ts +0 -155
- package/src/shared/lib/code/editor/commands.ts +0 -260
- package/src/shared/lib/code/editor/edits.test.ts +0 -100
- package/src/shared/lib/code/editor/edits.ts +0 -122
- package/src/shared/lib/code/editor/history.test.ts +0 -118
- package/src/shared/lib/code/editor/history.ts +0 -101
- package/src/shared/lib/code/editor/layers.test.ts +0 -77
- package/src/shared/lib/code/editor/layers.ts +0 -67
- package/src/shared/lib/code/editor/line-edits.test.ts +0 -131
- package/src/shared/lib/code/editor/line-edits.ts +0 -159
- package/src/shared/lib/code/editor/occurrences.test.ts +0 -117
- package/src/shared/lib/code/editor/occurrences.ts +0 -111
- package/src/shared/lib/code/editor/reset-css.test.ts +0 -35
- package/src/shared/lib/code/editor/reset-css.ts +0 -32
- package/src/shared/lib/code/editor/text-commands.test.ts +0 -178
- package/src/shared/lib/code/editor/text-commands.ts +0 -154
- package/src/shared/lib/code/lazy-window.test.ts +0 -161
- package/src/shared/lib/code/lazy-window.ts +0 -156
- package/src/shared/lib/code/windowed-highlight.ts +0 -114
- package/src/shared/lib/fs/diff-parser.test.ts +0 -116
- package/src/shared/lib/fs/diff-parser.ts +0 -213
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* POST /api/worktrees — create a worktree, and POST /api/worktrees/validate —
|
|
8
|
+
* the same plan without touching disk.
|
|
9
|
+
*
|
|
10
|
+
* Both go through `planWorktree`, so the dialog cannot be told "fine" by a check
|
|
11
|
+
* that the create then contradicts: every refusal a create can raise is raised
|
|
12
|
+
* by the validate call first, with the same code and the same field.
|
|
13
|
+
*
|
|
14
|
+
* The body carries the DIALOG's state, never a resolved path: a client that
|
|
15
|
+
* could name the directory could name one outside the project.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { json } from '@/server/lib/remix-compat';
|
|
19
|
+
import type { ActionFunctionArgs } from '@/server/lib/remix-compat';
|
|
20
|
+
import { methodNotAllowed } from '@/server/lib/route-adapter';
|
|
21
|
+
import { getDb } from '@/server/db.server';
|
|
22
|
+
import { emitRealtimeSignal } from '@/server/lib/realtime/signals.server';
|
|
23
|
+
import { getDataDir } from '@/server/lib/lifecycle/paths';
|
|
24
|
+
import { putWorktreeRow, worktreeIdentityFor } from '@/server/lib/db/worktree-registry.server';
|
|
25
|
+
import { asWorktreeFailure } from '@/server/lib/fs/worktree/errors';
|
|
26
|
+
import { createWorktree, planWorktree } from '@/server/lib/fs/worktree/create';
|
|
27
|
+
import { invalidateWorktreeTopology } from '@/server/lib/fs/worktree/topology';
|
|
28
|
+
import { readWorktreeSettings } from '@/server/lib/fs/worktree/settings.server';
|
|
29
|
+
import type { WorktreeValidation } from '@/shared/lib/workspace/worktree/types';
|
|
30
|
+
import { isMock, mockRefusal, resolveWorktreeProject, worktreeFailureResponse } from '@/server/routes/worktrees/shared';
|
|
31
|
+
|
|
32
|
+
interface CreateBody {
|
|
33
|
+
mode?: unknown;
|
|
34
|
+
branch?: unknown;
|
|
35
|
+
worktreeName?: unknown;
|
|
36
|
+
startRef?: unknown;
|
|
37
|
+
carryIgnored?: unknown;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function readBody(body: CreateBody) {
|
|
41
|
+
return {
|
|
42
|
+
mode: body.mode === 'existing' ? ('existing' as const) : ('new' as const),
|
|
43
|
+
branch: typeof body.branch === 'string' ? body.branch : '',
|
|
44
|
+
worktreeName: typeof body.worktreeName === 'string' ? body.worktreeName : '',
|
|
45
|
+
startRef: typeof body.startRef === 'string' ? body.startRef : undefined,
|
|
46
|
+
carryIgnored: typeof body.carryIgnored === 'boolean' ? body.carryIgnored : undefined,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function action({ request }: ActionFunctionArgs) {
|
|
51
|
+
if (request.method !== 'POST') return methodNotAllowed({ request, params: {} });
|
|
52
|
+
if (isMock()) return mockRefusal();
|
|
53
|
+
|
|
54
|
+
const url = new URL(request.url);
|
|
55
|
+
const resolved = await resolveWorktreeProject(url.searchParams.get('root'), url.searchParams.get('folderId'));
|
|
56
|
+
if (resolved.error) return resolved.error;
|
|
57
|
+
const projectPath = resolved.root as string;
|
|
58
|
+
|
|
59
|
+
const body = (await request.json().catch(() => ({}))) as CreateBody;
|
|
60
|
+
const parsed = readBody(body);
|
|
61
|
+
const settings = await readWorktreeSettings();
|
|
62
|
+
const db = await getDb();
|
|
63
|
+
const deps = {
|
|
64
|
+
identityFor: (candidate: string) => worktreeIdentityFor(db, candidate),
|
|
65
|
+
carryIgnored: settings.carryIgnored,
|
|
66
|
+
dataDir: getDataDir(),
|
|
67
|
+
configuredDirectory: settings.directory,
|
|
68
|
+
record: async (row: {
|
|
69
|
+
path: string;
|
|
70
|
+
primaryRoot: string;
|
|
71
|
+
projectId: string;
|
|
72
|
+
branch: string;
|
|
73
|
+
startRef: string;
|
|
74
|
+
createdAt: string;
|
|
75
|
+
}) => {
|
|
76
|
+
await putWorktreeRow(db, {
|
|
77
|
+
path: row.path,
|
|
78
|
+
primary_root: row.primaryRoot,
|
|
79
|
+
project_id: row.projectId,
|
|
80
|
+
branch: row.branch,
|
|
81
|
+
start_ref: row.startRef,
|
|
82
|
+
created_at: row.createdAt,
|
|
83
|
+
});
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const created = await createWorktree({ projectPath, ...parsed }, deps);
|
|
89
|
+
await invalidateWorktreeTopology(created.primaryRoot);
|
|
90
|
+
// The new folder is structure: every sidebar re-reads, and the panels that
|
|
91
|
+
// already point at this repository drop their cached topology.
|
|
92
|
+
emitRealtimeSignal('sidebar-structure');
|
|
93
|
+
emitRealtimeSignal('workspace-dirty');
|
|
94
|
+
return json({ worktree: created, carried: created.carried });
|
|
95
|
+
} catch (error) {
|
|
96
|
+
return worktreeFailureResponse(error);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* The same plan, without creating anything. Answers every failure rather than
|
|
102
|
+
* the first, because a dialog that shows one error at a time makes the user
|
|
103
|
+
* discover the second one after fixing the first.
|
|
104
|
+
*/
|
|
105
|
+
export async function validateAction({ request }: ActionFunctionArgs) {
|
|
106
|
+
if (request.method !== 'POST') return methodNotAllowed({ request, params: {} });
|
|
107
|
+
if (isMock()) return mockRefusal();
|
|
108
|
+
|
|
109
|
+
const url = new URL(request.url);
|
|
110
|
+
const resolved = await resolveWorktreeProject(url.searchParams.get('root'), url.searchParams.get('folderId'));
|
|
111
|
+
if (resolved.error) return resolved.error;
|
|
112
|
+
const projectPath = resolved.root as string;
|
|
113
|
+
|
|
114
|
+
const body = (await request.json().catch(() => ({}))) as CreateBody;
|
|
115
|
+
const parsed = readBody(body);
|
|
116
|
+
const settings = await readWorktreeSettings();
|
|
117
|
+
const db = await getDb();
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
const plan = await planWorktree(
|
|
121
|
+
{ projectPath, ...parsed },
|
|
122
|
+
{
|
|
123
|
+
identityFor: (candidate: string) => worktreeIdentityFor(db, candidate),
|
|
124
|
+
dataDir: getDataDir(),
|
|
125
|
+
configuredDirectory: settings.directory,
|
|
126
|
+
},
|
|
127
|
+
);
|
|
128
|
+
const payload: WorktreeValidation = {
|
|
129
|
+
ok: true,
|
|
130
|
+
failures: [],
|
|
131
|
+
resolved: { name: plan.name, directory: plan.directory, branch: plan.branch },
|
|
132
|
+
};
|
|
133
|
+
return json(payload);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
const payload: WorktreeValidation = { ok: false, failures: [asWorktreeFailure(error)] };
|
|
136
|
+
return json(payload, { status: 400 });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { bindingsFor, type HandlerBinding } from '@/server/lib/route-adapter';
|
|
2
|
+
import * as worktreesList from '@/server/routes/worktrees/list';
|
|
3
|
+
import { action as createAction, validateAction } from '@/server/routes/worktrees/create';
|
|
4
|
+
import { action as removeAction } from '@/server/routes/worktrees/remove';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* `/api/worktrees` — list, create, validate, remove.
|
|
8
|
+
*
|
|
9
|
+
* Every binding is explicit rather than `actionBindings`, and that is the whole
|
|
10
|
+
* reason this file exists: `actionBindings` mounts all four mutating verbs AND a
|
|
11
|
+
* 405 GET fallback, and Elysia lets a later registration win — so mounting the
|
|
12
|
+
* create module that way would replace the list loader with a "method not
|
|
13
|
+
* allowed" response on the same path. (`sessions/index.ts` records the same trap
|
|
14
|
+
* for the batch-delete routes.)
|
|
15
|
+
*
|
|
16
|
+
* `validate` is a STATIC segment registered before the collection path.
|
|
17
|
+
* `DELETE` takes the worktree path in its BODY, not in the URL: a path contains
|
|
18
|
+
* separators, and a wildcard segment would also answer for `/validate`.
|
|
19
|
+
*/
|
|
20
|
+
export const worktreesBindings: HandlerBinding[] = [
|
|
21
|
+
...bindingsFor(worktreesList, '/api/worktrees'),
|
|
22
|
+
{ method: 'POST', path: '/api/worktrees/validate', handler: validateAction },
|
|
23
|
+
{ method: 'POST', path: '/api/worktrees', handler: createAction },
|
|
24
|
+
{ method: 'DELETE', path: '/api/worktrees', handler: removeAction },
|
|
25
|
+
];
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* GET /api/worktrees — the worktrees of one project, with the facts a row shows.
|
|
8
|
+
*
|
|
9
|
+
* Deliberately NOT on the realtime `git:` topic: that payload is republished on
|
|
10
|
+
* every tool call, and this read costs a `git status` per worktree. It is opened
|
|
11
|
+
* by hand (Settings → Projects → Worktrees, and the delete dialog), exactly the
|
|
12
|
+
* reasoning `git-repo-details.ts` records for keeping the repo modal off it.
|
|
13
|
+
*
|
|
14
|
+
* `sessions` counts chats whose cwd is inside a worktree, from the same topology
|
|
15
|
+
* the sidebar uses — so the number a row shows is the number of rows the user
|
|
16
|
+
* can see under it.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
import { json } from '@/server/lib/remix-compat';
|
|
21
|
+
import type { LoaderFunctionArgs } from '@/server/lib/remix-compat';
|
|
22
|
+
import { getDataDir } from '@/server/lib/lifecycle/paths';
|
|
23
|
+
import { resolveProjectRoot } from '@/server/lib/omp/core/worktree';
|
|
24
|
+
import { listSessionFiles } from '@/server/lib/omp/session/files';
|
|
25
|
+
import { scanSessionInfoCached } from '@/server/lib/omp/session/files';
|
|
26
|
+
import { canonicalPath } from '@/server/lib/fs/canonical';
|
|
27
|
+
import { worktreeIdentityFor, listWorktreeRows } from '@/server/lib/db/worktree-registry.server';
|
|
28
|
+
import { dirtyCount, commitsAheadOf } from '@/server/lib/fs/worktree/remove';
|
|
29
|
+
import { listWorktrees, isWorkTree } from '@/server/lib/fs/worktree/list';
|
|
30
|
+
import {
|
|
31
|
+
defaultWorktreeRoot,
|
|
32
|
+
resolveConfiguredWorktreeRoot,
|
|
33
|
+
} from '@/server/lib/fs/worktree/location';
|
|
34
|
+
import { readWorktreeSettings } from '@/server/lib/fs/worktree/settings.server';
|
|
35
|
+
import { resolveProjectId } from '@/server/lib/fs/worktree/project-id';
|
|
36
|
+
import { getDb } from '@/server/db.server';
|
|
37
|
+
import { isMock } from '@/server/routes/worktrees/shared';
|
|
38
|
+
import { mockRefusal, resolveWorktreeProject, worktreeFailureResponse } from '@/server/routes/worktrees/shared';
|
|
39
|
+
import type { WorktreeInfo, WorktreeListPayload } from '@/shared/lib/workspace/worktree/types';
|
|
40
|
+
|
|
41
|
+
export async function loader({ request }: LoaderFunctionArgs) {
|
|
42
|
+
if (isMock()) return mockRefusal();
|
|
43
|
+
|
|
44
|
+
const url = new URL(request.url);
|
|
45
|
+
const resolved = await resolveWorktreeProject(url.searchParams.get('root'), url.searchParams.get('folderId'));
|
|
46
|
+
if (resolved.error) return resolved.error;
|
|
47
|
+
const root = resolved.root as string;
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const db = await getDb();
|
|
51
|
+
const primaryRoot = await resolveProjectRoot(root);
|
|
52
|
+
// "This folder is not a Git repository" is an ANSWER, not a failure. The
|
|
53
|
+
// payload has carried `isRepo` for exactly this, and a plain directory (a
|
|
54
|
+
// notes folder bound as a workspace) must read as "no worktrees here" —
|
|
55
|
+
// a 500 would make the composer report a broken feature for a folder that
|
|
56
|
+
// simply has nothing to offer. The probe runs before the identity read,
|
|
57
|
+
// which is what throws for a directory with no root commit.
|
|
58
|
+
if (!(await isWorkTree(primaryRoot))) {
|
|
59
|
+
const empty: WorktreeListPayload = { worktrees: [], base: '', isRepo: false };
|
|
60
|
+
return json(empty);
|
|
61
|
+
}
|
|
62
|
+
const settings = await readWorktreeSettings();
|
|
63
|
+
const projectId = await resolveProjectId(primaryRoot, (candidate) => worktreeIdentityFor(db, candidate));
|
|
64
|
+
const base = resolveConfiguredWorktreeRoot(settings.directory, primaryRoot)
|
|
65
|
+
?? defaultWorktreeRoot(getDataDir(), projectId);
|
|
66
|
+
|
|
67
|
+
const entries = await listWorktrees(primaryRoot);
|
|
68
|
+
const [rows, sessionCwds] = await Promise.all([
|
|
69
|
+
listWorktreeRows(db, primaryRoot),
|
|
70
|
+
// Every tree that can be counted, not just the repository: a worktree does
|
|
71
|
+
// NOT have to live inside it. Kabmo's own default puts one under the data
|
|
72
|
+
// directory (`<data>/worktrees/<projectId>/<name>`), so a `primaryRoot`
|
|
73
|
+
// prefix test counted ZERO sessions for every worktree at the default
|
|
74
|
+
// location — the number the delete dialog warns with.
|
|
75
|
+
sessionCwdsUnder([primaryRoot, ...entries.map((entry) => entry.path)]),
|
|
76
|
+
]);
|
|
77
|
+
const rowByPath = new Map(rows.map((row) => [row.path, row]));
|
|
78
|
+
|
|
79
|
+
const worktrees: WorktreeInfo[] = [];
|
|
80
|
+
for (const entry of entries) {
|
|
81
|
+
const row = rowByPath.get(entry.path);
|
|
82
|
+
const dirty = entry.isPrimary ? 0 : await dirtyCount(entry.path).catch(() => 0);
|
|
83
|
+
const ahead = entry.isPrimary
|
|
84
|
+
? 0
|
|
85
|
+
: (await commitsAheadOf(primaryRoot, row?.start_ref ?? '', entry.branch)) ?? 0;
|
|
86
|
+
worktrees.push({
|
|
87
|
+
path: entry.path,
|
|
88
|
+
branch: entry.branch,
|
|
89
|
+
head: entry.head,
|
|
90
|
+
detached: entry.detached,
|
|
91
|
+
missing: entry.missing,
|
|
92
|
+
isPrimary: entry.isPrimary,
|
|
93
|
+
dirty,
|
|
94
|
+
aheadOfStart: ahead,
|
|
95
|
+
startRef: row?.start_ref ?? '',
|
|
96
|
+
sessions: sessionCwds.filter((cwd) => cwd === entry.path || cwd.startsWith(`${entry.path}${path.sep}`)).length,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const payload: WorktreeListPayload = { worktrees, base, isRepo: true };
|
|
101
|
+
return json(payload);
|
|
102
|
+
} catch (error) {
|
|
103
|
+
// A failed listing is REPORTED, never substituted with an empty list: "no
|
|
104
|
+
// worktrees" and "git could not answer" are different answers, and only one
|
|
105
|
+
// of them means the user has nothing there.
|
|
106
|
+
return worktreeFailureResponse(error, 500);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Every session's recorded cwd, canonicalized, restricted to the trees that can
|
|
112
|
+
* be counted — the primary checkout and each worktree.
|
|
113
|
+
*
|
|
114
|
+
* Both sides are canonicalized, for the same reason `canonicalPath` exists: the
|
|
115
|
+
* recorded cwd is the spelling the agent was spawned with while a worktree path
|
|
116
|
+
* comes back realpath'd from git, and on macOS those differ for anything under
|
|
117
|
+
* `/var` (a symlink to `/private/var`) — a raw prefix test would then count zero
|
|
118
|
+
* for a worktree that is right there.
|
|
119
|
+
*/
|
|
120
|
+
async function sessionCwdsUnder(roots: string[]): Promise<string[]> {
|
|
121
|
+
const files = await listSessionFiles();
|
|
122
|
+
const infos = await Promise.all(files.map((file) => scanSessionInfoCached(file)));
|
|
123
|
+
const canonicalRoots = [...new Set(await Promise.all(roots.map((root) => canonicalPath(root))))];
|
|
124
|
+
const cwds: string[] = [];
|
|
125
|
+
const seen = new Set<string>();
|
|
126
|
+
for (const info of infos) {
|
|
127
|
+
if (!info?.cwd || seen.has(info.cwd)) continue;
|
|
128
|
+
seen.add(info.cwd);
|
|
129
|
+
const cwd = await canonicalPath(info.cwd);
|
|
130
|
+
if (!canonicalRoots.some((root) => cwd === root || cwd.startsWith(`${root}${path.sep}`))) continue;
|
|
131
|
+
cwds.push(cwd);
|
|
132
|
+
}
|
|
133
|
+
return cwds;
|
|
134
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* DELETE /api/worktrees — remove one worktree.
|
|
8
|
+
*
|
|
9
|
+
* Order, and it is the point: stop the live children, archive and delete their
|
|
10
|
+
* sessions, THEN remove the directory. The reverse — which is what a plain
|
|
11
|
+
* `git worktree remove` alone does — leaves a child whose cwd no longer exists,
|
|
12
|
+
* and `resolveSpawnCwd` then quietly runs that session in the server's own
|
|
13
|
+
* directory: a chat the user deleted, alive somewhere else.
|
|
14
|
+
*
|
|
15
|
+
* The dirty refusal happens AFTER the sessions are stopped, so a refusal leaves a
|
|
16
|
+
* worktree the user can still see and retry rather than a half-deleted state.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { json } from '@/server/lib/remix-compat';
|
|
20
|
+
import type { ActionFunctionArgs } from '@/server/lib/remix-compat';
|
|
21
|
+
import { methodNotAllowed } from '@/server/lib/route-adapter';
|
|
22
|
+
import { getDataDir } from '@/server/lib/lifecycle/paths';
|
|
23
|
+
import { getDb } from '@/server/db.server';
|
|
24
|
+
import { emitRealtimeSignal } from '@/server/lib/realtime/signals.server';
|
|
25
|
+
import { pathHolds } from '@/server/lib/fs/canonical';
|
|
26
|
+
import { removeWorktreeRow, worktreeRowFor } from '@/server/lib/db/worktree-registry.server';
|
|
27
|
+
import { removeWorktree } from '@/server/lib/fs/worktree/remove';
|
|
28
|
+
import { invalidateWorktreeTopology } from '@/server/lib/fs/worktree/topology';
|
|
29
|
+
import { resolveProjectRoot } from '@/server/lib/omp/core/worktree';
|
|
30
|
+
import { listRpcSessions } from '@/server/lib/omp/rpc/session-registry';
|
|
31
|
+
import { removeSession } from '@/server/lib/omp/session/delete-flow.server';
|
|
32
|
+
import { isMock, mockRefusal, resolveWorktreeProject, worktreeFailureResponse } from '@/server/routes/worktrees/shared';
|
|
33
|
+
|
|
34
|
+
interface RemoveBody {
|
|
35
|
+
path?: unknown;
|
|
36
|
+
deleteBranch?: unknown;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Stop and delete every session whose cwd is inside `worktreePath`.
|
|
41
|
+
*
|
|
42
|
+
* A session this process does not manage has no child to stop — its transcript
|
|
43
|
+
* is removed the same way, and the owning instance learns on its next spawn
|
|
44
|
+
* (`resolveSpawnCwd` finds no directory and the row is already gone).
|
|
45
|
+
*/
|
|
46
|
+
async function stopSessionsIn(worktreePath: string): Promise<string[]> {
|
|
47
|
+
const handled: string[] = [];
|
|
48
|
+
for (const session of liveSessions()) {
|
|
49
|
+
if (!(await pathHolds(worktreePath, session.cwd))) continue;
|
|
50
|
+
// `removeSession` IS the single-delete sequence: pending-id refusal, id
|
|
51
|
+
// shape, the busy-child 409, `destroyAndWait`, then the purge. Repeating any
|
|
52
|
+
// of it here is how the batch path would drift from `DELETE /api/sessions/:id`.
|
|
53
|
+
const removal = await removeSession(session.id);
|
|
54
|
+
if (removal.ok) handled.push(session.id);
|
|
55
|
+
}
|
|
56
|
+
return handled;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Live wrappers, as the registry reports them. */
|
|
60
|
+
function liveSessions(): { id: string; cwd: string }[] {
|
|
61
|
+
return listRpcSessions().map((session) => ({ id: session.sessionId, cwd: session.cwd }));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function action({ request }: ActionFunctionArgs) {
|
|
65
|
+
if (request.method !== 'DELETE') return methodNotAllowed({ request, params: {} });
|
|
66
|
+
if (isMock()) return mockRefusal();
|
|
67
|
+
|
|
68
|
+
const url = new URL(request.url);
|
|
69
|
+
const resolved = await resolveWorktreeProject(url.searchParams.get('root'), url.searchParams.get('folderId'));
|
|
70
|
+
if (resolved.error) return resolved.error;
|
|
71
|
+
const projectPath = resolved.root as string;
|
|
72
|
+
|
|
73
|
+
const body = (await request.json().catch(() => ({}))) as RemoveBody;
|
|
74
|
+
const worktreePath = typeof body.path === 'string' ? body.path.trim() : '';
|
|
75
|
+
if (!worktreePath) {
|
|
76
|
+
return json({ error: 'path is required', code: 'invalid_name', field: 'location' }, { status: 400 });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const db = await getDb();
|
|
80
|
+
try {
|
|
81
|
+
const primaryRoot = await resolveProjectRoot(projectPath);
|
|
82
|
+
const removed = await removeWorktree(
|
|
83
|
+
{
|
|
84
|
+
worktreePath,
|
|
85
|
+
projectPath,
|
|
86
|
+
deleteBranch: body.deleteBranch === true,
|
|
87
|
+
managedRoot: `${getDataDir()}/worktrees`,
|
|
88
|
+
stopSessions: stopSessionsIn,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
rowFor: (candidate) => worktreeRowFor(db, candidate),
|
|
92
|
+
removeRow: (candidate) => removeWorktreeRow(db, candidate),
|
|
93
|
+
},
|
|
94
|
+
);
|
|
95
|
+
await invalidateWorktreeTopology(primaryRoot);
|
|
96
|
+
emitRealtimeSignal('sidebar-structure');
|
|
97
|
+
emitRealtimeSignal('workspace-dirty');
|
|
98
|
+
return json(removed);
|
|
99
|
+
} catch (error) {
|
|
100
|
+
return worktreeFailureResponse(error);
|
|
101
|
+
}
|
|
102
|
+
}
|