- Isolated git worktree per session (create, status, merge, discard) - Permission card: accept file-edit hunks individually; agent learns what was rejected - Reasoning effort per message; regenerate the last answer with another model/effort - Voice input via the Web Speech API - Folder-up button in the file browser; ssh, rsync and mc in the image Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
60 lines
1.9 KiB
TypeScript
60 lines
1.9 KiB
TypeScript
export function Diff({ diff }: { diff: string }) {
|
|
const lines = diff.replace(/\n+$/, "").split("\n");
|
|
return (
|
|
<div className="diff">
|
|
{lines.map((l, i) => {
|
|
let cls = "";
|
|
if (l.startsWith("+++") || l.startsWith("---")) cls = "meta";
|
|
else if (l.startsWith("@@")) cls = "hunk";
|
|
else if (l.startsWith("+")) cls = "add";
|
|
else if (l.startsWith("-")) cls = "del";
|
|
return (
|
|
<div key={i} className={cls}>
|
|
{l || " "}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/** Split a unified diff into its file header and hunks (at "@@" lines). */
|
|
export function splitHunks(diff: string): { header: string[]; hunks: string[][] } {
|
|
const lines = diff.replace(/\n+$/, "").split("\n");
|
|
const header: string[] = [];
|
|
const hunks: string[][] = [];
|
|
for (const l of lines) {
|
|
if (l.startsWith("@@")) hunks.push([l]);
|
|
else if (hunks.length) hunks[hunks.length - 1].push(l);
|
|
else header.push(l);
|
|
}
|
|
return { header, hunks };
|
|
}
|
|
|
|
/** Diff with a checkbox per hunk (review a change piece by piece). */
|
|
export function HunkDiff({ diff, selected, onToggle }: { diff: string; selected: boolean[]; onToggle: (i: number) => void }) {
|
|
const { header, hunks } = splitHunks(diff);
|
|
return (
|
|
<div className="diff hunk-diff">
|
|
{header.map((l, i) => (
|
|
<div key={`h${i}`} className="meta">
|
|
{l}
|
|
</div>
|
|
))}
|
|
{hunks.map((h, i) => (
|
|
<div key={i} className={`hunk-block ${selected[i] ? "" : "off"}`}>
|
|
<label className="hunk-head">
|
|
<input type="checkbox" checked={!!selected[i]} onChange={() => onToggle(i)} />
|
|
<span>{h[0]}</span>
|
|
</label>
|
|
{h.slice(1).map((l, j) => (
|
|
<div key={j} className={l.startsWith("+") ? "add" : l.startsWith("-") ? "del" : ""}>
|
|
{l || " "}
|
|
</div>
|
|
))}
|
|
</div>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|