@agimon-ai/doompi-git 0.0.1-alpha.61 → 0.0.1-alpha.63
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/extensions/web.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { apiResponse, createApiClient, defineActivityGroup, defineApiRoutes, defineChannelFile, defineFill, defineGlobalStore, defineLeaderBinding, defineRepositorySettingsPanel, defineSessionStore, defineToolRenderer, defineWebLifecycle, defineWebPlugin, sessionApiAddress } from "@agimon-ai/doompi-core/web";
|
|
2
2
|
import { AlertIcon, BranchIcon, Button, ChevronDownIcon, ChevronRightIcon, CloseIcon, Dialog, DialogBody, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DiffView, Dot, DropdownMenuItem, EmptyState, Input, Label, MessageIcon, MessageItem, MessageItemBody, MessageItemHeader, OptionRow, RadioGroup, RadioGroupCard, ReviewCommentDraft, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, ShieldIcon, Spinner, StatusBadge, buildReviewPrompt, reviewCommentAnchor, toolTone } from "@agimon-ai/doompi-web-components";
|
|
3
|
-
import { createElement, useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
+
import { createElement, useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
4
4
|
import { sealedTransport } from "@agimon-ai/doompi-web-security/browser";
|
|
5
5
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
import { useStore } from "@tanstack/react-store";
|
|
@@ -1937,7 +1937,31 @@ function GitSyncBar({ changes, mergeBase, pending, error, errorTarget, onSync, o
|
|
|
1937
1937
|
]
|
|
1938
1938
|
});
|
|
1939
1939
|
}
|
|
1940
|
+
//#endregion
|
|
1941
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_components/GitReviewView.tsx
|
|
1942
|
+
/**
|
|
1943
|
+
* The review tab, drawn from what it is handed: the sync header, the changed
|
|
1944
|
+
* files on the left, every diff stacked on the right, and one footer that
|
|
1945
|
+
* sends every comment as a single message.
|
|
1946
|
+
*
|
|
1947
|
+
* DESIGN PATTERNS:
|
|
1948
|
+
* - Like a pull request's file view. Picking a file in the list scrolls to it;
|
|
1949
|
+
* each file's header stays pinned while its diff scrolls past.
|
|
1950
|
+
* - Only nearby file bodies mount and load; distant files retain their headers
|
|
1951
|
+
* and measured-height placeholders.
|
|
1952
|
+
* - A comment is drawn under the line it is about, on the side it is about.
|
|
1953
|
+
* The open draft sits in the same place, so writing happens in context.
|
|
1954
|
+
* - Only UI state lives here (the open draft, which files are collapsed, which
|
|
1955
|
+
* is active). Data and comments come in as props; the panel owns fetching.
|
|
1956
|
+
*
|
|
1957
|
+
* AVOID:
|
|
1958
|
+
* - Clearing comments here. The owner clears them once the host took the send.
|
|
1959
|
+
*/
|
|
1960
|
+
const PICK_ALIGNMENT_MS = 300;
|
|
1961
|
+
const ESTIMATED_ROW_HEIGHT = 21;
|
|
1962
|
+
const MAX_DIFF_ROWS = 5e3;
|
|
1940
1963
|
const NO_FILES = [];
|
|
1964
|
+
const NO_PATHS = /* @__PURE__ */ new Set();
|
|
1941
1965
|
/** Stable, URL-safe element id for one file's section. */
|
|
1942
1966
|
function sectionId(index) {
|
|
1943
1967
|
return `git-review-file-${String(index)}`;
|
|
@@ -1945,37 +1969,117 @@ function sectionId(index) {
|
|
|
1945
1969
|
function sideOf(row) {
|
|
1946
1970
|
return row.marker === "-" ? "old" : "new";
|
|
1947
1971
|
}
|
|
1948
|
-
function GitReviewView({ sync, summary, files, comments, onLoadFile, onAddComment, onRemoveComment, onSendReview, onDiscard, sendError, initialDraft }) {
|
|
1972
|
+
function GitReviewView({ sync, summary, files, comments, onLoadFile, onAddComment, onRemoveComment, onSendReview, onDiscard, onDraftChange, sendError, initialDraft }) {
|
|
1949
1973
|
const entries = summary.state === "ready" ? summary.summary.files : NO_FILES;
|
|
1950
1974
|
const [openOverride, setOpenOverride] = useState({});
|
|
1951
1975
|
const [picked, setPicked] = useState();
|
|
1952
|
-
const
|
|
1976
|
+
const [navigation, setNavigation] = useState();
|
|
1977
|
+
const [proximity, setProximity] = useState({
|
|
1978
|
+
entries: NO_FILES,
|
|
1979
|
+
paths: NO_PATHS
|
|
1980
|
+
});
|
|
1981
|
+
const near = proximity.entries === entries ? proximity.paths : NO_PATHS;
|
|
1982
|
+
const paneRef = useRef(null);
|
|
1983
|
+
const [heights, setHeights] = useState(/* @__PURE__ */ new Map());
|
|
1984
|
+
const pickedAt = useRef(0);
|
|
1985
|
+
const activePath = entries.some((entry) => entry.path === picked) ? picked : entries[0]?.path;
|
|
1953
1986
|
const [draft, setDraft] = useState(initialDraft);
|
|
1954
1987
|
useEffect(() => {
|
|
1955
|
-
|
|
1988
|
+
const pane = paneRef.current;
|
|
1989
|
+
if (pane === null || typeof IntersectionObserver === "undefined") return;
|
|
1990
|
+
setHeights(/* @__PURE__ */ new Map());
|
|
1991
|
+
let observer;
|
|
1992
|
+
const observe = () => {
|
|
1993
|
+
observer?.disconnect();
|
|
1994
|
+
observer = new IntersectionObserver((changes) => {
|
|
1995
|
+
for (const change of changes) {
|
|
1996
|
+
if (change.isIntersecting) continue;
|
|
1997
|
+
const section = change.target;
|
|
1998
|
+
const path = section.dataset.reviewPath;
|
|
1999
|
+
const body = section.querySelector("[data-review-body=\"measured\"]");
|
|
2000
|
+
if (path !== void 0 && body !== null) {
|
|
2001
|
+
const height = body.offsetHeight;
|
|
2002
|
+
setHeights((measured) => new Map(measured).set(path, height));
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
setProximity((current) => {
|
|
2006
|
+
const next = new Set(current.entries === entries ? current.paths : NO_PATHS);
|
|
2007
|
+
for (const change of changes) {
|
|
2008
|
+
const path = change.target.dataset.reviewPath;
|
|
2009
|
+
if (path === void 0) continue;
|
|
2010
|
+
if (change.isIntersecting) next.add(path);
|
|
2011
|
+
else next.delete(path);
|
|
2012
|
+
}
|
|
2013
|
+
return {
|
|
2014
|
+
entries,
|
|
2015
|
+
paths: next
|
|
2016
|
+
};
|
|
2017
|
+
});
|
|
2018
|
+
}, {
|
|
2019
|
+
root: pane,
|
|
2020
|
+
rootMargin: `${String(pane.clientHeight)}px 0px`
|
|
2021
|
+
});
|
|
2022
|
+
for (const section of pane.querySelectorAll("[data-review-path]")) observer.observe(section);
|
|
2023
|
+
};
|
|
2024
|
+
observe();
|
|
2025
|
+
const resize = typeof ResizeObserver === "undefined" ? void 0 : new ResizeObserver(observe);
|
|
2026
|
+
resize?.observe(pane);
|
|
2027
|
+
let frame = 0;
|
|
2028
|
+
const scroll = () => {
|
|
2029
|
+
cancelAnimationFrame(frame);
|
|
2030
|
+
frame = requestAnimationFrame(() => {
|
|
2031
|
+
if (Date.now() - pickedAt.current < PICK_ALIGNMENT_MS) return;
|
|
2032
|
+
const rect = pane.getBoundingClientRect();
|
|
2033
|
+
const section = document.elementFromPoint(rect.left + 8, rect.top + 1)?.closest("[data-review-path]");
|
|
2034
|
+
if (section !== null && section !== void 0 && pane.contains(section)) setPicked(section.dataset.reviewPath);
|
|
2035
|
+
});
|
|
2036
|
+
};
|
|
2037
|
+
pane.addEventListener("scroll", scroll, { passive: true });
|
|
2038
|
+
return () => {
|
|
2039
|
+
observer.disconnect();
|
|
2040
|
+
resize?.disconnect();
|
|
2041
|
+
pane.removeEventListener("scroll", scroll);
|
|
2042
|
+
cancelAnimationFrame(frame);
|
|
2043
|
+
};
|
|
2044
|
+
}, [entries]);
|
|
2045
|
+
useLayoutEffect(() => {
|
|
2046
|
+
if (navigation === void 0) return;
|
|
2047
|
+
[...paneRef.current?.querySelectorAll("[data-review-path]") ?? []].find((element) => element.dataset.reviewPath === navigation)?.scrollIntoView({ block: "start" });
|
|
2048
|
+
}, [navigation, openOverride]);
|
|
2049
|
+
useEffect(() => {
|
|
2050
|
+
if (navigation === void 0) return;
|
|
2051
|
+
const timeout = setTimeout(() => setNavigation(void 0), PICK_ALIGNMENT_MS);
|
|
2052
|
+
return () => clearTimeout(timeout);
|
|
2053
|
+
}, [navigation]);
|
|
2054
|
+
useEffect(() => {
|
|
2055
|
+
for (const file of entries) if ((openOverride[file.path] ?? true) && (near.has(file.path) || navigation === file.path || draft?.path === file.path) && files[file.path] === void 0) onLoadFile(file.path);
|
|
1956
2056
|
}, [
|
|
1957
2057
|
entries,
|
|
1958
2058
|
files,
|
|
2059
|
+
near,
|
|
2060
|
+
navigation,
|
|
2061
|
+
draft,
|
|
2062
|
+
openOverride,
|
|
1959
2063
|
onLoadFile
|
|
1960
2064
|
]);
|
|
1961
2065
|
const commentCounts = {};
|
|
1962
2066
|
for (const comment of comments) commentCounts[comment.path] = (commentCounts[comment.path] ?? 0) + 1;
|
|
1963
|
-
const isOpen = (path
|
|
2067
|
+
const isOpen = (path) => openOverride[path] ?? true;
|
|
1964
2068
|
const open = (path) => {
|
|
1965
2069
|
setOpenOverride((current) => current[path] === true ? current : {
|
|
1966
2070
|
...current,
|
|
1967
2071
|
[path]: true
|
|
1968
2072
|
});
|
|
1969
|
-
if (files[path] === void 0) onLoadFile(path);
|
|
1970
2073
|
};
|
|
1971
2074
|
const pick = (path) => {
|
|
2075
|
+
if (!entries.some((entry) => entry.path === path)) return;
|
|
2076
|
+
pickedAt.current = Date.now();
|
|
1972
2077
|
setPicked(path);
|
|
2078
|
+
setNavigation(path);
|
|
1973
2079
|
open(path);
|
|
1974
|
-
const index = entries.findIndex((file) => file.path === path);
|
|
1975
|
-
globalThis.document?.getElementById(sectionId(index))?.scrollIntoView({ block: "start" });
|
|
1976
2080
|
};
|
|
1977
|
-
const toggle = (path
|
|
1978
|
-
if (isOpen(path
|
|
2081
|
+
const toggle = (path) => {
|
|
2082
|
+
if (isOpen(path)) setOpenOverride((current) => ({
|
|
1979
2083
|
...current,
|
|
1980
2084
|
[path]: false
|
|
1981
2085
|
}));
|
|
@@ -1993,6 +2097,7 @@ function GitReviewView({ sync, summary, files, comments, onLoadFile, onAddCommen
|
|
|
1993
2097
|
body: body.trim()
|
|
1994
2098
|
});
|
|
1995
2099
|
setDraft(void 0);
|
|
2100
|
+
onDraftChange?.(false);
|
|
1996
2101
|
};
|
|
1997
2102
|
return /* @__PURE__ */ jsxs("div", {
|
|
1998
2103
|
"data-testid": "git-review-panel",
|
|
@@ -2035,6 +2140,7 @@ function GitReviewView({ sync, summary, files, comments, onLoadFile, onAddCommen
|
|
|
2035
2140
|
onPick: pick,
|
|
2036
2141
|
...activePath === void 0 ? {} : { activePath }
|
|
2037
2142
|
}), /* @__PURE__ */ jsxs("div", {
|
|
2143
|
+
ref: paneRef,
|
|
2038
2144
|
"data-testid": "git-review-diffs",
|
|
2039
2145
|
className: "min-h-0 min-w-0 flex-1 overflow-y-auto",
|
|
2040
2146
|
children: [summary.summary.truncated ? /* @__PURE__ */ jsx("p", {
|
|
@@ -2044,19 +2150,25 @@ function GitReviewView({ sync, summary, files, comments, onLoadFile, onAddCommen
|
|
|
2044
2150
|
id: sectionId(index),
|
|
2045
2151
|
entry: file,
|
|
2046
2152
|
state: files[file.path],
|
|
2047
|
-
collapsed: !isOpen(file.path
|
|
2153
|
+
collapsed: !isOpen(file.path),
|
|
2154
|
+
mounted: near.has(file.path) || navigation === file.path || draft?.path === file.path,
|
|
2155
|
+
placeholderHeight: heights.get(file.path) ?? (file.binary ? 40 : Math.min(MAX_DIFF_ROWS, file.added + file.removed + 6) * ESTIMATED_ROW_HEIGHT),
|
|
2048
2156
|
comments: comments.filter((comment) => comment.path === file.path),
|
|
2049
2157
|
draft: draft?.path === file.path ? draft.selection : void 0,
|
|
2050
|
-
onToggle: () => toggle(file.path
|
|
2158
|
+
onToggle: () => toggle(file.path),
|
|
2051
2159
|
onSelect: (selection) => {
|
|
2052
2160
|
setPicked(file.path);
|
|
2053
2161
|
setDraft({
|
|
2054
2162
|
path: file.path,
|
|
2055
2163
|
selection
|
|
2056
2164
|
});
|
|
2165
|
+
onDraftChange?.(true);
|
|
2057
2166
|
},
|
|
2058
2167
|
onSubmitDraft: submitDraft,
|
|
2059
|
-
onCancelDraft: () =>
|
|
2168
|
+
onCancelDraft: () => {
|
|
2169
|
+
setDraft(void 0);
|
|
2170
|
+
onDraftChange?.(false);
|
|
2171
|
+
},
|
|
2060
2172
|
onRemoveComment,
|
|
2061
2173
|
onRetry: () => onLoadFile(file.path)
|
|
2062
2174
|
}, file.path))]
|
|
@@ -2111,7 +2223,7 @@ const STATUS_WORD = {
|
|
|
2111
2223
|
conflicted: "conflicted"
|
|
2112
2224
|
};
|
|
2113
2225
|
/** One file: a pinned header, then its diff with comments drawn under the lines they are about. */
|
|
2114
|
-
function FileSection({ id, entry, state, collapsed, comments, draft, onToggle, onSelect, onSubmitDraft, onCancelDraft, onRemoveComment, onRetry }) {
|
|
2226
|
+
function FileSection({ id, entry, state, collapsed, mounted, placeholderHeight, comments, draft, onToggle, onSelect, onSubmitDraft, onCancelDraft, onRemoveComment, onRetry }) {
|
|
2115
2227
|
const renderAfterRow = (row) => {
|
|
2116
2228
|
const side = sideOf(row);
|
|
2117
2229
|
const here = comments.filter((comment) => (comment.side ?? "new") === side && (comment.endLine ?? comment.startLine) === row.line);
|
|
@@ -2162,6 +2274,7 @@ function FileSection({ id, entry, state, collapsed, comments, draft, onToggle, o
|
|
|
2162
2274
|
};
|
|
2163
2275
|
return /* @__PURE__ */ jsxs("section", {
|
|
2164
2276
|
id,
|
|
2277
|
+
"data-review-path": entry.path,
|
|
2165
2278
|
"data-testid": `git-review-file-${entry.path}`,
|
|
2166
2279
|
className: "border-b border-doom-border-soft",
|
|
2167
2280
|
children: [/* @__PURE__ */ jsxs(Button, {
|
|
@@ -2208,39 +2321,46 @@ function FileSection({ id, entry, state, collapsed, comments, draft, onToggle, o
|
|
|
2208
2321
|
]
|
|
2209
2322
|
})
|
|
2210
2323
|
]
|
|
2211
|
-
}), collapsed ? null :
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
})
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2324
|
+
}), collapsed ? null : !mounted ? /* @__PURE__ */ jsx("div", {
|
|
2325
|
+
"aria-hidden": true,
|
|
2326
|
+
style: { height: placeholderHeight }
|
|
2327
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
2328
|
+
"data-review-body": state === void 0 || state.state === "loading" ? "loading" : "measured",
|
|
2329
|
+
style: state === void 0 || state.state === "loading" ? { minHeight: placeholderHeight } : void 0,
|
|
2330
|
+
children: state === void 0 || state.state === "loading" ? /* @__PURE__ */ jsxs("div", {
|
|
2331
|
+
className: "flex items-center gap-2 px-4 py-3",
|
|
2332
|
+
children: [/* @__PURE__ */ jsx(Spinner, {
|
|
2333
|
+
className: "h-3 w-3 text-doom-faint",
|
|
2334
|
+
label: `loading ${entry.path}`
|
|
2335
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
2336
|
+
className: "text-2xs text-doom-faint",
|
|
2337
|
+
children: "loading diff…"
|
|
2338
|
+
})]
|
|
2339
|
+
}) : state.state === "error" ? /* @__PURE__ */ jsxs("div", {
|
|
2340
|
+
className: "flex items-center gap-2 px-4 py-3",
|
|
2341
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
2342
|
+
role: "alert",
|
|
2343
|
+
className: "text-2xs text-doom-red",
|
|
2344
|
+
children: state.error
|
|
2345
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
2346
|
+
variant: "ghost",
|
|
2347
|
+
size: "xs",
|
|
2348
|
+
onClick: onRetry,
|
|
2349
|
+
children: "retry"
|
|
2350
|
+
})]
|
|
2351
|
+
}) : state.diff.binary ? /* @__PURE__ */ jsx("p", {
|
|
2352
|
+
className: "px-4 py-3 text-2xs text-doom-faint",
|
|
2353
|
+
children: "binary file, not drawn"
|
|
2354
|
+
}) : state.diff.tooLarge ? /* @__PURE__ */ jsx("p", {
|
|
2355
|
+
className: "px-4 py-3 text-2xs text-doom-faint",
|
|
2356
|
+
children: "this diff is too large to draw here"
|
|
2357
|
+
}) : /* @__PURE__ */ jsx(DiffView, {
|
|
2358
|
+
hunks: state.diff.hunks,
|
|
2359
|
+
testId: `git-review-diff-${id}`,
|
|
2360
|
+
onSelect,
|
|
2361
|
+
renderAfterRow,
|
|
2362
|
+
className: "py-1"
|
|
2363
|
+
})
|
|
2244
2364
|
})]
|
|
2245
2365
|
});
|
|
2246
2366
|
}
|
|
@@ -2278,7 +2398,11 @@ function GitReviewPanel({ sessionId, sendSessionFrame }) {
|
|
|
2278
2398
|
const [files, setFiles] = useState({});
|
|
2279
2399
|
const [sendError, setSendError] = useState();
|
|
2280
2400
|
const [dismissedError, setDismissedError] = useState();
|
|
2401
|
+
const [draftSnapshot, setDraftSnapshot] = useState();
|
|
2281
2402
|
const changes = activity.changes;
|
|
2403
|
+
const generation = useRef(0);
|
|
2404
|
+
const pendingFiles = useRef(/* @__PURE__ */ new Map());
|
|
2405
|
+
const readyGeneration = useRef(false);
|
|
2282
2406
|
const fingerprint = JSON.stringify([
|
|
2283
2407
|
changes?.branch,
|
|
2284
2408
|
changes?.base,
|
|
@@ -2287,11 +2411,20 @@ function GitReviewPanel({ sessionId, sendSessionFrame }) {
|
|
|
2287
2411
|
changes?.files,
|
|
2288
2412
|
changes?.rebase?.conflicts.length
|
|
2289
2413
|
]);
|
|
2414
|
+
const refreshFingerprint = draftSnapshot?.sessionId === sessionId ? draftSnapshot.fingerprint : fingerprint;
|
|
2290
2415
|
useEffect(() => {
|
|
2416
|
+
generation.current += 1;
|
|
2417
|
+
readyGeneration.current = false;
|
|
2418
|
+
const pending = pendingFiles.current;
|
|
2419
|
+
for (const request of pending.values()) request.abort();
|
|
2420
|
+
pending.clear();
|
|
2421
|
+
setSummary({ state: "loading" });
|
|
2422
|
+
setFiles({});
|
|
2291
2423
|
if (sessionId === null) return;
|
|
2292
2424
|
const controller = new AbortController();
|
|
2293
2425
|
fetchReviewSummary(sessionId, controller.signal).then((result) => {
|
|
2294
2426
|
if (controller.signal.aborted) return;
|
|
2427
|
+
readyGeneration.current = result.ok;
|
|
2295
2428
|
setSummary(result.ok ? {
|
|
2296
2429
|
state: "ready",
|
|
2297
2430
|
summary: result.summary
|
|
@@ -2307,19 +2440,31 @@ function GitReviewPanel({ sessionId, sendSessionFrame }) {
|
|
|
2307
2440
|
error: error instanceof Error ? error.message : "The changes could not be read."
|
|
2308
2441
|
});
|
|
2309
2442
|
});
|
|
2310
|
-
return () =>
|
|
2311
|
-
|
|
2443
|
+
return () => {
|
|
2444
|
+
controller.abort();
|
|
2445
|
+
generation.current += 1;
|
|
2446
|
+
readyGeneration.current = false;
|
|
2447
|
+
for (const request of pending.values()) request.abort();
|
|
2448
|
+
pending.clear();
|
|
2449
|
+
};
|
|
2450
|
+
}, [sessionId, refreshFingerprint]);
|
|
2312
2451
|
const loadFile = useCallback((path) => {
|
|
2313
|
-
if (sessionId === null) return;
|
|
2452
|
+
if (sessionId === null || !readyGeneration.current || pendingFiles.current.has(path)) return;
|
|
2453
|
+
const requestGeneration = generation.current;
|
|
2454
|
+
const controller = new AbortController();
|
|
2455
|
+
pendingFiles.current.set(path, controller);
|
|
2314
2456
|
setFiles((current) => ({
|
|
2315
2457
|
...current,
|
|
2316
2458
|
[path]: { state: "loading" }
|
|
2317
2459
|
}));
|
|
2318
|
-
const settle = (state) =>
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2460
|
+
const settle = (state) => {
|
|
2461
|
+
if (controller.signal.aborted || requestGeneration !== generation.current) return;
|
|
2462
|
+
setFiles((current) => ({
|
|
2463
|
+
...current,
|
|
2464
|
+
[path]: state
|
|
2465
|
+
}));
|
|
2466
|
+
};
|
|
2467
|
+
fetchReviewFile(sessionId, path, controller.signal).then((result) => settle(result.ok ? {
|
|
2323
2468
|
state: "ready",
|
|
2324
2469
|
diff: result.diff
|
|
2325
2470
|
} : {
|
|
@@ -2328,7 +2473,9 @@ function GitReviewPanel({ sessionId, sendSessionFrame }) {
|
|
|
2328
2473
|
})).catch((error) => settle({
|
|
2329
2474
|
state: "error",
|
|
2330
2475
|
error: error instanceof Error ? error.message : "The diff could not be read."
|
|
2331
|
-
}))
|
|
2476
|
+
})).finally(() => {
|
|
2477
|
+
if (pendingFiles.current.get(path) === controller) pendingFiles.current.delete(path);
|
|
2478
|
+
});
|
|
2332
2479
|
}, [sessionId]);
|
|
2333
2480
|
if (sessionId === null) return null;
|
|
2334
2481
|
const branch = summary.state === "ready" ? summary.summary.branch : changes?.branch;
|
|
@@ -2340,6 +2487,10 @@ function GitReviewPanel({ sessionId, sendSessionFrame }) {
|
|
|
2340
2487
|
if (failure === void 0) onSent?.();
|
|
2341
2488
|
};
|
|
2342
2489
|
return /* @__PURE__ */ jsx(GitReviewView, {
|
|
2490
|
+
onDraftChange: (open) => setDraftSnapshot((current) => open ? current?.sessionId === sessionId ? current : {
|
|
2491
|
+
sessionId,
|
|
2492
|
+
fingerprint
|
|
2493
|
+
} : void 0),
|
|
2343
2494
|
sync: {
|
|
2344
2495
|
...changes === void 0 ? {} : { changes },
|
|
2345
2496
|
...activity.pending === void 0 ? {} : { pending: activity.pending },
|
|
@@ -2359,7 +2510,7 @@ function GitReviewPanel({ sessionId, sendSessionFrame }) {
|
|
|
2359
2510
|
onSendReview: () => send(buildReviewPrompt(comments, reviewHeading(comments.length, branch, base)), () => clearReviewComments(sessionId)),
|
|
2360
2511
|
onDiscard: () => clearReviewComments(sessionId),
|
|
2361
2512
|
...sendError === void 0 ? {} : { sendError }
|
|
2362
|
-
});
|
|
2513
|
+
}, sessionId);
|
|
2363
2514
|
}
|
|
2364
2515
|
//#endregion
|
|
2365
2516
|
//#region src/extensions/workspaces/sessions/(frontend)/fill/_components/DiffActivitySection.tsx
|