@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,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The desktop workspace group's three panel widths, resolved from one place.
|
|
8
|
+
*
|
|
9
|
+
* `WorkspacePanels` used to compute these inline, which was correct but made the
|
|
10
|
+
* component's own contract hard to read and impossible to unit-test: every rule
|
|
11
|
+
* here — the two-call order, the pair caps, the leftover width a collapsed chat
|
|
12
|
+
* hands the editor — is arithmetic over values the caller already has.
|
|
13
|
+
*
|
|
14
|
+
* Pure: no hooks, no DOM. The caller measures the group and reads the stored
|
|
15
|
+
* widths; this module decides what those mean.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
DEFAULT_EDITOR_FRACTIONS,
|
|
20
|
+
DEFAULT_EDITOR_WIDTHS,
|
|
21
|
+
HANDLE_WIDTH,
|
|
22
|
+
MIN_EDITOR_PANEL_WIDTH,
|
|
23
|
+
panelFraction,
|
|
24
|
+
pairMaxSize,
|
|
25
|
+
resolvePanelWidth,
|
|
26
|
+
type EditorWidthMode,
|
|
27
|
+
type PanelWidths,
|
|
28
|
+
} from '@/shared/lib/workspace/panel-widths';
|
|
29
|
+
import { DEFAULT_RIGHT_PANEL_FRACTION } from '@/shared/lib/workspace/right-panels';
|
|
30
|
+
|
|
31
|
+
export interface WorkspacePanelWidthsArgs {
|
|
32
|
+
/** Measured area of the group, or null before the observer reports. */
|
|
33
|
+
available: number | null;
|
|
34
|
+
/** This session's remembered widths. */
|
|
35
|
+
panelWidths: PanelWidths;
|
|
36
|
+
/** Which slot the editor column is using (a diff wants more room). */
|
|
37
|
+
editorWidthMode: EditorWidthMode;
|
|
38
|
+
/** The right panel's active view, whose slot and defaults apply. */
|
|
39
|
+
activeRightPanel: string;
|
|
40
|
+
/** The active view's own defaults and floor, when it has them. */
|
|
41
|
+
activePanel?: { defaultFraction?: number; defaultWidth?: number; minWidth?: number };
|
|
42
|
+
showEditor: boolean;
|
|
43
|
+
showRightPanel: boolean;
|
|
44
|
+
/** The chat column is collapsed, so it holds no floor and no pair share. */
|
|
45
|
+
chatHidden: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface WorkspacePanelWidths {
|
|
49
|
+
right: number;
|
|
50
|
+
editor: number;
|
|
51
|
+
/** Drag ceilings; `undefined` before the group is measured. */
|
|
52
|
+
editorMax: number | undefined;
|
|
53
|
+
rightMax: number | undefined;
|
|
54
|
+
/** Separators actually rendered, which also consume width. */
|
|
55
|
+
handleCount: number;
|
|
56
|
+
/** The right panel's floor, which the drag ceiling also reserves. */
|
|
57
|
+
rightMin: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function resolveWorkspacePanelWidths(args: WorkspacePanelWidthsArgs): WorkspacePanelWidths {
|
|
61
|
+
const { available, panelWidths, editorWidthMode, activeRightPanel, activePanel, showEditor, showRightPanel, chatHidden } = args;
|
|
62
|
+
|
|
63
|
+
// The separator before the editor is gone while the chat is collapsed: there
|
|
64
|
+
// is nothing on its left to transfer width to, and the drag's neighbour walk
|
|
65
|
+
// skips a collapsed panel, so it would render a handle that does nothing.
|
|
66
|
+
const handleCount = (showEditor && !chatHidden ? 1 : 0) + (showRightPanel ? 1 : 0);
|
|
67
|
+
const rightMin = activePanel?.minWidth ?? MIN_EDITOR_PANEL_WIDTH;
|
|
68
|
+
|
|
69
|
+
// The editor and the right panel are a COUPLED pair: the right panel is
|
|
70
|
+
// resolved first, reserving the editor's WANTED share (the editor has no
|
|
71
|
+
// result yet), and the editor then reserves the right panel's ACTUAL width.
|
|
72
|
+
// That asymmetry is deliberate — an editor asking for more than its default
|
|
73
|
+
// makes the right panel give way first, and the pair still sums to the total.
|
|
74
|
+
const editorFraction = panelFraction(panelWidths[editorWidthMode], DEFAULT_EDITOR_FRACTIONS[editorWidthMode], available);
|
|
75
|
+
const editorReservePx = available != null && available > 0 ? Math.round(editorFraction * available) : 0;
|
|
76
|
+
|
|
77
|
+
const right = resolvePanelWidth({
|
|
78
|
+
stored: panelWidths.right?.[activeRightPanel],
|
|
79
|
+
defaultFraction: activePanel?.defaultFraction ?? DEFAULT_RIGHT_PANEL_FRACTION,
|
|
80
|
+
defaultPx: activePanel?.defaultWidth ?? MIN_EDITOR_PANEL_WIDTH,
|
|
81
|
+
available,
|
|
82
|
+
min: rightMin,
|
|
83
|
+
pairReservePx: showEditor ? editorReservePx : 0,
|
|
84
|
+
// The editor has not been resolved yet, so only its floor can be reserved.
|
|
85
|
+
siblingWidth: showEditor ? MIN_EDITOR_PANEL_WIDTH : 0,
|
|
86
|
+
handleCount,
|
|
87
|
+
chatHidden,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// With the chat collapsed the editor takes everything the right panel and the
|
|
91
|
+
// separators do not. Resolving from its remembered FRACTION would leave the
|
|
92
|
+
// freed width as an empty gap, which is the opposite of what the mode
|
|
93
|
+
// promises; the remembered width is untouched, so leaving restores it.
|
|
94
|
+
const editor = chatHidden
|
|
95
|
+
? Math.max(
|
|
96
|
+
MIN_EDITOR_PANEL_WIDTH,
|
|
97
|
+
available != null && available > 0
|
|
98
|
+
? Math.floor(available - (showRightPanel ? right : 0) - handleCount * HANDLE_WIDTH)
|
|
99
|
+
: DEFAULT_EDITOR_WIDTHS[editorWidthMode],
|
|
100
|
+
)
|
|
101
|
+
: resolvePanelWidth({
|
|
102
|
+
stored: panelWidths[editorWidthMode],
|
|
103
|
+
defaultFraction: DEFAULT_EDITOR_FRACTIONS[editorWidthMode],
|
|
104
|
+
defaultPx: DEFAULT_EDITOR_WIDTHS[editorWidthMode],
|
|
105
|
+
available,
|
|
106
|
+
min: MIN_EDITOR_PANEL_WIDTH,
|
|
107
|
+
pairReservePx: showRightPanel ? right : 0,
|
|
108
|
+
siblingWidth: showRightPanel ? right : 0,
|
|
109
|
+
handleCount,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
right,
|
|
114
|
+
editor,
|
|
115
|
+
// `resolvePanelWidth` caps what it HANDS to `defaultSize`, but a separator
|
|
116
|
+
// drag bypasses it — the resizer transfers pixels between two panels clamped
|
|
117
|
+
// only by their own `maxSize`. Each max is its own cap and the pair total
|
|
118
|
+
// minus the sibling's FLOOR (it gives way down to its minimum mid-drag).
|
|
119
|
+
editorMax: pairMaxSize({ available, siblingMin: rightMin, siblingOpen: showRightPanel, chatHidden }),
|
|
120
|
+
rightMax: pairMaxSize({ available, siblingMin: MIN_EDITOR_PANEL_WIDTH, siblingOpen: showEditor, chatHidden }),
|
|
121
|
+
handleCount,
|
|
122
|
+
rightMin,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Branch naming and slugging.
|
|
8
|
+
*
|
|
9
|
+
* Two rules here are load-bearing and each was a defect first:
|
|
10
|
+
*
|
|
11
|
+
* - `slug('..')` is `..`, and `path.join(root, '..')` escapes the root — so a
|
|
12
|
+
* dot-only result is REJECTED, not passed through.
|
|
13
|
+
* - our own `wt/` namespace is stripped from the folder (the folder does not
|
|
14
|
+
* repeat it) while a repository's own namespace stays visible (`feat/x` →
|
|
15
|
+
* `feat-x`), so a folder never reads as a second branch name.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { describe, expect, test } from 'bun:test';
|
|
19
|
+
import {
|
|
20
|
+
defaultWorktreeBranch,
|
|
21
|
+
folderNameForBranch,
|
|
22
|
+
generateBranchSlug,
|
|
23
|
+
isPlausibleBranchName,
|
|
24
|
+
normalizeBranchName,
|
|
25
|
+
slugWorktreeName,
|
|
26
|
+
WORKTREE_BRANCH_PREFIX,
|
|
27
|
+
} from '@/shared/lib/workspace/worktree/branch-name';
|
|
28
|
+
|
|
29
|
+
describe('worktree branch names', () => {
|
|
30
|
+
test('the generated branch carries our namespace', () => {
|
|
31
|
+
const branch = defaultWorktreeBranch();
|
|
32
|
+
expect(branch.startsWith(`${WORKTREE_BRANCH_PREFIX}/`)).toBe(true);
|
|
33
|
+
expect(branch.slice(WORKTREE_BRANCH_PREFIX.length + 1)).toMatch(/^[a-z]+-[a-z]+$/);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('the ref namespace is stripped and only that', () => {
|
|
37
|
+
expect(normalizeBranchName('refs/heads/wt/x')).toBe('wt/x');
|
|
38
|
+
expect(normalizeBranchName('heads/wt/x')).toBe('wt/x');
|
|
39
|
+
expect(normalizeBranchName(' wt/x ')).toBe('wt/x');
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('folder naming', () => {
|
|
44
|
+
test('our namespace is stripped, a repository namespace is kept', () => {
|
|
45
|
+
expect(folderNameForBranch('wt/cosmic-dolphin')).toBe('cosmic-dolphin');
|
|
46
|
+
expect(folderNameForBranch('feat/payments')).toBe('feat-payments');
|
|
47
|
+
expect(folderNameForBranch('refs/heads/wt/yak')).toBe('yak');
|
|
48
|
+
expect(folderNameForBranch('plain')).toBe('plain');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('a folder is one path segment and cannot escape its root', () => {
|
|
52
|
+
expect(slugWorktreeName('../../etc/passwd')).toBe('etc-passwd');
|
|
53
|
+
expect(slugWorktreeName('a b/c')).toBe('a-b-c');
|
|
54
|
+
expect(slugWorktreeName('-weird-')).toBe('weird');
|
|
55
|
+
// The trap: these two ARE valid path segments and would leave the root.
|
|
56
|
+
expect(slugWorktreeName('..')).toBe('');
|
|
57
|
+
expect(slugWorktreeName('.')).toBe('');
|
|
58
|
+
expect(slugWorktreeName('...')).toBe('');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('a folder name is bounded', () => {
|
|
62
|
+
expect(slugWorktreeName('x'.repeat(200)).length).toBeLessThanOrEqual(80);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('a non-Latin branch leaves no slug, so the caller generates a name', () => {
|
|
66
|
+
expect(folderNameForBranch('测试分支')).toBe('');
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe('generated slugs', () => {
|
|
71
|
+
test('stay inside the word lists and vary', () => {
|
|
72
|
+
const seen = new Set<string>();
|
|
73
|
+
for (let index = 0; index < 60; index += 1) seen.add(generateBranchSlug());
|
|
74
|
+
expect(seen.size).toBeGreaterThan(10);
|
|
75
|
+
for (const slug of seen) expect(slug).toMatch(/^[a-z]+-[a-z]+$/);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe('branch name plausibility', () => {
|
|
80
|
+
test('accepts what git accepts', () => {
|
|
81
|
+
expect(isPlausibleBranchName('wt/ok')).toBe(true);
|
|
82
|
+
expect(isPlausibleBranchName('feat/payments-v2')).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('refuses what git refuses, before git has to say so', () => {
|
|
86
|
+
expect(isPlausibleBranchName('')).toBe(false);
|
|
87
|
+
expect(isPlausibleBranchName('has space')).toBe(false);
|
|
88
|
+
expect(isPlausibleBranchName('a..b')).toBe(false);
|
|
89
|
+
expect(isPlausibleBranchName('-leading')).toBe(false);
|
|
90
|
+
expect(isPlausibleBranchName('trailing/')).toBe(false);
|
|
91
|
+
expect(isPlausibleBranchName('tilde~1')).toBe(false);
|
|
92
|
+
expect(isPlausibleBranchName('at@{1}')).toBe(false);
|
|
93
|
+
expect(isPlausibleBranchName('a//b')).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Branch naming and slugging for worktrees — ONE module, because the dialog and
|
|
8
|
+
* the create route must agree on what a name means. The dialog pre-fills the
|
|
9
|
+
* branch and derives the folder; the server re-derives both from what arrives.
|
|
10
|
+
*
|
|
11
|
+
* The word lists are OpenChamber's Ubuntu-style pairs (their
|
|
12
|
+
* `lib/git/branchNameGenerator.ts`), and the pick is seeded from
|
|
13
|
+
* `crypto.getRandomValues` rather than `Math.random`, which is available in the
|
|
14
|
+
* browser and in Bun without an import.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Kabmo's namespace for a generated worktree branch. Deliberately `wt`, not
|
|
19
|
+
* OpenChamber's `openchamber`: omp's own `/wt` already creates `wt/*`, so a
|
|
20
|
+
* repository that has used either tool reads one convention instead of two.
|
|
21
|
+
*/
|
|
22
|
+
export const WORKTREE_BRANCH_PREFIX = 'wt';
|
|
23
|
+
|
|
24
|
+
const ADJECTIVES = [
|
|
25
|
+
'artful', 'bionic', 'cosmic', 'disco', 'focal', 'groovy', 'jammy', 'kinetic',
|
|
26
|
+
'lunar', 'noble', 'bold', 'brave', 'calm', 'eager', 'gentle', 'happy', 'keen',
|
|
27
|
+
'lively', 'merry', 'swift', 'warm', 'wise', 'bright', 'clever', 'daring',
|
|
28
|
+
'agile', 'crisp', 'fresh', 'lucid', 'quick', 'sharp', 'vivid', 'zealous',
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
const NOUNS = [
|
|
32
|
+
'aardvark', 'beaver', 'chipmunk', 'dolphin', 'falcon', 'gopher', 'hedgehog',
|
|
33
|
+
'jackal', 'koala', 'lemur', 'mongoose', 'narwhal', 'otter', 'pangolin',
|
|
34
|
+
'quokka', 'raccoon', 'salamander', 'toucan', 'walrus', 'yak', 'zebra',
|
|
35
|
+
'badger', 'condor', 'dingo', 'egret', 'ferret', 'gecko', 'heron', 'iguana',
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
/** A random index from the platform CSPRNG, so two sessions rarely collide. */
|
|
39
|
+
function pick(values: readonly string[]): string {
|
|
40
|
+
const bytes = new Uint8Array(2);
|
|
41
|
+
crypto.getRandomValues(bytes);
|
|
42
|
+
return values[((bytes[0] ?? 0) << 8 | (bytes[1] ?? 0)) % values.length] as string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** One adjective-noun pair, e.g. `cosmic-dolphin`. */
|
|
46
|
+
export function generateBranchSlug(): string {
|
|
47
|
+
return `${pick(ADJECTIVES)}-${pick(NOUNS)}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** The default branch of a worktree: our namespace plus a generated slug. */
|
|
51
|
+
export function defaultWorktreeBranch(): string {
|
|
52
|
+
return `${WORKTREE_BRANCH_PREFIX}/${generateBranchSlug()}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** The branch as git names it, without the ref namespace. */
|
|
56
|
+
export function normalizeBranchName(value: string): string {
|
|
57
|
+
return String(value ?? '')
|
|
58
|
+
.trim()
|
|
59
|
+
.replace(/^refs\/heads\//, '')
|
|
60
|
+
.replace(/^heads\//, '')
|
|
61
|
+
.replace(/\s+/g, '-')
|
|
62
|
+
.replace(/^\/+|\/+$/g, '');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* One path segment, ≤80 chars, safe on every filesystem we target.
|
|
67
|
+
*
|
|
68
|
+
* A leading `.` is stripped and the dot-only results are rejected: `slug('.')`
|
|
69
|
+
* is `.` and `slug('..')` is `..`, and `path.join(root, name)` resolves those to
|
|
70
|
+
* the root itself and to its parent. A folder name that escapes its root is not
|
|
71
|
+
* a name.
|
|
72
|
+
*/
|
|
73
|
+
export function slugWorktreeName(value: string): string {
|
|
74
|
+
const slug = String(value ?? '')
|
|
75
|
+
.trim()
|
|
76
|
+
.replace(/^refs\/heads\//, '')
|
|
77
|
+
.replace(/^heads\//, '')
|
|
78
|
+
.replace(/\s+/g, '-')
|
|
79
|
+
.replace(/^\/+|\/+$/g, '')
|
|
80
|
+
.split('/')
|
|
81
|
+
.join('-')
|
|
82
|
+
.replace(/[^A-Za-z0-9._-]+/g, '-')
|
|
83
|
+
.replace(/-+/g, '-')
|
|
84
|
+
.replace(/^[.-]+|[.-]+$/g, '')
|
|
85
|
+
.slice(0, 80);
|
|
86
|
+
return slug === '.' || slug === '..' ? '' : slug;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The folder a branch gets. Our own `wt/` namespace is stripped — the folder
|
|
91
|
+
* does not repeat it — while a repository's own namespaces stay visible
|
|
92
|
+
* (`feat/x` → `feat-x`). A non-Latin branch (`测试分支`) leaves no slug, and the
|
|
93
|
+
* caller falls back to a generated name rather than an empty folder.
|
|
94
|
+
*/
|
|
95
|
+
export function folderNameForBranch(branch: string): string {
|
|
96
|
+
const withoutRef = normalizeBranchName(branch);
|
|
97
|
+
const namespaced = `${WORKTREE_BRANCH_PREFIX}/`;
|
|
98
|
+
const withoutOwnPrefix = withoutRef.startsWith(namespaced)
|
|
99
|
+
? withoutRef.slice(namespaced.length)
|
|
100
|
+
: withoutRef;
|
|
101
|
+
return slugWorktreeName(withoutOwnPrefix);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Whether git would plausibly accept this as a branch name. Rejects only what
|
|
106
|
+
* `git check-ref-format` rejects in every case — the server still asks git
|
|
107
|
+
* itself, because a plausible name can still collide with an existing ref.
|
|
108
|
+
*/
|
|
109
|
+
export function isPlausibleBranchName(value: string): boolean {
|
|
110
|
+
if (!value) return false;
|
|
111
|
+
if (/^[.\-/]/.test(value) || /[/.]$/.test(value)) return false;
|
|
112
|
+
if (value.includes('..') || value.includes('//') || value.includes('@{')) return false;
|
|
113
|
+
if (/[\s~^:?*[\\]/.test(value)) return false;
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Worktree shapes shared by the server and the client.
|
|
8
|
+
*
|
|
9
|
+
* A worktree session is a normal session whose cwd is a linked checkout, so the
|
|
10
|
+
* only thing that crosses the wire is where it is and whether it is still there
|
|
11
|
+
* — everything else is read from git.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** One row of `GET /api/worktrees`, and what a create answers with. */
|
|
15
|
+
export interface WorktreeInfo {
|
|
16
|
+
/** Absolute, canonical path of the checkout. */
|
|
17
|
+
path: string;
|
|
18
|
+
/** Branch checked out there (`refs/heads/` stripped); `''` when detached. */
|
|
19
|
+
branch: string;
|
|
20
|
+
/** The commit HEAD points at — what a detached row names instead of a branch. */
|
|
21
|
+
head: string;
|
|
22
|
+
detached: boolean;
|
|
23
|
+
/** git reports it prunable, or the directory is gone. */
|
|
24
|
+
missing: boolean;
|
|
25
|
+
/** The main checkout — listed, but never removable. */
|
|
26
|
+
isPrimary: boolean;
|
|
27
|
+
/** Changed + untracked count. */
|
|
28
|
+
dirty: number;
|
|
29
|
+
/**
|
|
30
|
+
* Commits on this branch that are not on the ref it was created from. Gates
|
|
31
|
+
* the delete dialog's branch checkbox, because `git branch -d` refuses an
|
|
32
|
+
* unmerged branch and a checkbox that cannot succeed is worse than none.
|
|
33
|
+
*/
|
|
34
|
+
aheadOfStart: number;
|
|
35
|
+
/** The ref recorded when the worktree was created; `''` for an existing branch. */
|
|
36
|
+
startRef: string;
|
|
37
|
+
/** Chats whose cwd is inside this worktree. */
|
|
38
|
+
sessions: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The per-session view, carried on `SessionItemData` so every layout and every
|
|
43
|
+
* plugin reads one field instead of asking git.
|
|
44
|
+
*/
|
|
45
|
+
export interface SessionWorktreeInfo {
|
|
46
|
+
branch: string;
|
|
47
|
+
/** Canonical worktree root. */
|
|
48
|
+
path: string;
|
|
49
|
+
/** The checkout this worktree belongs to — the folder's `project_path`. */
|
|
50
|
+
primaryRoot: string;
|
|
51
|
+
/** `missing` when git still tracks it but the directory is gone. */
|
|
52
|
+
status: 'ready' | 'missing';
|
|
53
|
+
detached: boolean;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Where a chat that has not started will run. */
|
|
57
|
+
export interface SpawnTarget {
|
|
58
|
+
folderId: number | null;
|
|
59
|
+
/** Absolute worktree path, or null for the folder's own checkout. */
|
|
60
|
+
worktreePath: string | null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Why a worktree action was refused. Codes are stable; messages are not. */
|
|
64
|
+
export type WorktreeErrorCode =
|
|
65
|
+
| 'not_a_repo'
|
|
66
|
+
| 'no_commits'
|
|
67
|
+
| 'invalid_name'
|
|
68
|
+
| 'branch_exists'
|
|
69
|
+
| 'branch_in_use'
|
|
70
|
+
| 'path_exists'
|
|
71
|
+
| 'create_failed'
|
|
72
|
+
| 'not_a_worktree'
|
|
73
|
+
| 'primary_checkout'
|
|
74
|
+
| 'dirty'
|
|
75
|
+
| 'dirty_carryover'
|
|
76
|
+
| 'remove_failed'
|
|
77
|
+
| 'mock_mode';
|
|
78
|
+
|
|
79
|
+
/** One refusal, with the field it belongs to so a dialog can mark the input. */
|
|
80
|
+
export interface WorktreeFailure {
|
|
81
|
+
code: WorktreeErrorCode;
|
|
82
|
+
message: string;
|
|
83
|
+
field?: 'branch' | 'folder' | 'location';
|
|
84
|
+
/** Present for `dirty`: how many files would be lost. */
|
|
85
|
+
count?: number;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** `GET /api/worktrees`. */
|
|
89
|
+
export interface WorktreeListPayload {
|
|
90
|
+
worktrees: WorktreeInfo[];
|
|
91
|
+
/** The resolved parent directory new worktrees are created in. */
|
|
92
|
+
base: string;
|
|
93
|
+
isRepo: boolean;
|
|
94
|
+
/** Present when the listing failed — never substituted with an empty list. */
|
|
95
|
+
error?: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** `POST /api/worktrees/validate`. */
|
|
99
|
+
export interface WorktreeValidation {
|
|
100
|
+
ok: boolean;
|
|
101
|
+
failures: WorktreeFailure[];
|
|
102
|
+
/** The name and directory the create would use, so the dialog can show them. */
|
|
103
|
+
resolved?: { name: string; directory: string; branch: string };
|
|
104
|
+
}
|
package/src/shared/types/fs.ts
CHANGED
|
@@ -74,4 +74,14 @@ export interface FsActionResponse {
|
|
|
74
74
|
error?: string;
|
|
75
75
|
/** The path a create or a transfer landed at, relative to the listed root. */
|
|
76
76
|
path?: string;
|
|
77
|
+
/** What a create made, so the tree can draw the row it just added. */
|
|
78
|
+
kind?: 'file' | 'folder';
|
|
79
|
+
/**
|
|
80
|
+
* The path a rename moved FROM, relative to the listed root.
|
|
81
|
+
*
|
|
82
|
+
* A rename answers with both ends so the panel can move the row in place:
|
|
83
|
+
* re-reading every open folder to discover a path it was just told is the
|
|
84
|
+
* reload this field exists to avoid.
|
|
85
|
+
*/
|
|
86
|
+
previousPath?: string;
|
|
77
87
|
}
|
|
@@ -32,3 +32,32 @@ export interface SidebarStatusEntry {
|
|
|
32
32
|
|
|
33
33
|
/** `sidebar:status` — keyed by session id. Absent id means "no volatile fields". */
|
|
34
34
|
export type SidebarStatusPayload = Record<string, SidebarStatusEntry>;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* One live session's row fields that can still move between turn boundaries.
|
|
38
|
+
*
|
|
39
|
+
* Exactly two, and the list is closed on purpose: everything else about a row
|
|
40
|
+
* (title, folder, cwd, worktree, archived) only changes on a STRUCTURAL event
|
|
41
|
+
* that publishes the structure for its own reason. These two are the ones a
|
|
42
|
+
* run advances mid-turn — the transcript appends, and a subagent's transcript
|
|
43
|
+
* appears beside it (which is what lights the row's roster chevron).
|
|
44
|
+
*/
|
|
45
|
+
export interface SidebarActivityEntry {
|
|
46
|
+
/** Newest transcript entry's ISO timestamp — the row's age and its
|
|
47
|
+
* `LATEST_SESSION` position. */
|
|
48
|
+
updatedAt?: string;
|
|
49
|
+
/** Whether the session has any subagent transcript on disk. */
|
|
50
|
+
hasSubagents?: boolean;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* `sidebar:activity` — session id → the live fields above, for the sessions
|
|
55
|
+
* with a run in flight.
|
|
56
|
+
*
|
|
57
|
+
* Only a running session can move between turn boundaries, so an id that is
|
|
58
|
+
* absent here simply keeps what the structure gave it. Both sides read the same
|
|
59
|
+
* sources (the newest JSONL entry's own `timestamp`, and the subagent
|
|
60
|
+
* transcript directory), so the client merges field by field and lets the
|
|
61
|
+
* newer stamp win.
|
|
62
|
+
*/
|
|
63
|
+
export type SidebarActivityPayload = Record<string, SidebarActivityEntry>;
|
|
@@ -48,6 +48,16 @@ export interface SettingsState {
|
|
|
48
48
|
* cannot render a broken editor, only a different one.
|
|
49
49
|
*/
|
|
50
50
|
editorFont: string;
|
|
51
|
+
/**
|
|
52
|
+
* Colour theme for the code editor, INDEPENDENT of `theme` above.
|
|
53
|
+
*
|
|
54
|
+
* `auto` (the default) keeps the editor on the app palette. Any other value
|
|
55
|
+
* is a Shiki theme id from `EDITOR_THEMES` — the editor then paints its own
|
|
56
|
+
* surface, gutter, selection and tokens while the console around it keeps the
|
|
57
|
+
* app palette. `resolveEditorTheme` treats an id this build no longer ships
|
|
58
|
+
* as `auto`, so a downgrade renders the app palette rather than half a theme.
|
|
59
|
+
*/
|
|
60
|
+
editorTheme: string;
|
|
51
61
|
/** Generate a session title from the first run. omp suppresses its
|
|
52
62
|
* own auto-titling under `--mode rpc-ui` (PI_NO_TITLE), so without this a
|
|
53
63
|
* Kabmo session keeps its `New Session - <timestamp>` placeholder. Asked
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* JSONL discovery and use the omp session UUID (string) as id — hence the
|
|
4
4
|
* union with string ids. Mock/demo mode keeps numeric SQLite ids.
|
|
5
5
|
*/
|
|
6
|
+
import type { SessionWorktreeInfo } from '@/shared/lib/workspace/worktree/types';
|
|
7
|
+
|
|
6
8
|
export interface SessionItemData {
|
|
7
9
|
id: number | string;
|
|
8
10
|
folder_id: number | string;
|
|
@@ -38,6 +40,18 @@ export interface SessionItemData {
|
|
|
38
40
|
* saying so. Absent means the row is not waiting (the normal case).
|
|
39
41
|
*/
|
|
40
42
|
waitingFor?: { waitId: string; sessionId: string; title: string | null };
|
|
43
|
+
/**
|
|
44
|
+
* The directory the session actually runs in. A worktree session's cwd is the
|
|
45
|
+
* linked checkout, not the folder's `project_path` — the difference the panels
|
|
46
|
+
* have to honour and the sidebar badge names.
|
|
47
|
+
*/
|
|
48
|
+
cwd?: string;
|
|
49
|
+
/**
|
|
50
|
+
* The worktree this session runs in, when its cwd is a linked checkout.
|
|
51
|
+
* Absent for an ordinary session (the common case) and for a session whose cwd
|
|
52
|
+
* is outside every worktree of its project, which the row draws the same way.
|
|
53
|
+
*/
|
|
54
|
+
worktree?: SessionWorktreeInfo;
|
|
41
55
|
}
|
|
42
56
|
|
|
43
57
|
/**
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The Files panel harness's fake disk, and the two writes a spec drives it with.
|
|
8
|
+
*
|
|
9
|
+
* A separate module because the harness file is at the repository's line
|
|
10
|
+
* ceiling, and because the disk is the part with its own rules: a create and a
|
|
11
|
+
* rename MUTATE it the way the real route mutates the filesystem, so the very
|
|
12
|
+
* next read agrees with the row the panel just drew. A stub that answered
|
|
13
|
+
* "created" while leaving the folder empty would make every local-edit spec pass
|
|
14
|
+
* against a tree the server would contradict.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** The fake disk: `path -> child -> (null for a folder, 'file' for a file)`. */
|
|
18
|
+
export type Disk = Record<string, Record<string, null | 'file'> | undefined>;
|
|
19
|
+
|
|
20
|
+
/** One directory's own listing, created on demand. */
|
|
21
|
+
function listingOf(disk: Disk, path: string): Record<string, null | 'file'> {
|
|
22
|
+
disk[path] = disk[path] ?? {};
|
|
23
|
+
return disk[path]!;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Apply a create: every directory on the way exists afterwards, and the entry
|
|
28
|
+
* sits in its parent's listing. Returns the entry's path relative to the root.
|
|
29
|
+
*/
|
|
30
|
+
export function writeToDisk(disk: Disk, folder: string, name: string, kind: 'file' | 'folder'): string {
|
|
31
|
+
const segments = name.split('/');
|
|
32
|
+
let parent = folder === '.' ? '' : folder;
|
|
33
|
+
for (const segment of segments.slice(0, -1)) {
|
|
34
|
+
listingOf(disk, parent || '.')[segment] = null;
|
|
35
|
+
parent = parent ? `${parent}/${segment}` : segment;
|
|
36
|
+
listingOf(disk, parent);
|
|
37
|
+
}
|
|
38
|
+
const leaf = segments[segments.length - 1];
|
|
39
|
+
listingOf(disk, parent || '.')[leaf] = kind === 'folder' ? null : 'file';
|
|
40
|
+
const created = parent ? `${parent}/${leaf}` : leaf;
|
|
41
|
+
if (kind === 'folder') listingOf(disk, created);
|
|
42
|
+
return created;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Split a relative path into its directory (`.` at the root) and its name. */
|
|
46
|
+
function splitPath(path: string): { dir: string; name: string } {
|
|
47
|
+
const cut = path.lastIndexOf('/');
|
|
48
|
+
return cut === -1 ? { dir: '.', name: path } : { dir: path.slice(0, cut), name: path.slice(cut + 1) };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Apply a rename: remove the old entry, write the new one, kind preserved. */
|
|
52
|
+
export function moveOnDisk(disk: Disk, fromPath: string, toPath: string): void {
|
|
53
|
+
const from = splitPath(fromPath);
|
|
54
|
+
const source = disk[from.dir];
|
|
55
|
+
const kind = source?.[from.name] === null ? 'folder' : 'file';
|
|
56
|
+
if (source) delete source[from.name];
|
|
57
|
+
const to = splitPath(toPath);
|
|
58
|
+
writeToDisk(disk, to.dir, to.name, kind);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Whether `path` already names an entry of the fake disk. */
|
|
62
|
+
export function diskHas(disk: Disk, path: string): boolean {
|
|
63
|
+
const { dir, name } = splitPath(path);
|
|
64
|
+
return (disk[dir] ?? {})[name] !== undefined;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** One directory's entries as the server emits them: folders with `children: null`. */
|
|
68
|
+
export function entriesOf(disk: Disk, path: string): unknown[] {
|
|
69
|
+
const dir = disk[path] ?? {};
|
|
70
|
+
return Object.keys(dir).map((child) => {
|
|
71
|
+
const rel = path === '.' ? child : `${path}/${child}`;
|
|
72
|
+
return dir[child] === null
|
|
73
|
+
? { id: rel, name: child, type: 'folder', path: rel, children: null, ignored: false }
|
|
74
|
+
: { id: rel, name: child, type: 'file', path: rel, ignored: false };
|
|
75
|
+
});
|
|
76
|
+
}
|