mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-10-02 00:23:16 +00:00
Part of the domain-reorg adoption (build plan step C6): fork content,
upstream layout. git mv's all 80 pure-rename files from the move-map
(agent-scoped components/lib -> ui/src/features/{agents,automations,
reviews,settings}/...), including ported/ splitting into
features/agents/experiments/ (still-unintegrated desktop-host ports)
and features/agents/components/chat/ (CloudPromptBar, CodeBlock,
DiffView, Logo, Markdown, ReplyCard, ShellCommand, ToolExecution --
files actually wired into the live dashboard). Fork-diverged files
(PlanReview.tsx, WorkflowApprovalCard.tsx, AgentsSidebar.tsx,
SidebarFilterMenu.tsx, AgentGitPanel.tsx, AutomationEditor.tsx,
DiffView.tsx, CloudPromptBar.tsx) keep fork content -- verified via
diff that only import lines changed.
Rewrote @/components/agents and @/lib/agents imports across all 54
importer files plus the moved files' own internal imports (grep-driven,
85-entry alias map covering every old->new path pair). Two hazards
caught only by the build gate (not tsc, since both files sit under the
new experiments/** tsconfig exclude): ui/src/lib/notifications.ts had
a relative `./agents/types` import (no `@/` alias) that the grep missed;
and features/agents/experiments/index.ts's barrel re-export of the
messages module needed to switch from an `@/` alias to a relative
import (`../components/messages`) after landing inside the newly
tsconfig-excluded experiments/ directory -- vite-tsconfig-paths failed
to resolve it at build time even though tsc stayed silent.
AgentPromptBar.tsx (the 2-line CloudPromptBar re-export shim) moves to
features/agents/components/ with its export path retargeted at
CloudPromptBar's new chat/ location -- a D/A pair, not a content loss,
since the import-path edit drops it below git's rename-similarity
threshold. ported/index.ts is the same D/A story for the same reason.
Swapped the 8-file ported/ exclude lists in tsconfig.json and
eslint.config.js for the single `src/features/agents/experiments/**`
glob (upstream's simplification, no behavior change). Deleted
ui/pnpm-workspace.yaml (lockfile hygiene -- bun stays the toolchain).
Gates: bunx tsc --noEmit (clean), bun run build (clean after the two
notifications.ts / experiments-index.ts fixes above), bun run test
(33/33), and the full Playwright E2E suite against the real langgraph
dev server + built dashboard (9/9).
128 lines
4.4 KiB
TypeScript
128 lines
4.4 KiB
TypeScript
import { memo, useCallback, useEffect, useMemo, useState } from "react";
|
|
import { ChevronDown, ChevronUp } from "lucide-react";
|
|
import { MultiFileDiff } from "@pierre/diffs/react";
|
|
|
|
import type { ChangedFileSummaryItem } from "./types";
|
|
import { useDiffOptions, warmDiffHighlighter } from "@/features/agents/utils/diffUtils";
|
|
|
|
function stripProjectPathForDisplay(path: string, projectPath?: string): string {
|
|
if (!projectPath) return path;
|
|
const normalizedPath = path.replace(/\\/g, "/");
|
|
const normalizedProjectPath = projectPath.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
if (!normalizedPath.startsWith(`${normalizedProjectPath}/`)) return path;
|
|
return normalizedPath.slice(normalizedProjectPath.length + 1);
|
|
}
|
|
|
|
const ChangedFileRow = memo(function ChangedFileRow({
|
|
file,
|
|
projectPath,
|
|
open,
|
|
diffReady,
|
|
onToggle,
|
|
}: {
|
|
file: ChangedFileSummaryItem;
|
|
projectPath?: string;
|
|
open: boolean;
|
|
diffReady: boolean;
|
|
onToggle: (filePath: string) => void;
|
|
}) {
|
|
const diffOptions = useDiffOptions();
|
|
const displayPath = useMemo(
|
|
() => stripProjectPathForDisplay(file.filePath, projectPath),
|
|
[file.filePath, projectPath],
|
|
);
|
|
const oldFile = useMemo(
|
|
() => ({ name: displayPath, contents: file.originalContent }),
|
|
[displayPath, file.originalContent],
|
|
);
|
|
const newFile = useMemo(
|
|
() => ({ name: displayPath, contents: file.modifiedContent }),
|
|
[displayPath, file.modifiedContent],
|
|
);
|
|
|
|
return (
|
|
<div className="border-b last:border-b-0 border-[var(--ui-border)]">
|
|
<button
|
|
type="button"
|
|
onClick={() => onToggle(file.filePath)}
|
|
className="w-full px-3 py-2 text-left hover:bg-[var(--ui-panel-2)] transition-colors flex items-center justify-between gap-3"
|
|
aria-expanded={open}
|
|
>
|
|
<span className="text-[13px] text-[color:var(--ui-accent)] truncate min-w-0">{displayPath}</span>
|
|
<span className="shrink-0 flex items-center gap-2">
|
|
<span className="text-xs flex items-center gap-2">
|
|
<span className="text-green-400">+{file.additions}</span>
|
|
<span className="text-red-400">-{file.deletions}</span>
|
|
</span>
|
|
{open ? (
|
|
<ChevronUp className="h-3.5 w-3.5 text-[color:var(--ui-text-dim)] shrink-0" aria-hidden />
|
|
) : (
|
|
<ChevronDown className="h-3.5 w-3.5 text-[color:var(--ui-text-dim)] shrink-0" aria-hidden />
|
|
)}
|
|
</span>
|
|
</button>
|
|
{open && (
|
|
<div className="px-2 pb-2">
|
|
<div className="rounded-lg bg-[var(--ui-panel)] overflow-hidden border border-[var(--ui-border-subtle)]">
|
|
{diffReady ? (
|
|
<div className="max-h-[250px] overflow-auto">
|
|
<MultiFileDiff oldFile={oldFile} newFile={newFile} options={diffOptions} />
|
|
</div>
|
|
) : (
|
|
<div className="px-3 py-4 text-xs text-[color:var(--ui-text-dim)]">Loading diff…</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
});
|
|
|
|
export const TurnChangedFilesCard = memo(function TurnChangedFilesCard({
|
|
files,
|
|
totals,
|
|
projectPath,
|
|
}: {
|
|
files: Array<ChangedFileSummaryItem>;
|
|
totals: { additions: number; deletions: number };
|
|
projectPath?: string;
|
|
}) {
|
|
const [expandedByPath, setExpandedByPath] = useState<Record<string, boolean>>({});
|
|
const [diffReady, setDiffReady] = useState(false);
|
|
|
|
useEffect(() => {
|
|
let active = true;
|
|
warmDiffHighlighter().finally(() => {
|
|
if (active) setDiffReady(true);
|
|
});
|
|
return () => {
|
|
active = false;
|
|
};
|
|
}, []);
|
|
|
|
const toggleFile = useCallback((filePath: string) => {
|
|
setExpandedByPath((prev) => ({ ...prev, [filePath]: !prev[filePath] }));
|
|
}, []);
|
|
|
|
return (
|
|
<div className="mt-3 rounded-xl bg-[var(--ui-code-bubble)] overflow-hidden">
|
|
<div className="px-3 py-2 text-xs text-[color:var(--ui-text-muted)] border-b border-[var(--ui-border)] flex items-center gap-2">
|
|
<span>{files.length} file{files.length === 1 ? "" : "s"} changed</span>
|
|
<span className="text-green-400">+{totals.additions}</span>
|
|
<span className="text-red-400">-{totals.deletions}</span>
|
|
</div>
|
|
<div>
|
|
{files.map((file) => (
|
|
<ChangedFileRow
|
|
key={file.filePath}
|
|
file={file}
|
|
projectPath={projectPath}
|
|
open={!!expandedByPath[file.filePath]}
|
|
diffReady={diffReady}
|
|
onToggle={toggleFile}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
});
|