feat(FN-2944): merge fusion/fn-2944
- test(FN-2944): cover already checked out worktree conflict recovery - fix(FN-2944): recognize git already checked out worktree conflict - fix(engine): auto-recover from squash-merge orphan rebase failures Fusion-Task-Id: FN-2944
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { useState, useCallback, useEffect } from "react";
|
||||
import { Folder, FolderOpen, ChevronRight, ChevronUp, Loader2, Eye, EyeOff, AlertCircle } from "lucide-react";
|
||||
import { browseDirectory, type BrowseDirectoryResult } from "../api";
|
||||
import { getPathBreadcrumbs } from "../utils/pathDisplay";
|
||||
import "./DirectoryPicker.css";
|
||||
|
||||
export interface DirectoryPickerProps {
|
||||
@@ -99,9 +100,7 @@ export function DirectoryPicker({ value, onChange, placeholder, onInputKeyDown,
|
||||
}
|
||||
}, [browser.showHidden]);
|
||||
|
||||
const breadcrumbs = browser.currentPath
|
||||
? browser.currentPath.split("/").filter(Boolean)
|
||||
: [];
|
||||
const breadcrumbs = browser.currentPath ? getPathBreadcrumbs(browser.currentPath) : [];
|
||||
|
||||
return (
|
||||
<div className="directory-picker">
|
||||
@@ -129,26 +128,17 @@ export function DirectoryPicker({ value, onChange, placeholder, onInputKeyDown,
|
||||
<div className="directory-picker-browser" role="tree" aria-label="Directory browser">
|
||||
{/* Breadcrumbs */}
|
||||
<div className="directory-picker-breadcrumbs">
|
||||
<button
|
||||
type="button"
|
||||
className="directory-picker-breadcrumb"
|
||||
onClick={() => handleNavigate("/")}
|
||||
title="Root"
|
||||
>
|
||||
/
|
||||
</button>
|
||||
{breadcrumbs.map((segment, i) => {
|
||||
const segPath = "/" + breadcrumbs.slice(0, i + 1).join("/");
|
||||
{breadcrumbs.map((breadcrumb, index) => {
|
||||
return (
|
||||
<span key={segPath} className="directory-picker-breadcrumb-item">
|
||||
<ChevronRight size={12} className="directory-picker-breadcrumb-sep" />
|
||||
<span key={breadcrumb.path} className="directory-picker-breadcrumb-item">
|
||||
{index > 0 && <ChevronRight size={12} className="directory-picker-breadcrumb-sep" />}
|
||||
<button
|
||||
type="button"
|
||||
className="directory-picker-breadcrumb"
|
||||
onClick={() => handleNavigate(segPath)}
|
||||
title={segPath}
|
||||
onClick={() => handleNavigate(breadcrumb.path)}
|
||||
title={breadcrumb.path}
|
||||
>
|
||||
{segment}
|
||||
{breadcrumb.label}
|
||||
</button>
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { FileNode } from "../api";
|
||||
import { copyFile, moveFile, deleteFile, renameFile, downloadFileUrl, downloadZipUrl } from "../api";
|
||||
import { appendTokenQuery } from "../auth";
|
||||
import { getErrorMessage } from "@fusion/core";
|
||||
import { getParentDisplayPath, joinDisplayPath, normalizeDisplayPath } from "../utils/pathDisplay";
|
||||
|
||||
interface FileBrowserProps {
|
||||
entries: FileNode[];
|
||||
@@ -37,7 +38,7 @@ function formatTime(mtime?: string): string {
|
||||
|
||||
/** Build the full relative path for a file/directory entry */
|
||||
function entryPath(currentPath: string, name: string): string {
|
||||
return currentPath === "." ? name : `${currentPath}/${name}`;
|
||||
return joinDisplayPath(currentPath, name);
|
||||
}
|
||||
|
||||
// ── Context Menu State ──────────────────────────────────────────────────
|
||||
@@ -550,16 +551,14 @@ export function FileBrowser({
|
||||
<button
|
||||
className="file-browser-up"
|
||||
onClick={() => {
|
||||
const parts = currentPath.split("/").filter(Boolean);
|
||||
parts.pop();
|
||||
onNavigate(parts.length === 0 ? "." : parts.join("/"));
|
||||
onNavigate(getParentDisplayPath(currentPath));
|
||||
}}
|
||||
>
|
||||
<ChevronRight size={16} style={{ transform: "rotate(-90deg)" }} />
|
||||
Up one level
|
||||
</button>
|
||||
)}
|
||||
<span className="file-browser-path">{currentPath === "." ? "Root" : currentPath}</span>
|
||||
<span className="file-browser-path">{currentPath === "." ? "Root" : normalizeDisplayPath(currentPath)}</span>
|
||||
</div>
|
||||
|
||||
<div className="file-browser-list">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { File } from "lucide-react";
|
||||
import type { FileSearchItem } from "../hooks/useFileMention";
|
||||
import { getDisplayDirname } from "../utils/pathDisplay";
|
||||
import "./FileMentionPopup.css";
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
@@ -54,7 +55,7 @@ export function FileMentionPopup({
|
||||
{!loading && files.length > 0 && (
|
||||
<ul className="file-mention-popup-list" role="listbox">
|
||||
{files.map((file, index) => {
|
||||
const dirPath = file.path.includes("/") ? file.path.slice(0, file.path.lastIndexOf("/") + 1) : "";
|
||||
const dirPath = getDisplayDirname(file.path);
|
||||
const highlightName = file.name;
|
||||
|
||||
return (
|
||||
@@ -85,4 +86,4 @@ export function FileMentionPopup({
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { getErrorMessage } from "@fusion/core";
|
||||
import type { ToastType } from "../hooks/useToast";
|
||||
import { useConfirm } from "../hooks/useConfirm";
|
||||
import { truncateMiddle } from "../utils/truncatePath";
|
||||
import { getPathBasename } from "../utils/pathDisplay";
|
||||
import { useModalResizePersist } from "../hooks/useModalResizePersist";
|
||||
import { useOverlayDismiss } from "../hooks/useOverlayDismiss";
|
||||
import type {
|
||||
@@ -1624,7 +1625,7 @@ function WorktreesPanel({ worktrees }: { worktrees: GitWorktree[] }) {
|
||||
{worktree.isMain && <span className="gm-badge main">main</span>}
|
||||
{worktree.isBare && <span className="gm-badge bare">bare</span>}
|
||||
<span className="gm-worktree-path" title={worktree.path}>
|
||||
{worktree.path.split("/").pop() || worktree.path}
|
||||
{getPathBasename(worktree.path) || worktree.path}
|
||||
</span>
|
||||
</div>
|
||||
<div className="gm-worktree-detail">
|
||||
|
||||
@@ -10,6 +10,7 @@ import { NodeStatusIndicator } from "./NodeStatusIndicator";
|
||||
import { NodeHealthDot } from "./NodeHealthDot";
|
||||
import { PluginSlot } from "./PluginSlot";
|
||||
import { useViewportMode, type ViewportMode } from "../hooks/useViewportMode";
|
||||
import { getTrailingPath } from "../utils/pathDisplay";
|
||||
|
||||
export { useViewportMode };
|
||||
|
||||
@@ -114,7 +115,7 @@ function ProjectSelector({
|
||||
<div className="project-selector-info">
|
||||
<span className="project-selector-name">{project.name}</span>
|
||||
<span className="project-selector-path">
|
||||
{project.path.split("/").slice(-2).join("/")}
|
||||
{getTrailingPath(project.path, 2)}
|
||||
</span>
|
||||
</div>
|
||||
{isCurrent && <Check size={14} className="project-selector-check" />}
|
||||
@@ -874,7 +875,7 @@ export function Header({
|
||||
<div className="mobile-project-switch-info">
|
||||
<span className="mobile-project-switch-name">{project.name}</span>
|
||||
<span className="mobile-project-switch-path">
|
||||
{project.path.split("/").slice(-2).join("/")}
|
||||
{getTrailingPath(project.path, 2)}
|
||||
</span>
|
||||
</div>
|
||||
{isCurrent && <Check size={14} className="mobile-project-switch-check" />}
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
} from "lucide-react";
|
||||
import type { ProjectInfo } from "../api";
|
||||
import type { ProjectStatus } from "@fusion/core";
|
||||
import { getTrailingPath } from "../utils/pathDisplay";
|
||||
|
||||
export interface ProjectSelectorProps {
|
||||
projects: ProjectInfo[];
|
||||
@@ -354,7 +355,7 @@ export function ProjectSelector({
|
||||
{project.name}
|
||||
</span>
|
||||
<span className="project-selector__item-path">
|
||||
{project.path.split("/").slice(-2).join("/")}
|
||||
{getTrailingPath(project.path, 2)}
|
||||
</span>
|
||||
</div>
|
||||
{currentProject?.id === project.id && (
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
import { useTerminal } from "../hooks/useTerminal";
|
||||
import { useTerminalSessions } from "../hooks/useTerminalSessions";
|
||||
import { useModalResizePersist } from "../hooks/useModalResizePersist";
|
||||
import { getPathBasename } from "../utils/pathDisplay";
|
||||
import "@xterm/xterm/css/xterm.css";
|
||||
|
||||
import type { Terminal as XTerm, ITerminalAddon } from "@xterm/xterm";
|
||||
@@ -750,7 +751,7 @@ export function TerminalModal({ isOpen, onClose, initialCommand, projectId }: Te
|
||||
|
||||
const unsubConnect = onConnect((info) => {
|
||||
// Update tab title with shell name
|
||||
updateTabTitle(activeTab.id, info.shell.split("/").pop() || info.shell);
|
||||
updateTabTitle(activeTab.id, getPathBasename(info.shell) || info.shell);
|
||||
});
|
||||
|
||||
const unsubExit = onExit((code) => {
|
||||
|
||||
Reference in New Issue
Block a user