@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,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The New worktree dialog, rendered.
|
|
8
|
+
*
|
|
9
|
+
* Three rules here are the ones a reader would otherwise discover in a browser
|
|
10
|
+
* and nowhere else:
|
|
11
|
+
*
|
|
12
|
+
* - the generated branch is in the FIRST paint (before any effect flushes),
|
|
13
|
+
* because the window between paint and the effect is exactly when a user
|
|
14
|
+
* types — measured: a field filled with `main` was replaced by the generated
|
|
15
|
+
* name and the dialog created `wt/calm-iguana` instead of reporting that
|
|
16
|
+
* `main` is taken;
|
|
17
|
+
* - the folder follows the branch, so it is never named after a previous one;
|
|
18
|
+
* - a closed dialog renders nothing at all, so no field is focusable while it
|
|
19
|
+
* is shut.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { afterAll, afterEach, beforeAll, describe, expect, test } from 'bun:test';
|
|
23
|
+
import { Window } from 'happy-dom';
|
|
24
|
+
import { h, render } from 'preact';
|
|
25
|
+
import { act } from 'preact/test-utils';
|
|
26
|
+
import { installDomGlobals, restoreDomGlobals } from '@/test-support/pristine-globals';
|
|
27
|
+
import { NewWorktreeModal } from '@/client/components/workspace/worktree-modal/index';
|
|
28
|
+
|
|
29
|
+
const nativeFetch = Bun.fetch;
|
|
30
|
+
const mounted: HTMLElement[] = [];
|
|
31
|
+
|
|
32
|
+
beforeAll(() => {
|
|
33
|
+
installDomGlobals(new Window({ url: 'http://localhost' }));
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
afterEach(() => {
|
|
37
|
+
for (const host of mounted.splice(0)) {
|
|
38
|
+
render(null, host);
|
|
39
|
+
host.remove();
|
|
40
|
+
}
|
|
41
|
+
(globalThis as unknown as Record<string, unknown>).fetch = nativeFetch;
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
afterAll(() => {
|
|
45
|
+
restoreDomGlobals();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Mount, with the first render FLUSHED. Preact batches a state update into a
|
|
50
|
+
* microtask, so a `useEffect` that seeds a field has not run when `render`
|
|
51
|
+
* returns — reading the inputs then reads empty ones, which is a property of the
|
|
52
|
+
* test harness and not of the dialog.
|
|
53
|
+
*/
|
|
54
|
+
async function mount(node: h.JSX.Element): Promise<HTMLElement> {
|
|
55
|
+
const host = document.createElement('div');
|
|
56
|
+
document.body.appendChild(host);
|
|
57
|
+
await act(async () => {
|
|
58
|
+
render(node, host);
|
|
59
|
+
});
|
|
60
|
+
mounted.push(host);
|
|
61
|
+
return host;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function textOf(host: HTMLElement): string {
|
|
65
|
+
return (host.textContent ?? '').replace(/\s+/g, ' ').trim();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
describe('NewWorktreeModal', () => {
|
|
69
|
+
test('opens on a generated branch, with the folder following it', async () => {
|
|
70
|
+
const host = await mount(
|
|
71
|
+
h(NewWorktreeModal, { isOpen: true, onClose: () => {}, root: '/repo', folderId: 1, folderName: 'kabmo', onCreated: () => {} }),
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const inputs = [...host.querySelectorAll('input')] as HTMLInputElement[];
|
|
75
|
+
const branch = inputs[0]?.value ?? '';
|
|
76
|
+
const folderName = inputs[2]?.value ?? '';
|
|
77
|
+
expect(branch).toMatch(/^wt\/[a-z]+-[a-z]+$/);
|
|
78
|
+
// The folder is the branch without our namespace, so it is never `wt-x`.
|
|
79
|
+
expect(folderName).toBe(branch.replace(/^wt\//, ''));
|
|
80
|
+
expect(folderName.startsWith('wt-')).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('the generated branch is in the FIRST paint, before any effect flushes', () => {
|
|
84
|
+
const host = document.createElement('div');
|
|
85
|
+
document.body.appendChild(host);
|
|
86
|
+
// Deliberately NOT `mount`: no `act`, so no effect has run. A field seeded
|
|
87
|
+
// by an effect reads empty here, and the browser's own window between paint
|
|
88
|
+
// and the effect flush is exactly when a user types into it.
|
|
89
|
+
render(
|
|
90
|
+
h(NewWorktreeModal, { isOpen: true, onClose: () => {}, root: '/repo', folderId: 1, folderName: 'kabmo', onCreated: () => {} }),
|
|
91
|
+
host,
|
|
92
|
+
);
|
|
93
|
+
mounted.push(host);
|
|
94
|
+
|
|
95
|
+
const inputs = [...host.querySelectorAll('input')] as HTMLInputElement[];
|
|
96
|
+
const branch = inputs[0]?.value ?? '';
|
|
97
|
+
expect(branch).toMatch(/^wt\/[a-z]+-[a-z]+$/);
|
|
98
|
+
// One generated name for both fields: deriving the folder from a SECOND
|
|
99
|
+
// call would show a folder the branch does not have.
|
|
100
|
+
expect(inputs[2]?.value ?? '').toBe(branch.replace(/^wt\//, ''));
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('states what happens to uncommitted changes, and on which project', async () => {
|
|
104
|
+
const host = await mount(
|
|
105
|
+
h(NewWorktreeModal, { isOpen: true, onClose: () => {}, root: '/repo', folderId: 1, folderName: 'kabmo', onCreated: () => {} }),
|
|
106
|
+
);
|
|
107
|
+
const text = textOf(host);
|
|
108
|
+
expect(text).toContain('Uncommitted changes stay in');
|
|
109
|
+
expect(text).toContain('kabmo');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('renders nothing while closed', async () => {
|
|
113
|
+
const host = await mount(
|
|
114
|
+
h(NewWorktreeModal, { isOpen: false, onClose: () => {}, root: '/repo', folderId: 1, onCreated: () => {} }),
|
|
115
|
+
);
|
|
116
|
+
expect(host.textContent).toBe('');
|
|
117
|
+
});
|
|
118
|
+
});
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* New worktree — the dialog, modelled on OpenChamber's.
|
|
8
|
+
*
|
|
9
|
+
* Two columns on a wide screen and one on a phone, and the split is the point:
|
|
10
|
+
* the left column is the CHOICE (which branch this checkout is), the right is the
|
|
11
|
+
* NAMES (branch, base, folder). A single column put the choice above a wall of
|
|
12
|
+
* fields and the "Create worktree" button below the fold.
|
|
13
|
+
*
|
|
14
|
+
* Three rules the form carries, each from a measured failure:
|
|
15
|
+
*
|
|
16
|
+
* - **The folder follows the branch until the user edits it.** A dialog that
|
|
17
|
+
* asked for both independently produced a folder named after the previous
|
|
18
|
+
* branch the moment the branch was retyped; the reset button puts it back in
|
|
19
|
+
* sync and is disabled while it already is.
|
|
20
|
+
* - **Validation is the SERVER's.** A branch collision, a branch already checked
|
|
21
|
+
* out, an unborn repository — the client cannot know any of them without git,
|
|
22
|
+
* so the debounced call runs the same plan the create will run.
|
|
23
|
+
* - **The dirty notice states what stays behind.** Uncommitted changes are not
|
|
24
|
+
* carried into a new worktree, and a user told that afterwards has already
|
|
25
|
+
* lost the chance to commit them.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { useEffect, useMemo, useState } from 'preact/hooks';
|
|
29
|
+
import { Modal } from '@/client/components/common/Modal';
|
|
30
|
+
import { createWorktree, validateWorktree } from '@/client/hooks/workspace/worktree/index';
|
|
31
|
+
import { WorktreeRequestError } from '@/client/hooks/workspace/worktree/index';
|
|
32
|
+
import { defaultWorktreeBranch, folderNameForBranch } from '@/shared/lib/workspace/worktree/branch-name';
|
|
33
|
+
import type { WorktreeFailure } from '@/shared/lib/workspace/worktree/types';
|
|
34
|
+
|
|
35
|
+
export interface NewWorktreeModalProps {
|
|
36
|
+
isOpen: boolean;
|
|
37
|
+
onClose: () => void;
|
|
38
|
+
/** The project the worktree belongs to. */
|
|
39
|
+
root: string | null;
|
|
40
|
+
folderId: number | null;
|
|
41
|
+
folderName?: string;
|
|
42
|
+
/** Called with the created path — the caller selects it as the chat's target. */
|
|
43
|
+
onCreated: (worktree: { path: string; branch: string }) => void;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
type Mode = 'new' | 'existing';
|
|
47
|
+
|
|
48
|
+
/** A thrown request, narrowed to the failure the dialog reports. */
|
|
49
|
+
function asFailure(error: unknown): WorktreeFailure {
|
|
50
|
+
if (error instanceof WorktreeRequestError) return error.failure;
|
|
51
|
+
return { code: 'create_failed', message: error instanceof Error ? error.message : String(error) };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const FIELD_CLASS = 'w-full h-8 rounded-md border border-ink/15 bg-paper px-2 text-xs text-ink outline-none focus:border-ink/40';
|
|
55
|
+
|
|
56
|
+
export function NewWorktreeModal(props: NewWorktreeModalProps) {
|
|
57
|
+
// Mounted fresh on every open, never reset by an effect. Preact runs effects
|
|
58
|
+
// AFTER paint, so an effect-seeded form is on screen and focusable while its
|
|
59
|
+
// fields still hold the previous open's values — and anything typed in that
|
|
60
|
+
// window is discarded. Measured in the browser: the branch field was filled
|
|
61
|
+
// with `main`, the effect then restored the generated name, and the dialog
|
|
62
|
+
// created `wt/calm-iguana` instead of reporting that `main` is taken.
|
|
63
|
+
if (!props.isOpen) return null;
|
|
64
|
+
return <WorktreeForm {...props} />;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function WorktreeForm({ onClose, root, folderId, folderName, onCreated }: NewWorktreeModalProps) {
|
|
68
|
+
const [mode, setMode] = useState<Mode>('new');
|
|
69
|
+
// Seeded by the initial state, so the very first paint carries a usable form.
|
|
70
|
+
const [branch, setBranch] = useState(defaultWorktreeBranch);
|
|
71
|
+
const [basedOn, setBasedOn] = useState('');
|
|
72
|
+
const [folder, setFolder] = useState(() => folderNameForBranch(branch));
|
|
73
|
+
const [folderFollowsBranch, setFolderFollowsBranch] = useState(true);
|
|
74
|
+
const [failure, setFailure] = useState<WorktreeFailure | null>(null);
|
|
75
|
+
const [creating, setCreating] = useState(false);
|
|
76
|
+
|
|
77
|
+
// The folder mirrors the branch while the user has not renamed it.
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (!folderFollowsBranch) return;
|
|
80
|
+
setFolder(folderNameForBranch(branch));
|
|
81
|
+
}, [branch, folderFollowsBranch]);
|
|
82
|
+
|
|
83
|
+
const request = useMemo(
|
|
84
|
+
() => ({
|
|
85
|
+
mode,
|
|
86
|
+
branch: branch.trim(),
|
|
87
|
+
worktreeName: folder.trim(),
|
|
88
|
+
...(basedOn.trim() ? { startRef: basedOn.trim() } : {}),
|
|
89
|
+
}),
|
|
90
|
+
[mode, branch, folder, basedOn],
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
// Debounced server validation, after the first edit — the answers depend on
|
|
94
|
+
// git state a keystroke cannot know.
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
if (!root) return;
|
|
97
|
+
const timer = setTimeout(() => {
|
|
98
|
+
void validateWorktree({ root, folderId }, request).then((result) => {
|
|
99
|
+
setFailure(result.ok ? null : result.failures[0] ?? null);
|
|
100
|
+
}).catch(() => {
|
|
101
|
+
// A validation request that failed says nothing about the form; the
|
|
102
|
+
// create reports what it finds.
|
|
103
|
+
});
|
|
104
|
+
}, 300);
|
|
105
|
+
return () => clearTimeout(timer);
|
|
106
|
+
}, [root, folderId, request]);
|
|
107
|
+
|
|
108
|
+
const canCreate = Boolean(root) && branch.trim().length > 0 && folder.trim().length > 0 && !creating && !failure;
|
|
109
|
+
|
|
110
|
+
const submit = async () => {
|
|
111
|
+
if (!root || !canCreate) return;
|
|
112
|
+
setCreating(true);
|
|
113
|
+
try {
|
|
114
|
+
const created = await createWorktree({ root, folderId }, request);
|
|
115
|
+
onCreated({ path: created.path, branch: created.branch });
|
|
116
|
+
onClose();
|
|
117
|
+
} catch (error) {
|
|
118
|
+
// `WorktreeRequestError` carries the server's field/code; anything else is
|
|
119
|
+
// reported as a create failure with its own message.
|
|
120
|
+
setFailure(asFailure(error));
|
|
121
|
+
} finally {
|
|
122
|
+
setCreating(false);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const choiceColumn = (
|
|
127
|
+
<div className="flex flex-col gap-4">
|
|
128
|
+
<div className="flex flex-col gap-2">
|
|
129
|
+
<span className="text-[11px] font-semibold text-ink">Start from</span>
|
|
130
|
+
{([
|
|
131
|
+
{ id: 'new' as const, label: 'New branch', hint: 'A fresh branch, based on the branch you pick.' },
|
|
132
|
+
{ id: 'existing' as const, label: 'Existing branch', hint: 'Check out a branch you already have.' },
|
|
133
|
+
]).map((option) => (
|
|
134
|
+
<button
|
|
135
|
+
key={option.id}
|
|
136
|
+
type="button"
|
|
137
|
+
role="radio"
|
|
138
|
+
aria-checked={mode === option.id}
|
|
139
|
+
onClick={() => setMode(option.id)}
|
|
140
|
+
className={`rounded-md border px-3 py-2 text-left transition-colors ${
|
|
141
|
+
mode === option.id ? 'border-ink/40 bg-ink/5' : 'border-ink/15 hover:bg-ink/5'
|
|
142
|
+
}`}
|
|
143
|
+
>
|
|
144
|
+
<span className="block text-xs font-medium text-ink">{option.label}</span>
|
|
145
|
+
<span className="block text-[10.5px] text-ink/50">{option.hint}</span>
|
|
146
|
+
</button>
|
|
147
|
+
))}
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
const fieldColumn = (
|
|
153
|
+
<div className="flex flex-col gap-4">
|
|
154
|
+
<label className="flex flex-col gap-1.5">
|
|
155
|
+
<span className="text-[11px] font-semibold text-ink">{mode === 'existing' ? 'Branch' : 'Branch name'}</span>
|
|
156
|
+
<input
|
|
157
|
+
value={branch}
|
|
158
|
+
onChange={(event) => setBranch(event.currentTarget.value)}
|
|
159
|
+
onKeyDown={(event) => {
|
|
160
|
+
if (event.key === 'Enter') {
|
|
161
|
+
event.preventDefault();
|
|
162
|
+
void submit();
|
|
163
|
+
}
|
|
164
|
+
}}
|
|
165
|
+
placeholder="feature/my-awesome-feature"
|
|
166
|
+
className={FIELD_CLASS}
|
|
167
|
+
aria-invalid={failure?.field === 'branch'}
|
|
168
|
+
/>
|
|
169
|
+
</label>
|
|
170
|
+
|
|
171
|
+
<label className="flex flex-col gap-1.5">
|
|
172
|
+
<span className="text-[11px] font-semibold text-ink">Based on</span>
|
|
173
|
+
<input
|
|
174
|
+
value={basedOn}
|
|
175
|
+
onChange={(event) => setBasedOn(event.currentTarget.value)}
|
|
176
|
+
placeholder="origin/main"
|
|
177
|
+
disabled={mode === 'existing'}
|
|
178
|
+
className={`${FIELD_CLASS} disabled:opacity-50`}
|
|
179
|
+
/>
|
|
180
|
+
<span className="text-[10px] text-ink/40">
|
|
181
|
+
Defaults to the repository's default branch. Left empty means its current HEAD.
|
|
182
|
+
</span>
|
|
183
|
+
</label>
|
|
184
|
+
|
|
185
|
+
<label className="flex flex-col gap-1.5">
|
|
186
|
+
<span className="text-[11px] font-semibold text-ink">Folder name</span>
|
|
187
|
+
<span className="flex items-center gap-1.5">
|
|
188
|
+
<input
|
|
189
|
+
value={folder}
|
|
190
|
+
onChange={(event) => {
|
|
191
|
+
setFolder(event.currentTarget.value);
|
|
192
|
+
setFolderFollowsBranch(false);
|
|
193
|
+
}}
|
|
194
|
+
placeholder="my-worktree-directory"
|
|
195
|
+
className={`${FIELD_CLASS} min-w-0 flex-1`}
|
|
196
|
+
aria-invalid={failure?.field === 'folder'}
|
|
197
|
+
/>
|
|
198
|
+
<button
|
|
199
|
+
type="button"
|
|
200
|
+
title="Reset to match branch name"
|
|
201
|
+
aria-label="Reset to match branch name"
|
|
202
|
+
disabled={folderFollowsBranch}
|
|
203
|
+
onClick={() => {
|
|
204
|
+
setFolderFollowsBranch(true);
|
|
205
|
+
setFolder(folderNameForBranch(branch));
|
|
206
|
+
}}
|
|
207
|
+
className="h-8 shrink-0 rounded-md border border-ink/15 px-2 text-[11px] text-ink/70 hover:bg-ink/5 disabled:opacity-40"
|
|
208
|
+
>
|
|
209
|
+
⟳
|
|
210
|
+
</button>
|
|
211
|
+
</span>
|
|
212
|
+
</label>
|
|
213
|
+
|
|
214
|
+
<p className="text-[10px] text-ink/45">
|
|
215
|
+
Uncommitted changes stay in {folderName ? `“${folderName}”` : 'the project'}. A new worktree starts from a
|
|
216
|
+
commit, so nothing half-written comes with it.
|
|
217
|
+
</p>
|
|
218
|
+
</div>
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
return (
|
|
222
|
+
<Modal
|
|
223
|
+
onClose={onClose}
|
|
224
|
+
maxWidthClass="max-w-[min(96vw,880px)]"
|
|
225
|
+
header={<h2 className="text-sm font-semibold text-ink">New worktree</h2>}
|
|
226
|
+
footer={
|
|
227
|
+
<div className="flex w-full items-center gap-2 border-t border-ink/10 px-5 py-3">
|
|
228
|
+
{/* One line that never grows the dialog; the full message is its title. */}
|
|
229
|
+
<p
|
|
230
|
+
className="min-w-0 flex-1 truncate text-[11px] text-error"
|
|
231
|
+
title={failure?.message}
|
|
232
|
+
role={failure ? 'alert' : undefined}
|
|
233
|
+
>
|
|
234
|
+
{failure?.message ?? ''}
|
|
235
|
+
</p>
|
|
236
|
+
<button
|
|
237
|
+
type="button"
|
|
238
|
+
onClick={onClose}
|
|
239
|
+
disabled={creating}
|
|
240
|
+
className="h-8 shrink-0 rounded-md border border-ink/15 px-3 text-xs text-ink/80 hover:bg-ink/5"
|
|
241
|
+
>
|
|
242
|
+
Cancel
|
|
243
|
+
</button>
|
|
244
|
+
<button
|
|
245
|
+
type="button"
|
|
246
|
+
onClick={() => void submit()}
|
|
247
|
+
disabled={!canCreate}
|
|
248
|
+
className="h-8 shrink-0 rounded-md bg-ink px-3 text-xs font-medium text-paper hover:bg-ink/90 disabled:opacity-40"
|
|
249
|
+
>
|
|
250
|
+
{creating ? 'Creating…' : 'Create worktree'}
|
|
251
|
+
</button>
|
|
252
|
+
</div>
|
|
253
|
+
}
|
|
254
|
+
>
|
|
255
|
+
<div className="grid grid-cols-1 gap-6 px-5 py-4 md:grid-cols-2">
|
|
256
|
+
{choiceColumn}
|
|
257
|
+
{fieldColumn}
|
|
258
|
+
</div>
|
|
259
|
+
</Modal>
|
|
260
|
+
);
|
|
261
|
+
}
|