Multi-agent audit + aggressive cleanup across server/web/coder/booterm, delivered behind a DEFER discipline so none of the in-flight files were touched. Removes dead code/deps/columns, dedups server + coder helpers, and splits the oversized modules (tools.ts, opencode-server.ts, sentinel-summaries, turn.ts, TerminalPane.tsx) behind stable contracts. Adds 78 parity/unit tests (server 587, coder 323); fixes two latent bugs (ChatPane queue keys, FileViewerOverlay blank-line parity). Intended tag: v2.7.12-audit-cleanup. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
35 lines
1.2 KiB
TypeScript
35 lines
1.2 KiB
TypeScript
import type { ReactNode } from 'react';
|
|
import { sessionEvents } from '@/hooks/sessionEvents';
|
|
|
|
// Match path-shaped substrings ending in `.ext`. Requires a `/` in the match
|
|
// to reduce false positives in prose (e.g. plain `foo.ts` won't match but
|
|
// `src/foo.ts` will). False positives at the edges are accepted (2026-05-14).
|
|
const PATH_REGEX = /([a-zA-Z0-9._/-]+\.[a-zA-Z0-9]+)/g;
|
|
|
|
export function linkifyPaths(text: string, keyPrefix = 'p'): ReactNode {
|
|
const out: ReactNode[] = [];
|
|
let lastIdx = 0;
|
|
let idx = 0;
|
|
for (const match of text.matchAll(PATH_REGEX)) {
|
|
const matchedText = match[0];
|
|
const start = match.index ?? 0;
|
|
if (!matchedText.includes('/')) continue;
|
|
if (start > lastIdx) out.push(text.slice(lastIdx, start));
|
|
out.push(
|
|
<button
|
|
key={`${keyPrefix}-${idx}`}
|
|
type="button"
|
|
onClick={() => sessionEvents.emit({ type: 'open_file_in_browser', path: matchedText })}
|
|
className="text-primary underline cursor-pointer hover:text-primary/80"
|
|
>
|
|
{matchedText}
|
|
</button>
|
|
);
|
|
lastIdx = start + matchedText.length;
|
|
idx += 1;
|
|
}
|
|
if (out.length === 0) return text;
|
|
if (lastIdx < text.length) out.push(text.slice(lastIdx));
|
|
return out;
|
|
}
|