@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
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { AlignJustify, Check, Code2, Columns, Copy, Diff, FileCode, Minus, Plus, RefreshCw, Space, Undo2, WrapText } from 'lucide-preact';
|
|
2
2
|
import { FileIcon } from '@/client/components/common/file-icon';
|
|
3
|
+
import { EditorViewButtons, useEditorViewActions } from '@/client/components/common/editor-view-buttons';
|
|
3
4
|
import { getGitStatusInfo } from '@/shared/lib/fs/git-status';
|
|
5
|
+
import { ToolbarRow, type ToolbarCluster } from '@/client/components/common/toolbar-row';
|
|
6
|
+
import { DiffContentModeSegments, DiffViewModeSegments } from '@/client/components/workspace/diff-panel/ToolbarSegments';
|
|
4
7
|
|
|
5
8
|
export type DiffViewMode = 'unified' | 'split';
|
|
6
9
|
|
|
@@ -26,6 +29,12 @@ interface DiffToolbarProps {
|
|
|
26
29
|
isCopied: boolean;
|
|
27
30
|
/** A stage/unstage/discard write is in flight; the toolbar locks its actions. */
|
|
28
31
|
isBusy?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Offer the two WIDTH-mode buttons (compact / full view). Desktop-only: the
|
|
34
|
+
* phone renders this same panel in a full-screen overlay, where there is no
|
|
35
|
+
* chat column or sidebar beside it for a mode to widen into.
|
|
36
|
+
*/
|
|
37
|
+
viewModeButtons?: boolean;
|
|
29
38
|
onSetViewMode: (mode: DiffViewMode) => void;
|
|
30
39
|
onSetContentMode: (mode: DiffContentMode) => void;
|
|
31
40
|
onToggleWordWrap: () => void;
|
|
@@ -53,6 +62,7 @@ export function DiffToolbar({
|
|
|
53
62
|
isLoading,
|
|
54
63
|
isCopied,
|
|
55
64
|
isBusy = false,
|
|
65
|
+
viewModeButtons = true,
|
|
56
66
|
onSetViewMode,
|
|
57
67
|
onSetContentMode,
|
|
58
68
|
onToggleWordWrap,
|
|
@@ -63,219 +73,247 @@ export function DiffToolbar({
|
|
|
63
73
|
onRefresh,
|
|
64
74
|
onCopyDiff,
|
|
65
75
|
}: DiffToolbarProps) {
|
|
76
|
+
const viewModeActions = useEditorViewActions();
|
|
66
77
|
const fileName = filePath.split('/').pop() || filePath;
|
|
67
78
|
const statusInfo = getGitStatusInfo(status, isStaged);
|
|
68
79
|
// Only the actions that write to git lock while one is in flight. Copying the
|
|
69
80
|
// diff is a local read of what is already on screen, so it stays available.
|
|
70
81
|
const locked = isLoading || isBusy;
|
|
71
82
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
<div className="@container flex items-center justify-between px-3 py-1.5 bg-paper border-b border-ink/10 text-xs font-sans select-none flex-shrink-0 gap-2 min-w-0">
|
|
77
|
-
{/* Left: File name and status badge. Hidden when the panel is narrow —
|
|
78
|
-
the editor tab already carries the name and the status character, and
|
|
79
|
-
the mobile diff header carries both. */}
|
|
80
|
-
<div className="hidden @[560px]:flex items-center space-x-2 min-w-0 pr-2">
|
|
81
|
-
<FileIcon name={fileName} size={14} className="flex-shrink-0" />
|
|
82
|
-
<span className="font-mono text-ink font-medium truncate" title={filePath}>
|
|
83
|
-
{fileName}
|
|
84
|
-
</span>
|
|
83
|
+
const stageLabel = isStaged ? 'Unstage file' : 'Stage file';
|
|
84
|
+
const wrapLabel = wordWrap ? 'Word wrap on' : 'Word wrap off';
|
|
85
|
+
const whitespaceLabel = ignoreWhitespace ? 'Whitespace changes hidden' : 'Hide whitespace changes';
|
|
86
|
+
const copyLabel = isCopied ? 'Copied' : 'Copy diff text';
|
|
85
87
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
outside it and no scrollbar to say so. Wrapping costs one extra row on
|
|
97
|
-
a narrow panel and keeps every action reachable. Each segment sets its
|
|
98
|
-
OWN mode — toggling meant clicking the already-active segment flipped
|
|
99
|
-
the view to the other one. */}
|
|
100
|
-
<div className="flex flex-wrap items-center justify-end gap-x-0.5 @[440px]:gap-x-1 gap-y-1 text-ink/70 ml-auto">
|
|
101
|
-
{/* Change counts live here, not in the identity block: the editor tab
|
|
102
|
-
carries the file name but never the counts, so this is the one piece
|
|
103
|
-
of the header that must survive a narrow panel. */}
|
|
104
|
-
{(additions > 0 || deletions > 0) && (
|
|
105
|
-
<div className="flex items-center space-x-1.5 text-[11px] font-mono flex-shrink-0 pr-1.5 mr-0.5 @[440px]:mr-1 border-r border-ink/10">
|
|
106
|
-
{additions > 0 && <span className="text-success font-semibold">+{additions}</span>}
|
|
107
|
-
{deletions > 0 && <span className="text-error font-semibold">-{deletions}</span>}
|
|
108
|
-
</div>
|
|
109
|
-
)}
|
|
88
|
+
/**
|
|
89
|
+
* The controls, as clusters that hide as a unit.
|
|
90
|
+
*
|
|
91
|
+
* ORDER is the visual order a reader looks for. PINNING says what a narrow
|
|
92
|
+
* panel must not bury: the width modes (the reason these buttons exist), Stage
|
|
93
|
+
* (the panel's primary write) and Edit File (the way out of the diff). The
|
|
94
|
+
* reading options — Unified/Split, Full/Diff, wrap, whitespace — are what a
|
|
95
|
+
* `⋯` menu is for; they change how the diff READS, not what it does.
|
|
96
|
+
*/
|
|
97
|
+
const clusters: ToolbarCluster[] = [];
|
|
110
98
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
>
|
|
123
|
-
<Columns size={12} />
|
|
124
|
-
<span className="hidden @[420px]:inline">Split</span>
|
|
125
|
-
</button>
|
|
126
|
-
<button
|
|
127
|
-
type="button"
|
|
128
|
-
onClick={() => onSetViewMode('unified')}
|
|
129
|
-
className={`px-2 py-0.5 rounded text-[11px] font-medium flex items-center space-x-1 transition-colors cursor-pointer flex-shrink-0 ${
|
|
130
|
-
viewMode === 'unified' ? 'bg-paper text-ink shadow-xs' : 'text-ink/60 hover:text-ink'
|
|
131
|
-
}`}
|
|
132
|
-
title="Unified diff"
|
|
133
|
-
aria-label="Unified diff"
|
|
134
|
-
aria-pressed={viewMode === 'unified'}
|
|
135
|
-
>
|
|
136
|
-
<AlignJustify size={12} />
|
|
137
|
-
<span className="hidden @[420px]:inline">Unified</span>
|
|
138
|
-
</button>
|
|
99
|
+
if (additions > 0 || deletions > 0) {
|
|
100
|
+
clusters.push({
|
|
101
|
+
id: 'counts',
|
|
102
|
+
// The only piece of the header that survives everywhere: the editor tab
|
|
103
|
+
// carries the file name but never the counts.
|
|
104
|
+
pinned: true,
|
|
105
|
+
actions: [],
|
|
106
|
+
children: (
|
|
107
|
+
<div className="flex items-center space-x-1.5 text-[11px] font-mono pr-1.5 mr-0.5 @[440px]:mr-1 border-r border-ink/10">
|
|
108
|
+
{additions > 0 && <span className="text-success font-semibold">+{additions}</span>}
|
|
109
|
+
{deletions > 0 && <span className="text-error font-semibold">-{deletions}</span>}
|
|
139
110
|
</div>
|
|
111
|
+
),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
140
114
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
onClick={() => onSetContentMode('full')}
|
|
150
|
-
className={`px-2 py-0.5 rounded text-[11px] font-medium flex items-center space-x-1 transition-colors cursor-pointer flex-shrink-0 ${
|
|
151
|
-
contentMode === 'full' ? 'bg-paper text-ink shadow-xs' : 'text-ink/60 hover:text-ink'
|
|
152
|
-
}`}
|
|
153
|
-
title="Full code (whole file, changes marked)"
|
|
154
|
-
aria-label="Full code (whole file, changes marked)"
|
|
155
|
-
aria-pressed={contentMode === 'full'}
|
|
156
|
-
>
|
|
157
|
-
<FileCode size={12} />
|
|
158
|
-
<span className="hidden @[420px]:inline">Full Code</span>
|
|
159
|
-
</button>
|
|
160
|
-
<button
|
|
161
|
-
type="button"
|
|
162
|
-
onClick={() => onSetContentMode('diff')}
|
|
163
|
-
className={`px-2 py-0.5 rounded text-[11px] font-medium flex items-center space-x-1 transition-colors cursor-pointer flex-shrink-0 ${
|
|
164
|
-
contentMode === 'diff' ? 'bg-paper text-ink shadow-xs' : 'text-ink/60 hover:text-ink'
|
|
165
|
-
}`}
|
|
166
|
-
title="Diff only (changed hunks)"
|
|
167
|
-
aria-label="Diff only (changed hunks)"
|
|
168
|
-
aria-pressed={contentMode === 'diff'}
|
|
169
|
-
>
|
|
170
|
-
<Diff size={12} />
|
|
171
|
-
<span className="hidden @[420px]:inline">Diff Only</span>
|
|
172
|
-
</button>
|
|
173
|
-
</div>
|
|
115
|
+
clusters.push({
|
|
116
|
+
id: 'view-mode',
|
|
117
|
+
actions: [
|
|
118
|
+
{ label: 'Split (side-by-side) diff', icon: Columns, onSelect: () => onSetViewMode('split'), active: viewMode === 'split' },
|
|
119
|
+
{ label: 'Unified diff', icon: AlignJustify, onSelect: () => onSetViewMode('unified'), active: viewMode === 'unified' },
|
|
120
|
+
],
|
|
121
|
+
children: <DiffViewModeSegments viewMode={viewMode} onSetViewMode={onSetViewMode} />,
|
|
122
|
+
});
|
|
174
123
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
>
|
|
189
|
-
<WrapText size={13} />
|
|
190
|
-
</button>
|
|
124
|
+
clusters.push({
|
|
125
|
+
id: 'content-mode',
|
|
126
|
+
actions: [
|
|
127
|
+
{
|
|
128
|
+
label: 'Full code (whole file, changes marked)',
|
|
129
|
+
icon: FileCode,
|
|
130
|
+
onSelect: () => onSetContentMode('full'),
|
|
131
|
+
active: contentMode === 'full',
|
|
132
|
+
},
|
|
133
|
+
{ label: 'Diff only (changed hunks)', icon: Diff, onSelect: () => onSetContentMode('diff'), active: contentMode === 'diff' },
|
|
134
|
+
],
|
|
135
|
+
children: <DiffContentModeSegments contentMode={contentMode} onSetContentMode={onSetContentMode} />,
|
|
136
|
+
});
|
|
191
137
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
138
|
+
clusters.push({
|
|
139
|
+
id: 'wrap',
|
|
140
|
+
actions: [{ label: wrapLabel, icon: WrapText, onSelect: onToggleWordWrap, active: wordWrap }],
|
|
141
|
+
children: (
|
|
142
|
+
<button
|
|
143
|
+
type="button"
|
|
144
|
+
onClick={onToggleWordWrap}
|
|
145
|
+
className={`p-1.5 rounded transition-colors cursor-pointer flex-shrink-0 ${
|
|
146
|
+
wordWrap ? 'bg-ink/10 text-ink' : 'text-ink/50 hover:text-ink hover:bg-ink/5'
|
|
147
|
+
}`}
|
|
148
|
+
title={wrapLabel}
|
|
149
|
+
aria-label={wrapLabel}
|
|
150
|
+
aria-pressed={wordWrap}
|
|
151
|
+
>
|
|
152
|
+
<WrapText size={13} />
|
|
153
|
+
</button>
|
|
154
|
+
),
|
|
155
|
+
});
|
|
205
156
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
157
|
+
clusters.push({
|
|
158
|
+
id: 'whitespace',
|
|
159
|
+
actions: [{ label: whitespaceLabel, icon: Space, onSelect: onToggleWhitespace, active: ignoreWhitespace }],
|
|
160
|
+
children: (
|
|
161
|
+
<button
|
|
162
|
+
type="button"
|
|
163
|
+
onClick={onToggleWhitespace}
|
|
164
|
+
className={`p-1.5 rounded transition-colors cursor-pointer flex-shrink-0 ${
|
|
165
|
+
ignoreWhitespace ? 'bg-ink/10 text-ink' : 'text-ink/50 hover:text-ink hover:bg-ink/5'
|
|
166
|
+
}`}
|
|
167
|
+
title={whitespaceLabel}
|
|
168
|
+
aria-label={whitespaceLabel}
|
|
169
|
+
aria-pressed={ignoreWhitespace}
|
|
170
|
+
>
|
|
171
|
+
<Space size={13} />
|
|
172
|
+
</button>
|
|
173
|
+
),
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
if (viewModeButtons) {
|
|
177
|
+
clusters.push({
|
|
178
|
+
id: 'view-modes',
|
|
179
|
+
// The same two buttons the editor toolbar draws, in the same order, so a
|
|
180
|
+
// mode entered from either tab kind is one mode.
|
|
181
|
+
pinned: true,
|
|
182
|
+
actions: viewModeActions,
|
|
183
|
+
children: <EditorViewButtons size={13} />,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
clusters.push({
|
|
188
|
+
id: 'refresh',
|
|
189
|
+
actions: [{ label: 'Reload this diff', icon: RefreshCw, onSelect: onRefresh, disabled: locked }],
|
|
190
|
+
children: (
|
|
191
|
+
<button
|
|
192
|
+
type="button"
|
|
193
|
+
onClick={onRefresh}
|
|
194
|
+
className={`${ACTION_CLASS} text-ink/50 hover:text-ink hover:bg-ink/5`}
|
|
195
|
+
title="Reload this diff"
|
|
196
|
+
aria-label="Reload this diff"
|
|
197
|
+
disabled={locked}
|
|
198
|
+
>
|
|
199
|
+
<RefreshCw size={13} className={isLoading ? 'animate-spin' : ''} />
|
|
200
|
+
</button>
|
|
201
|
+
),
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
clusters.push({
|
|
205
|
+
id: 'copy',
|
|
206
|
+
// Never disabled: it reads the buffer already on screen.
|
|
207
|
+
actions: [{ label: copyLabel, icon: Copy, onSelect: onCopyDiff }],
|
|
208
|
+
children: (
|
|
209
|
+
<button
|
|
210
|
+
type="button"
|
|
211
|
+
onClick={onCopyDiff}
|
|
212
|
+
className={`${ACTION_CLASS} text-ink/50 hover:text-ink hover:bg-ink/5`}
|
|
213
|
+
title={copyLabel}
|
|
214
|
+
aria-label={copyLabel}
|
|
215
|
+
>
|
|
216
|
+
{isCopied ? <Check size={13} className="text-success" /> : <Copy size={13} />}
|
|
217
|
+
</button>
|
|
218
|
+
),
|
|
219
|
+
});
|
|
217
220
|
|
|
218
|
-
|
|
221
|
+
if (!isStaged) {
|
|
222
|
+
clusters.push({
|
|
223
|
+
id: 'discard',
|
|
224
|
+
actions: [{ label: 'Discard changes in this file', icon: Undo2, onSelect: onDiscard, disabled: locked }],
|
|
225
|
+
children: (
|
|
219
226
|
<button
|
|
220
227
|
type="button"
|
|
221
|
-
onClick={
|
|
222
|
-
className={`${ACTION_CLASS} text-ink/50 hover:text-
|
|
223
|
-
title=
|
|
224
|
-
aria-label=
|
|
228
|
+
onClick={onDiscard}
|
|
229
|
+
className={`${ACTION_CLASS} text-ink/50 hover:text-error hover:bg-error/10`}
|
|
230
|
+
title="Discard changes in this file"
|
|
231
|
+
aria-label="Discard changes in this file"
|
|
232
|
+
disabled={locked}
|
|
225
233
|
>
|
|
226
|
-
|
|
234
|
+
<Undo2 size={13} />
|
|
227
235
|
</button>
|
|
236
|
+
),
|
|
237
|
+
});
|
|
238
|
+
}
|
|
228
239
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
240
|
+
clusters.push({
|
|
241
|
+
id: 'stage',
|
|
242
|
+
// The panel's primary write: a menu row must not be the only way to stage.
|
|
243
|
+
pinned: true,
|
|
244
|
+
actions: [{ label: stageLabel, icon: isStaged ? Minus : Plus, onSelect: onStageUnstage, disabled: locked }],
|
|
245
|
+
children: (
|
|
246
|
+
<button
|
|
247
|
+
type="button"
|
|
248
|
+
onClick={onStageUnstage}
|
|
249
|
+
className="px-2 py-1 text-[11px] font-medium rounded border border-ink/15 hover:bg-ink/5 flex items-center space-x-1 transition-colors text-ink cursor-pointer disabled:cursor-default disabled:opacity-40 disabled:hover:bg-transparent"
|
|
250
|
+
title={stageLabel}
|
|
251
|
+
aria-label={stageLabel}
|
|
252
|
+
disabled={locked}
|
|
253
|
+
>
|
|
254
|
+
{isStaged ? (
|
|
255
|
+
<>
|
|
256
|
+
<Minus size={12} />
|
|
257
|
+
<span className="hidden @[360px]:inline">Unstage</span>
|
|
258
|
+
</>
|
|
259
|
+
) : (
|
|
260
|
+
<>
|
|
261
|
+
<Plus size={12} />
|
|
262
|
+
<span className="hidden @[360px]:inline">Stage</span>
|
|
263
|
+
</>
|
|
241
264
|
)}
|
|
265
|
+
</button>
|
|
266
|
+
),
|
|
267
|
+
});
|
|
242
268
|
|
|
243
|
-
|
|
269
|
+
if (onOpenInEditor) {
|
|
270
|
+
clusters.push({
|
|
271
|
+
id: 'edit-file',
|
|
272
|
+
// The way out of the diff, and the only control that changes the SURFACE
|
|
273
|
+
// rather than the view: kept on the row.
|
|
274
|
+
pinned: true,
|
|
275
|
+
actions: [{ label: 'Open in code editor', icon: Code2, onSelect: onOpenInEditor }],
|
|
276
|
+
children: (
|
|
244
277
|
<button
|
|
245
278
|
type="button"
|
|
246
|
-
onClick={
|
|
247
|
-
className="px-2 py-1 text-[11px] font-medium rounded
|
|
248
|
-
title=
|
|
249
|
-
aria-label=
|
|
250
|
-
disabled={locked}
|
|
279
|
+
onClick={onOpenInEditor}
|
|
280
|
+
className="px-2 py-1 text-[11px] font-medium rounded bg-ink text-canvas hover:bg-ink/90 flex items-center space-x-1 transition-colors shadow-xs ml-1 cursor-pointer"
|
|
281
|
+
title="Open in code editor"
|
|
282
|
+
aria-label="Open in code editor"
|
|
251
283
|
>
|
|
252
|
-
{
|
|
253
|
-
|
|
254
|
-
<Minus size={12} />
|
|
255
|
-
<span className="hidden @[360px]:inline">Unstage</span>
|
|
256
|
-
</>
|
|
257
|
-
) : (
|
|
258
|
-
<>
|
|
259
|
-
<Plus size={12} />
|
|
260
|
-
<span className="hidden @[360px]:inline">Stage</span>
|
|
261
|
-
</>
|
|
262
|
-
)}
|
|
284
|
+
<Code2 size={12} />
|
|
285
|
+
<span className="hidden @[360px]:inline">Edit File</span>
|
|
263
286
|
</button>
|
|
287
|
+
),
|
|
288
|
+
});
|
|
289
|
+
}
|
|
264
290
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
291
|
+
return (
|
|
292
|
+
// `@container` so the labels below can hide on a NARROW PANEL rather than a
|
|
293
|
+
// narrow viewport: the editor panel is 320px wide on a 1440px screen, and
|
|
294
|
+
// `sm:` (viewport-keyed) kept every label on, overflowing the toolbar.
|
|
295
|
+
<div className="@container flex items-center justify-between px-3 py-1.5 bg-paper border-b border-ink/10 text-xs font-sans select-none flex-shrink-0 gap-2 min-w-0">
|
|
296
|
+
{/* Left: File name and status badge. Hidden when the panel is narrow —
|
|
297
|
+
the editor tab already carries the name and the status character, and
|
|
298
|
+
the mobile diff header carries both. */}
|
|
299
|
+
<div className="hidden @[560px]:flex items-center space-x-2 min-w-0 pr-2">
|
|
300
|
+
<FileIcon name={fileName} size={14} className="flex-shrink-0" />
|
|
301
|
+
<span className="font-mono text-ink font-medium truncate" title={filePath}>
|
|
302
|
+
{fileName}
|
|
303
|
+
</span>
|
|
304
|
+
|
|
305
|
+
<span className={`px-1.5 py-0.5 text-[10px] font-mono font-semibold rounded border ${statusInfo.badgeBgClass} flex-shrink-0`}>
|
|
306
|
+
{statusInfo.charStatus} {statusInfo.label}
|
|
307
|
+
</span>
|
|
278
308
|
</div>
|
|
309
|
+
|
|
310
|
+
{/* Right: Controls & actions. ONE row, never two: what does not fit moves
|
|
311
|
+
into `⋯` (see `ToolbarRow`), because a wrapped strip moved the diff
|
|
312
|
+
down by its own height and read as a second toolbar, while the
|
|
313
|
+
`overflow-x-auto` before that clipped the tail invisibly (measured:
|
|
314
|
+
361px of content in a 296px box, `Stage` entirely outside it).
|
|
315
|
+
Each segment SETS its own mode — a toggle would flip the other one. */}
|
|
316
|
+
<ToolbarRow clusters={clusters} className="text-ink/70 ml-auto" />
|
|
279
317
|
</div>
|
|
280
318
|
);
|
|
281
319
|
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The diff toolbar's two segmented controls.
|
|
8
|
+
*
|
|
9
|
+
* Split out because they are the widest part of the toolbar (a segment pair is
|
|
10
|
+
* 130-190px, and the panel's floor is 320px) and because the pair carries a rule
|
|
11
|
+
* of its own: each segment SETS its mode rather than toggling it. Clicking the
|
|
12
|
+
* already-active segment used to flip the view to the other one, which made the
|
|
13
|
+
* control mean two things depending on which half the reader aimed at.
|
|
14
|
+
*
|
|
15
|
+
* A separate group from each other because the two axes are orthogonal:
|
|
16
|
+
* Unified/Split is the SHAPE of the rows, Full/Diff is how MANY of them there
|
|
17
|
+
* are.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { AlignJustify, Columns, Diff, FileCode } from 'lucide-preact';
|
|
21
|
+
import type { DiffContentMode, DiffViewMode } from '@/client/components/workspace/diff-panel/Toolbar';
|
|
22
|
+
|
|
23
|
+
const SEGMENT_CLASS =
|
|
24
|
+
'px-2 py-0.5 rounded text-[11px] font-medium flex items-center space-x-1 transition-colors cursor-pointer flex-shrink-0';
|
|
25
|
+
|
|
26
|
+
const GROUP_CLASS = 'flex items-center bg-canvas rounded border border-ink/15 p-0.5 mr-0.5 @[440px]:mr-1';
|
|
27
|
+
|
|
28
|
+
/** Row shape: side-by-side or unified. */
|
|
29
|
+
export function DiffViewModeSegments({
|
|
30
|
+
viewMode,
|
|
31
|
+
onSetViewMode,
|
|
32
|
+
}: {
|
|
33
|
+
viewMode: DiffViewMode;
|
|
34
|
+
onSetViewMode: (mode: DiffViewMode) => void;
|
|
35
|
+
}) {
|
|
36
|
+
return (
|
|
37
|
+
<div className={GROUP_CLASS}>
|
|
38
|
+
<button
|
|
39
|
+
type="button"
|
|
40
|
+
onClick={() => onSetViewMode('split')}
|
|
41
|
+
className={`${SEGMENT_CLASS} ${viewMode === 'split' ? 'bg-paper text-ink shadow-xs' : 'text-ink/60 hover:text-ink'}`}
|
|
42
|
+
title="Split (side-by-side) diff"
|
|
43
|
+
aria-label="Split (side-by-side) diff"
|
|
44
|
+
aria-pressed={viewMode === 'split'}
|
|
45
|
+
>
|
|
46
|
+
<Columns size={12} />
|
|
47
|
+
<span className="hidden @[420px]:inline">Split</span>
|
|
48
|
+
</button>
|
|
49
|
+
<button
|
|
50
|
+
type="button"
|
|
51
|
+
onClick={() => onSetViewMode('unified')}
|
|
52
|
+
className={`${SEGMENT_CLASS} ${viewMode === 'unified' ? 'bg-paper text-ink shadow-xs' : 'text-ink/60 hover:text-ink'}`}
|
|
53
|
+
title="Unified diff"
|
|
54
|
+
aria-label="Unified diff"
|
|
55
|
+
aria-pressed={viewMode === 'unified'}
|
|
56
|
+
>
|
|
57
|
+
<AlignJustify size={12} />
|
|
58
|
+
<span className="hidden @[420px]:inline">Unified</span>
|
|
59
|
+
</button>
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** How much of the file: the whole thing with changes marked, or the hunks. */
|
|
65
|
+
export function DiffContentModeSegments({
|
|
66
|
+
contentMode,
|
|
67
|
+
onSetContentMode,
|
|
68
|
+
}: {
|
|
69
|
+
contentMode: DiffContentMode;
|
|
70
|
+
onSetContentMode: (mode: DiffContentMode) => void;
|
|
71
|
+
}) {
|
|
72
|
+
return (
|
|
73
|
+
<div className={GROUP_CLASS}>
|
|
74
|
+
<button
|
|
75
|
+
type="button"
|
|
76
|
+
onClick={() => onSetContentMode('full')}
|
|
77
|
+
className={`${SEGMENT_CLASS} ${contentMode === 'full' ? 'bg-paper text-ink shadow-xs' : 'text-ink/60 hover:text-ink'}`}
|
|
78
|
+
title="Full code (whole file, changes marked)"
|
|
79
|
+
aria-label="Full code (whole file, changes marked)"
|
|
80
|
+
aria-pressed={contentMode === 'full'}
|
|
81
|
+
>
|
|
82
|
+
<FileCode size={12} />
|
|
83
|
+
<span className="hidden @[420px]:inline">Full Code</span>
|
|
84
|
+
</button>
|
|
85
|
+
<button
|
|
86
|
+
type="button"
|
|
87
|
+
onClick={() => onSetContentMode('diff')}
|
|
88
|
+
className={`${SEGMENT_CLASS} ${contentMode === 'diff' ? 'bg-paper text-ink shadow-xs' : 'text-ink/60 hover:text-ink'}`}
|
|
89
|
+
title="Diff only (changed hunks)"
|
|
90
|
+
aria-label="Diff only (changed hunks)"
|
|
91
|
+
aria-pressed={contentMode === 'diff'}
|
|
92
|
+
>
|
|
93
|
+
<Diff size={12} />
|
|
94
|
+
<span className="hidden @[420px]:inline">Diff Only</span>
|
|
95
|
+
</button>
|
|
96
|
+
</div>
|
|
97
|
+
);
|
|
98
|
+
}
|