Replays the transcript's accumulated Write/Edit/MultiEdit toolUseResults (and Bash rm deletes) into per-file before/after content, cut off at each repo's last conversation commit, and serves it as /api/conversation-uncommitted-diff (same DiffResult model, stats variant for the panel). The detail panel's Commits card links it above the commit rows; /diff/uncommitted/<id> renders it in the shared DiffView. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
84 lines
3.3 KiB
TypeScript
84 lines
3.3 KiB
TypeScript
import { useLocation } from "react-router-dom";
|
|
import { useCommitDiff, useConversationDiff, useUncommittedDiff } from "@/lib/queries";
|
|
import type { DiffResult } from "@/types";
|
|
|
|
/**
|
|
* The diff view is reachable three ways, all under `/diff/*`:
|
|
* - `/diff/commit/<kind>/<slug>/<sha>` — one commit in a repo
|
|
* - `/diff/conversation/<encodedId>` — a conversation's aggregated diff
|
|
* - `/diff/uncommitted/<encodedId>` — its not-yet-committed changes
|
|
* Both the page (`DiffView`) and the left-drawer file tree (`DiffFilesPanel`)
|
|
* parse the same path and read the same React Query, so the fetch is deduped.
|
|
*/
|
|
export type DiffRoute =
|
|
| { mode: "commit"; repo: string; sha: string; label: string }
|
|
| { mode: "conversation"; id: string }
|
|
| { mode: "uncommitted"; id: string }
|
|
| null;
|
|
|
|
export function parseDiffPath(pathname: string): DiffRoute {
|
|
const seg = pathname.split("/").filter(Boolean);
|
|
if (seg[0] !== "diff") return null;
|
|
if (seg[1] === "commit" && seg.length >= 5) {
|
|
const kind = seg[2];
|
|
const slug = decodeURIComponent(seg[3]);
|
|
const sha = decodeURIComponent(seg[4]);
|
|
return { mode: "commit", repo: `${kind}:${slug}`, sha, label: slug };
|
|
}
|
|
if (seg[1] === "conversation" && seg.length >= 3) {
|
|
return { mode: "conversation", id: decodeURIComponent(seg.slice(2).join("/")) };
|
|
}
|
|
if (seg[1] === "uncommitted" && seg.length >= 3) {
|
|
return { mode: "uncommitted", id: decodeURIComponent(seg.slice(2).join("/")) };
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/** Build the URL to a single commit's diff page. */
|
|
export function commitDiffHref(kind: "project" | "service" | "super", slug: string, sha: string): string {
|
|
return `/diff/commit/${kind}/${encodeURIComponent(slug)}/${encodeURIComponent(sha)}`;
|
|
}
|
|
|
|
/** Build the URL to a repo token's commit diff page (token = `kind:slug`). */
|
|
export function repoTokenHref(repo: string, sha: string): string {
|
|
const [kind, ...rest] = repo.split(":");
|
|
return commitDiffHref(kind as "project" | "service" | "super", rest.join(":") || "_", sha);
|
|
}
|
|
|
|
/** Build the URL to a conversation's aggregated diff page. */
|
|
export function conversationDiffHref(id: string): string {
|
|
return `/diff/conversation/${encodeURIComponent(id)}`;
|
|
}
|
|
|
|
/** Build the URL to a conversation's uncommitted-changes diff page. */
|
|
export function uncommittedDiffHref(id: string): string {
|
|
return `/diff/uncommitted/${encodeURIComponent(id)}`;
|
|
}
|
|
|
|
/** Stable DOM id for a file's diff card, so the tree can scroll to it. */
|
|
export function fileAnchor(path: string): string {
|
|
return "f-" + path.replace(/[^a-zA-Z0-9]+/g, "-");
|
|
}
|
|
|
|
/** Resolve the current `/diff/*` route to its DiffResult query. */
|
|
export function useDiffRouteData(): {
|
|
route: DiffRoute;
|
|
data: DiffResult | undefined;
|
|
isLoading: boolean;
|
|
error: unknown;
|
|
} {
|
|
const { pathname } = useLocation();
|
|
const route = parseDiffPath(pathname);
|
|
const commit = useCommitDiff(
|
|
route?.mode === "commit" ? route.repo : "",
|
|
route?.mode === "commit" ? route.sha : "",
|
|
);
|
|
const convo = useConversationDiff(route?.mode === "conversation" ? route.id : "");
|
|
const uncommitted = useUncommittedDiff(route?.mode === "uncommitted" ? route.id : "");
|
|
const q =
|
|
route?.mode === "commit" ? commit
|
|
: route?.mode === "uncommitted" ? uncommitted
|
|
: convo;
|
|
return { route, data: q.data, isLoading: q.isLoading, error: q.error };
|
|
}
|