mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-10-01 20:13:17 +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).
190 lines
6.6 KiB
TypeScript
190 lines
6.6 KiB
TypeScript
import { useMemo } from "react"
|
|
import { preloadHighlighter } from "@pierre/diffs"
|
|
import type {
|
|
VirtualFileMetrics,
|
|
WorkerInitializationRenderOptions,
|
|
WorkerPoolOptions,
|
|
} from "@pierre/diffs/react"
|
|
import { useResolvedTheme } from "@/lib/theme"
|
|
|
|
export type DiffStyle = "unified" | "split"
|
|
|
|
export const DIFF_UNSAFE_CSS = `
|
|
[data-diffs-header],
|
|
[data-diff],
|
|
[data-file],
|
|
[data-error-wrapper],
|
|
[data-virtualizer-buffer] {
|
|
--diffs-bg: var(--ui-panel) !important;
|
|
--diffs-light-bg: var(--ui-panel) !important;
|
|
--diffs-dark-bg: var(--ui-panel) !important;
|
|
--diffs-token-light-bg: transparent;
|
|
--diffs-token-dark-bg: transparent;
|
|
|
|
--diffs-bg-context-override: var(--ui-panel);
|
|
--diffs-bg-hover-override: var(--ui-panel-2);
|
|
--diffs-bg-separator-override: var(--ui-accent-bubble);
|
|
--diffs-bg-buffer-override: var(--ui-bg);
|
|
|
|
--diffs-bg-addition-override: color-mix(in srgb, var(--ui-panel) 80%, #22c55e);
|
|
--diffs-bg-addition-number-override: color-mix(in srgb, var(--ui-panel) 75%, #22c55e);
|
|
--diffs-bg-addition-hover-override: color-mix(in srgb, var(--ui-panel) 70%, #22c55e);
|
|
--diffs-bg-addition-emphasis-override: color-mix(in srgb, var(--ui-panel) 60%, #22c55e);
|
|
|
|
--diffs-bg-deletion-override: color-mix(in srgb, var(--ui-panel) 80%, #ef4444);
|
|
--diffs-bg-deletion-number-override: color-mix(in srgb, var(--ui-panel) 75%, #ef4444);
|
|
--diffs-bg-deletion-hover-override: color-mix(in srgb, var(--ui-panel) 70%, #ef4444);
|
|
--diffs-bg-deletion-emphasis-override: color-mix(in srgb, var(--ui-panel) 60%, #ef4444);
|
|
|
|
--diffs-fg-number-override: var(--ui-text-dim);
|
|
--diffs-font-size: 12px;
|
|
--diffs-line-height: 1.5;
|
|
--diffs-font-family: "SF Mono", "Fira Code", "Cascadia Code", Menlo, Monaco, monospace;
|
|
|
|
background-color: var(--ui-panel) !important;
|
|
}
|
|
|
|
[data-file-info] {
|
|
background-color: var(--ui-accent-bubble) !important;
|
|
border-block-color: var(--ui-border) !important;
|
|
color: var(--ui-text) !important;
|
|
}
|
|
|
|
[data-diffs-header] {
|
|
position: sticky !important;
|
|
top: 0;
|
|
z-index: 4;
|
|
background-color: var(--ui-accent-bubble) !important;
|
|
border-bottom: 1px solid var(--ui-border) !important;
|
|
}
|
|
|
|
[data-separator] {
|
|
background-color: var(--ui-accent-bubble) !important;
|
|
color: var(--ui-text-dim) !important;
|
|
}
|
|
|
|
/* A selected line propagates [data-selected-line] onto its annotation row and
|
|
gutter, bleeding the selection background behind inline annotation content.
|
|
Keep the code line highlighted, but hold the annotation row at the panel bg. */
|
|
[data-line-annotation][data-selected-line],
|
|
[data-gutter-buffer="annotation"][data-selected-line] {
|
|
--diffs-line-bg: var(--ui-panel) !important;
|
|
}
|
|
|
|
/* Pin every code row to one exact, uniform height (kept in sync with
|
|
DIFF_VIRTUAL_METRICS.lineHeight below). In scroll mode code never wraps, so a
|
|
hard height won't clip content — it just makes the virtualizer's per-line
|
|
estimate match measured layout, so scroll-to lands precisely instead of
|
|
over/under-shooting as off-estimate rows reconcile while scrolling. */
|
|
[data-line] {
|
|
height: 18px !important;
|
|
min-height: 18px !important;
|
|
max-height: 18px !important;
|
|
line-height: 18px !important;
|
|
}
|
|
`
|
|
|
|
export const diffOptions = {
|
|
theme: { light: "pierre-light", dark: "pierre-dark" } as const,
|
|
themeType: "system" as const,
|
|
diffStyle: "unified" as const,
|
|
overflow: "scroll" as const,
|
|
disableFileHeader: true,
|
|
unsafeCSS: DIFF_UNSAFE_CSS,
|
|
collapsedContextThreshold: 4,
|
|
lineDiffType: "word-alt" as const,
|
|
maxLineDiffLength: 800,
|
|
tokenizeMaxLineLength: 1200,
|
|
tokenizeMaxLength: 120_000,
|
|
}
|
|
|
|
export function useDiffOptions(diffStyle: DiffStyle = "unified") {
|
|
const resolvedTheme = useResolvedTheme()
|
|
return useMemo(
|
|
() => ({ ...diffOptions, themeType: resolvedTheme, diffStyle }),
|
|
[resolvedTheme, diffStyle]
|
|
)
|
|
}
|
|
|
|
// Shared virtualization + worker-pool config for <Virtualizer>/<MultiFileDiff>.
|
|
// Tuned for the agent git panel and the PR reviews page; keep them aligned so
|
|
// both viewers window rows and offload highlighting identically.
|
|
export const DIFF_VIRTUALIZER_CONFIG = {
|
|
overscrollSize: 1200,
|
|
intersectionObserverMargin: 4800,
|
|
}
|
|
|
|
export const DIFF_VIRTUAL_METRICS = {
|
|
hunkLineCount: 80,
|
|
// Must match the hard `[data-line]` height pinned in DIFF_UNSAFE_CSS so the
|
|
// virtualizer's pre-measurement estimate equals the measured row height.
|
|
lineHeight: 18,
|
|
diffHeaderHeight: 0,
|
|
spacing: 8,
|
|
} satisfies Partial<VirtualFileMetrics>
|
|
|
|
export const DIFF_WORKER_POOL_OPTIONS = {
|
|
workerFactory: () =>
|
|
new Worker(
|
|
new URL("@pierre/diffs/worker/worker-portable.js", import.meta.url),
|
|
{ type: "module" }
|
|
),
|
|
poolSize: 2,
|
|
totalASTLRUCacheSize: 120,
|
|
} satisfies WorkerPoolOptions
|
|
|
|
export const DIFF_WORKER_HIGHLIGHTER_OPTIONS = {
|
|
theme: { light: "pierre-light", dark: "pierre-dark" },
|
|
lineDiffType: "word-alt",
|
|
maxLineDiffLength: 800,
|
|
tokenizeMaxLineLength: 1200,
|
|
langs: ["text"],
|
|
} satisfies WorkerInitializationRenderOptions
|
|
|
|
function hashFileContents(contents: string): string {
|
|
let hash = 0x811c9dc5
|
|
for (let i = 0; i < contents.length; i++) {
|
|
hash ^= contents.charCodeAt(i)
|
|
hash = Math.imul(hash, 0x01000193)
|
|
}
|
|
return (hash >>> 0).toString(36)
|
|
}
|
|
|
|
// Stable per-file content key so the worker pool dedupes highlight work across
|
|
// re-renders instead of re-tokenizing identical content. Added/removed/binary/
|
|
// oversized blobs arrive as null (see pr_diff.py); coerce to "" so the key never
|
|
// dereferences null — these files don't render a diff, so the exact key is moot.
|
|
export function fileContentsCacheKey(
|
|
path: string,
|
|
side: "old" | "new",
|
|
contents: string | null | undefined
|
|
): string {
|
|
const text = contents ?? ""
|
|
return `${path}:${side}:${text.length}:${hashFileContents(text)}`
|
|
}
|
|
|
|
let highlighterWarmup: Promise<void> | null = null
|
|
|
|
/**
|
|
* Pierre's <MultiFileDiff> renders an empty <diffs-container> on its first mount
|
|
* when the shared Shiki highlighter (specifically its themes) hasn't loaded yet:
|
|
* the cold-start render bails before painting and relies on an async repaint that
|
|
* can be dropped — most reliably under React StrictMode's mount/unmount/mount,
|
|
* which leaves a stale empty <pre> behind so the remounted instance no-ops.
|
|
*
|
|
* Warming the themes up-front makes that first render synchronous and non-empty.
|
|
* Idempotent and client-only (preloadHighlighter creates a Shiki instance).
|
|
*/
|
|
export function warmDiffHighlighter(): Promise<void> {
|
|
if (typeof window === "undefined") return Promise.resolve()
|
|
if (highlighterWarmup == null) {
|
|
highlighterWarmup = preloadHighlighter({
|
|
themes: [diffOptions.theme.light, diffOptions.theme.dark],
|
|
langs: ["text"],
|
|
}).catch((error) => {
|
|
highlighterWarmup = null
|
|
throw error
|
|
})
|
|
}
|
|
return highlighterWarmup
|
|
}
|