dsh-diff-approval 0.25.0 → 0.27.0
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/lib/client.js +2132 -639
- package/lib/index.js +68 -21
- package/lib/types/client/PendingPanel.d.ts +45 -0
- package/lib/types/client/discussion.d.ts +38 -1
- package/lib/types/client/dock.d.ts +10 -0
- package/lib/types/client/locales.d.ts +30 -4
- package/lib/types/client/panel-memory.d.ts +33 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -1114,9 +1114,20 @@ async function writeJson(file, value) {
|
|
|
1114
1114
|
throw error;
|
|
1115
1115
|
}
|
|
1116
1116
|
}
|
|
1117
|
+
/**
|
|
1118
|
+
* The directory a path names, in the platform's own spelling.
|
|
1119
|
+
*
|
|
1120
|
+
* `node:path` rather than a scan for the last `/`: on Windows the storage root is
|
|
1121
|
+
* `C:\Users\<user>\.dsh\diff-approval\workspaces`, which contains no forward slash at all — the
|
|
1122
|
+
* scan answered `.`, the `mkdir` before every write became a no-op, and the write itself threw
|
|
1123
|
+
* ENOENT, so no pending change was ever persisted there (the failure only reached a logger
|
|
1124
|
+
* warning). Both separators have to be understood, and only the platform knows which they are.
|
|
1125
|
+
*
|
|
1126
|
+
* @param file - the file whose directory is wanted.
|
|
1127
|
+
* @returns that directory, or `.` for a bare file name.
|
|
1128
|
+
*/
|
|
1117
1129
|
function dirName(file) {
|
|
1118
|
-
|
|
1119
|
-
return index < 0 ? "." : file.slice(0, index);
|
|
1130
|
+
return dirname(file);
|
|
1120
1131
|
}
|
|
1121
1132
|
/**
|
|
1122
1133
|
* File-backed pending entries, one global file keyed by path.
|
|
@@ -1928,7 +1939,7 @@ function apply(ctx, config) {
|
|
|
1928
1939
|
if (entries.length > 0) store.hydrate(entries);
|
|
1929
1940
|
if (migratedLegacy) await persistence.save(store.all());
|
|
1930
1941
|
} catch (error) {
|
|
1931
|
-
ctx.logger.
|
|
1942
|
+
ctx.logger.error(`diff-approval: loading persisted state failed, so the list starts empty: ${errorMessage(error)}`);
|
|
1932
1943
|
}
|
|
1933
1944
|
})();
|
|
1934
1945
|
return loadPromise;
|
|
@@ -2396,6 +2407,9 @@ function apply(ctx, config) {
|
|
|
2396
2407
|
let persistScheduled = false;
|
|
2397
2408
|
let lastPersistAt = 0;
|
|
2398
2409
|
let persistTimer;
|
|
2410
|
+
/** The last persist failure reported, so a writer that keeps failing says so once (cleared by a
|
|
2411
|
+
* write that works, which is what makes a later, different failure report again). */
|
|
2412
|
+
let persistFailureReported;
|
|
2399
2413
|
/** Actually write the (dirty) store to disk; one coalesced write. */
|
|
2400
2414
|
async function flushPersist() {
|
|
2401
2415
|
if (!persistDirty) return;
|
|
@@ -2404,8 +2418,13 @@ function apply(ctx, config) {
|
|
|
2404
2418
|
try {
|
|
2405
2419
|
await ensureLoaded();
|
|
2406
2420
|
await persistence.save(store.all());
|
|
2421
|
+
persistFailureReported = void 0;
|
|
2407
2422
|
} catch (error) {
|
|
2408
|
-
|
|
2423
|
+
const message = errorMessage(error);
|
|
2424
|
+
if (persistFailureReported !== message) {
|
|
2425
|
+
persistFailureReported = message;
|
|
2426
|
+
ctx.logger.error(`diff-approval: persisting pending changes failed, so the list will not survive a restart: ${message}`);
|
|
2427
|
+
}
|
|
2409
2428
|
}
|
|
2410
2429
|
}
|
|
2411
2430
|
/** Mark the store dirty and schedule one throttled, coalesced write. Pass
|
|
@@ -3029,6 +3048,50 @@ function apply(ctx, config) {
|
|
|
3029
3048
|
duplicates: 0
|
|
3030
3049
|
}
|
|
3031
3050
|
};
|
|
3051
|
+
/**
|
|
3052
|
+
* Admit what the add found and answer the wire value. Shared by the two ways in — the
|
|
3053
|
+
* one file a reader named, and the scan of a path — so a named file is listed, counted
|
|
3054
|
+
* and identified exactly like a scanned one.
|
|
3055
|
+
*
|
|
3056
|
+
* @param candidates - the entries to admit (already built, in order).
|
|
3057
|
+
* @param directory - whether the named path was a directory (its own outcome wording).
|
|
3058
|
+
* @param cut - whether the walk hit its cap.
|
|
3059
|
+
* @returns the channel's answer for this add.
|
|
3060
|
+
*/
|
|
3061
|
+
const admitNamed = async (candidates, directory, cut) => {
|
|
3062
|
+
const listed = new Set(store.list(target.sessionId).map((entry) => pathIdentity(entry.path)));
|
|
3063
|
+
const fresh = candidates.filter((entry) => !listed.has(pathIdentity(entry.path)));
|
|
3064
|
+
const duplicates = candidates.length - fresh.length;
|
|
3065
|
+
const added = await foldBatch(target.sessionId, fresh, true);
|
|
3066
|
+
const outcome = added > 0 ? "added" : duplicates > 0 ? "duplicate" : directory ? "empty" : "unchanged";
|
|
3067
|
+
const single = directory ? void 0 : fresh[0] ?? store.list(target.sessionId).find((entry) => pathIdentity(entry.path) === pathIdentity(absolute));
|
|
3068
|
+
return {
|
|
3069
|
+
ok: true,
|
|
3070
|
+
value: {
|
|
3071
|
+
outcome,
|
|
3072
|
+
added,
|
|
3073
|
+
duplicates,
|
|
3074
|
+
...single === void 0 ? {} : { id: single.id },
|
|
3075
|
+
...cut ? { truncated: true } : {}
|
|
3076
|
+
}
|
|
3077
|
+
};
|
|
3078
|
+
};
|
|
3079
|
+
const now = Date.now();
|
|
3080
|
+
if (target.exact) {
|
|
3081
|
+
const existing = store.list(target.sessionId).find((entry) => pathIdentity(entry.path) === pathIdentity(absolute));
|
|
3082
|
+
if (existing !== void 0) return await admitNamed([existing], false, false);
|
|
3083
|
+
const content = await readTextOrNone(absolute, signal);
|
|
3084
|
+
return await admitNamed(content === void 0 ? [] : [{
|
|
3085
|
+
id: absolute,
|
|
3086
|
+
sessionId: target.sessionId,
|
|
3087
|
+
path: absolute,
|
|
3088
|
+
kind: "edit",
|
|
3089
|
+
oldText: content,
|
|
3090
|
+
newText: content,
|
|
3091
|
+
updatedAt: now,
|
|
3092
|
+
sessionIds: [target.sessionId]
|
|
3093
|
+
}], false, false);
|
|
3094
|
+
}
|
|
3032
3095
|
const root = detectVcsRoot(workspace.path);
|
|
3033
3096
|
if (root === void 0) return {
|
|
3034
3097
|
ok: true,
|
|
@@ -3063,7 +3126,6 @@ function apply(ctx, config) {
|
|
|
3063
3126
|
}
|
|
3064
3127
|
};
|
|
3065
3128
|
}
|
|
3066
|
-
const now = Date.now();
|
|
3067
3129
|
const candidates = changes.map((change) => ({
|
|
3068
3130
|
id: change.path,
|
|
3069
3131
|
sessionId: target.sessionId,
|
|
@@ -3101,22 +3163,7 @@ function apply(ctx, config) {
|
|
|
3101
3163
|
});
|
|
3102
3164
|
}
|
|
3103
3165
|
}
|
|
3104
|
-
|
|
3105
|
-
const fresh = candidates.filter((entry) => !listed.has(pathIdentity(entry.path)));
|
|
3106
|
-
const duplicates = candidates.length - fresh.length;
|
|
3107
|
-
const added = await foldBatch(target.sessionId, fresh, true);
|
|
3108
|
-
const outcome = added > 0 ? "added" : duplicates > 0 ? "duplicate" : isDirectory ? "empty" : "unchanged";
|
|
3109
|
-
const single = isDirectory ? void 0 : fresh[0] ?? store.list(target.sessionId).find((entry) => pathIdentity(entry.path) === pathIdentity(absolute));
|
|
3110
|
-
return {
|
|
3111
|
-
ok: true,
|
|
3112
|
-
value: {
|
|
3113
|
-
outcome,
|
|
3114
|
-
added,
|
|
3115
|
-
duplicates,
|
|
3116
|
-
...single === void 0 ? {} : { id: single.id },
|
|
3117
|
-
...truncated ? { truncated: true } : {}
|
|
3118
|
-
}
|
|
3119
|
-
};
|
|
3166
|
+
return await admitNamed(candidates, isDirectory, truncated);
|
|
3120
3167
|
}
|
|
3121
3168
|
case "open": {
|
|
3122
3169
|
const target = openTargetOf(payload);
|
|
@@ -1,13 +1,27 @@
|
|
|
1
1
|
/** Sidebar-foot pending-edit review action and the split review panel it opens. */
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
2
3
|
import type { SessionId } from '@deepseek-ai/dsh-client-connection/client';
|
|
3
4
|
import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
4
5
|
import type { DiffApprovalBlockRange, PendingFileDiff } from '../types.ts';
|
|
5
6
|
import type { PendingPanelFace } from './slots.ts';
|
|
6
7
|
import type { Translator } from './locales.ts';
|
|
7
8
|
import { computeWholeFileDiff } from './whole-file-diff.ts';
|
|
9
|
+
import type { Discussion } from './discussion.ts';
|
|
8
10
|
import type { ChangeBlock, IntraRun } from './whole-file-diff.ts';
|
|
9
11
|
import type { HighlightSides } from './highlight.ts';
|
|
10
12
|
import type { VisibleLines } from './windowed-highlight.ts';
|
|
13
|
+
/**
|
|
14
|
+
* File-list pane width bounds for the manual split drag, in px.
|
|
15
|
+
*
|
|
16
|
+
* The floor is the width the pane's bulk footer needs to keep its three labels on ONE line
|
|
17
|
+
* (全部保留 / 全部回退 / 添加). Each is a `.action` — 12px text, 12px of side padding and a
|
|
18
|
+
* hairline, so 26px of chrome — laid out in `.bulkActions` (6px gaps, and its own 8px right inset)
|
|
19
|
+
* inside `.fileList`'s 8px side padding and 1px border: 74 + 6 + 74 + 6 + 66 + 8 + 17 = 251px, and
|
|
20
|
+
* a hair of slack on top. Narrower than that the row shrinks its buttons until the labels wrap onto
|
|
21
|
+
* two lines, which makes the pane's most-used controls two rows tall in the narrowest case — so the
|
|
22
|
+
* floor is not just a drag limit, it is the width at which the footer is still itself.
|
|
23
|
+
*/
|
|
24
|
+
export declare const MIN_LIST_WIDTH_PX = 260;
|
|
11
25
|
/** Normalize a path for comparison: forward slashes, no trailing slash. */
|
|
12
26
|
export declare function normalizeDiffPath(p: string): string;
|
|
13
27
|
/** Whether a produced-file chip path and a pending file path refer to the same
|
|
@@ -22,6 +36,30 @@ export declare function diffPathsMatch(chipPath: string, filePath: string, works
|
|
|
22
36
|
* breakpoint so the two stay consistent. */
|
|
23
37
|
export declare const SIDEBAR_AUTO_COLLAPSE_PX = 1024;
|
|
24
38
|
export declare function wrapInto(text: string, widthPx: number, measure: (t: string) => number, tabPx: number): string[];
|
|
39
|
+
/**
|
|
40
|
+
* Wrap one line of a turn the way the browser lays it out: the prose by character, each inline-code
|
|
41
|
+
* chip as one box that cannot be broken.
|
|
42
|
+
*
|
|
43
|
+
* `wrapInto` walks characters and nothing else, which is right for the code view and wrong here:
|
|
44
|
+
* a turn's chip is an inline-flex box (the chat's own inline code, see `.discussionCode`), so half
|
|
45
|
+
* of a chip never lands on the next line — it moves over whole, and the text after it starts after
|
|
46
|
+
* the box rather than inside it. Measuring the chip as ordinary text therefore reserves the wrong
|
|
47
|
+
* number of rows on exactly the lines that carry one, and a row too few is a clipped last line.
|
|
48
|
+
*
|
|
49
|
+
* The prose segments are wrapped by `wrapInto` itself, so a line with a chip in it breaks its words
|
|
50
|
+
* and its spaces exactly where a line without one does; only the chips are placed as boxes.
|
|
51
|
+
*
|
|
52
|
+
* @param line - the line's text, markers and all: the runs are read the way the render reads them.
|
|
53
|
+
* @param room - the width the line wraps in.
|
|
54
|
+
* @param measure - the prose measurer.
|
|
55
|
+
* @param chipWidth - the width of one chip's BOX (its text in the chip's own font, plus its padding
|
|
56
|
+
* and its hairline).
|
|
57
|
+
* @param tabPx - one tab stop, in px.
|
|
58
|
+
* @returns the row count for the line.
|
|
59
|
+
*/
|
|
60
|
+
export declare function wrapChipRows(line: string, room: number, measure: (text: string) => number, chipWidth: (text: string) => number, tabPx: number): number;
|
|
61
|
+
/** Create a measurer bound to `font`; falls back to a rough char estimate. */
|
|
62
|
+
export declare function makeMeasurer(font: string | undefined): ((text: string) => number) | undefined;
|
|
25
63
|
/** Full panel props composed by the sidebar footer-action slot. */
|
|
26
64
|
export type PendingPanelProps = PropsRuntime<'sidebar.footer.action'> & InjectFace<PendingPanelFace> & PropsLocale<'diff-approval'> & PendingPanelDockProps;
|
|
27
65
|
/** How the panel is hosted when it is not the footer's floating overlay: the
|
|
@@ -85,6 +123,7 @@ interface RowRange {
|
|
|
85
123
|
* shared toolbar/keyboard in split mode (its own `focus` is private here). */
|
|
86
124
|
export interface SplitDiffHandle {
|
|
87
125
|
jump: (direction: -1 | 1, wrapGuard?: boolean, singleToast?: boolean) => void;
|
|
126
|
+
land: (row: number, toCard?: boolean, flash?: boolean) => void;
|
|
88
127
|
openSearch: () => void;
|
|
89
128
|
toggleSearch: () => void;
|
|
90
129
|
closeSearch: () => boolean;
|
|
@@ -110,6 +149,12 @@ export declare const SplitDiff: import("react").ForwardRefExoticComponent<{
|
|
|
110
149
|
/** Report which source lines this view is showing, so the parent's windowed
|
|
111
150
|
* highlighter follows this view's own scroller (it has its own virtual window). */
|
|
112
151
|
onVisibleLines: (visible: VisibleLines) => void;
|
|
152
|
+
/** The threads this file carries, laid out: each hangs under the pair its anchor ends in. */
|
|
153
|
+
discussions?: readonly Discussion[];
|
|
154
|
+
/** Draws one thread's card at the width this view gives it (see `DiscussionBlock`). */
|
|
155
|
+
renderDiscussion?: (discussion: Discussion, bodyWidth: number, split: boolean) => ReactNode;
|
|
156
|
+
/** The comment action for the current selection, when the panel offers one for it. */
|
|
157
|
+
selectionComment?: ReactNode;
|
|
113
158
|
} & import("react").RefAttributes<SplitDiffHandle>>;
|
|
114
159
|
/**
|
|
115
160
|
* Reconstruct the plain text of the current selection so auto-wrap's visual
|
|
@@ -70,6 +70,17 @@ export interface Discussion {
|
|
|
70
70
|
* outdated and the quote is what the reader is shown to know what it had been about.
|
|
71
71
|
*/
|
|
72
72
|
quote?: string;
|
|
73
|
+
/**
|
|
74
|
+
* The quoted rows with one row of context on each side, as they read when the thread was created.
|
|
75
|
+
*
|
|
76
|
+
* The fingerprint a later rebuild matches the thread against. `quote` alone is too weak to say
|
|
77
|
+
* whether the code is still there: a comment on a common line — a closing brace, a blank line —
|
|
78
|
+
* found that line somewhere else in the file, followed it, and stayed live however thoroughly the
|
|
79
|
+
* code under its numbers had been rewritten. The context is what tells a genuine move from that
|
|
80
|
+
* coincidence. Absent on threads recorded before it was kept, which then falls back to matching
|
|
81
|
+
* the quote alone.
|
|
82
|
+
*/
|
|
83
|
+
quoteContext?: string;
|
|
73
84
|
/**
|
|
74
85
|
* The gutter numbers of `quote`'s lines, in the same order, so an outdated block can lay the
|
|
75
86
|
* quote out exactly as the file lays code out. See `DiscussionQuoteLine`.
|
|
@@ -118,6 +129,16 @@ export interface Discussion {
|
|
|
118
129
|
* sets the code's line height to.
|
|
119
130
|
*/
|
|
120
131
|
bodyRows?: number;
|
|
132
|
+
/**
|
|
133
|
+
* The wrap setting `bodyRows` was counted with, carried by the caller so the quote the
|
|
134
|
+
* block draws reads the very value its rows were reserved from. Set together with
|
|
135
|
+
* `bodyRows` and never on its own; an outdated block reserves the quote's rows in code
|
|
136
|
+
* rows scaled to the thread's grid (see `layoutDiscussion`), and a quote that wrapped
|
|
137
|
+
* while its rows were counted unwrapped — or the other way round — leaves the block
|
|
138
|
+
* exactly as tall as the difference, air that the bottom-anchored writing row shows as
|
|
139
|
+
* a hole in the middle of the thread.
|
|
140
|
+
*/
|
|
141
|
+
quoteWrap?: boolean;
|
|
121
142
|
/** How many older messages the row cap left out of the render. */
|
|
122
143
|
hidden?: number;
|
|
123
144
|
}
|
|
@@ -263,7 +284,23 @@ export declare function discussionOverlapping(discussions: readonly Discussion[]
|
|
|
263
284
|
* @param rowCount - how many rows the current model has.
|
|
264
285
|
* @returns the re-anchored discussion (the same object when nothing moved).
|
|
265
286
|
*/
|
|
266
|
-
export declare function remapDiscussion(discussion: Discussion, lineOf: (row: number) => number | undefined, textOf: (row: number) => string, rowCount: number): Discussion;
|
|
287
|
+
export declare function remapDiscussion(discussion: Discussion, lineOf: (row: number) => number | undefined, textOf: (row: number) => string, rowCount: number, hasNewLine?: (row: number) => boolean): Discussion;
|
|
288
|
+
/**
|
|
289
|
+
* Re-anchor a whole file's blocks in one pass.
|
|
290
|
+
*
|
|
291
|
+
* Each block is re-anchored exactly as `remapDiscussion` does it — same rules, same identities,
|
|
292
|
+
* same "nothing moved, same object back" contract — but the row windows every quote is looked up
|
|
293
|
+
* in are joined ONCE for the whole list. A rebuild (a keep, an edit, a switched file) runs this
|
|
294
|
+
* over every block a file has, and joining the rows per block is what made the pass cost grow with
|
|
295
|
+
* the number of comments on a long file.
|
|
296
|
+
*
|
|
297
|
+
* @param discussions - the blocks to re-anchor.
|
|
298
|
+
* @param lineOf - the new-file line number of a row, or `undefined` for a row that has none.
|
|
299
|
+
* @param textOf - the text of a row, for the quote comparison.
|
|
300
|
+
* @param rowCount - how many rows the current model has.
|
|
301
|
+
* @returns the re-anchored blocks, in the order they came in.
|
|
302
|
+
*/
|
|
303
|
+
export declare function remapDiscussions(discussions: readonly Discussion[], lineOf: (row: number) => number | undefined, textOf: (row: number) => string, rowCount: number, hasNewLine?: (row: number) => boolean): readonly Discussion[];
|
|
267
304
|
/** What the selection frame shows for the current selection. */
|
|
268
305
|
export interface SelectionFrameActions {
|
|
269
306
|
/** Whether keep/revert apply (the selection fully covers change blocks). */
|
|
@@ -95,6 +95,16 @@ export interface TabRegistryFace {
|
|
|
95
95
|
order: number;
|
|
96
96
|
title: () => string;
|
|
97
97
|
description?: () => string;
|
|
98
|
+
/**
|
|
99
|
+
* Glyph drawn before the title. The package types this as
|
|
100
|
+
* `ComponentType<IconProps>`; declared structurally so the build stays off
|
|
101
|
+
* that package's version line — the shape is what the seat reads, and
|
|
102
|
+
* leaving it out is why this panel's guide row had no mark of its own.
|
|
103
|
+
*/
|
|
104
|
+
icon?: (props: {
|
|
105
|
+
size?: number;
|
|
106
|
+
className?: string;
|
|
107
|
+
}) => unknown;
|
|
98
108
|
}[];
|
|
99
109
|
}): () => void;
|
|
100
110
|
}
|
|
@@ -11,6 +11,12 @@ export declare const zh: {
|
|
|
11
11
|
'panel.readFailed': string;
|
|
12
12
|
'panel.group.current': string;
|
|
13
13
|
'panel.group.others': string;
|
|
14
|
+
'panel.tabs': string;
|
|
15
|
+
'panel.tab.pending': string;
|
|
16
|
+
'panel.tab.comments': string;
|
|
17
|
+
'panel.commentsEmpty': string;
|
|
18
|
+
'panel.commentOutdated': string;
|
|
19
|
+
'panel.commentEmptyTitle': string;
|
|
14
20
|
'panel.aria': string;
|
|
15
21
|
'panel.stats': string;
|
|
16
22
|
'panel.blockPosition': string;
|
|
@@ -54,6 +60,11 @@ export declare const zh: {
|
|
|
54
60
|
'panel.discussionRoundsDesc': string;
|
|
55
61
|
'panel.quickSummonDesc': string;
|
|
56
62
|
'panel.recordShortcut': string;
|
|
63
|
+
'panel.key.goto': string;
|
|
64
|
+
'action.goto': string;
|
|
65
|
+
'action.gotoGo': string;
|
|
66
|
+
'action.gotoCancel': string;
|
|
67
|
+
'panel.gotoMissing': string;
|
|
57
68
|
'settings.keybindings': string;
|
|
58
69
|
'settings.keybindingsDesc': string;
|
|
59
70
|
'settings.comment': string;
|
|
@@ -98,8 +109,8 @@ export declare const zh: {
|
|
|
98
109
|
'discussion.promptRuleSkill': string;
|
|
99
110
|
'discussion.thinking': string;
|
|
100
111
|
'discussion.queued': string;
|
|
101
|
-
'discussion.
|
|
102
|
-
'discussion.
|
|
112
|
+
'discussion.answering': string;
|
|
113
|
+
'discussion.outdatedQuote': string;
|
|
103
114
|
'discussion.hidden': string;
|
|
104
115
|
'discussion.failed': string;
|
|
105
116
|
'discussion.stopped': string;
|
|
@@ -175,6 +186,8 @@ export declare const zh: {
|
|
|
175
186
|
'status.missing': string;
|
|
176
187
|
'panel.resolvedAsk': string;
|
|
177
188
|
'panel.keepInList': string;
|
|
189
|
+
'panel.removalQuiet': string;
|
|
190
|
+
'panel.removalQuietHint': string;
|
|
178
191
|
'panel.fileKeptAsk': string;
|
|
179
192
|
'panel.fileRevertedAsk': string;
|
|
180
193
|
'panel.confirmFileRemove': string;
|
|
@@ -230,6 +243,12 @@ export declare const en: {
|
|
|
230
243
|
'panel.readFailed': string;
|
|
231
244
|
'panel.group.current': string;
|
|
232
245
|
'panel.group.others': string;
|
|
246
|
+
'panel.tabs': string;
|
|
247
|
+
'panel.tab.pending': string;
|
|
248
|
+
'panel.tab.comments': string;
|
|
249
|
+
'panel.commentsEmpty': string;
|
|
250
|
+
'panel.commentOutdated': string;
|
|
251
|
+
'panel.commentEmptyTitle': string;
|
|
233
252
|
'panel.aria': string;
|
|
234
253
|
'panel.stats': string;
|
|
235
254
|
'panel.blockPosition': string;
|
|
@@ -273,6 +292,11 @@ export declare const en: {
|
|
|
273
292
|
'panel.discussionRoundsDesc': string;
|
|
274
293
|
'panel.quickSummonDesc': string;
|
|
275
294
|
'panel.recordShortcut': string;
|
|
295
|
+
'panel.key.goto': string;
|
|
296
|
+
'action.goto': string;
|
|
297
|
+
'action.gotoGo': string;
|
|
298
|
+
'action.gotoCancel': string;
|
|
299
|
+
'panel.gotoMissing': string;
|
|
276
300
|
'settings.keybindings': string;
|
|
277
301
|
'settings.keybindingsDesc': string;
|
|
278
302
|
'settings.comment': string;
|
|
@@ -317,8 +341,8 @@ export declare const en: {
|
|
|
317
341
|
'discussion.promptRuleSkill': string;
|
|
318
342
|
'discussion.thinking': string;
|
|
319
343
|
'discussion.queued': string;
|
|
320
|
-
'discussion.
|
|
321
|
-
'discussion.
|
|
344
|
+
'discussion.answering': string;
|
|
345
|
+
'discussion.outdatedQuote': string;
|
|
322
346
|
'discussion.hidden': string;
|
|
323
347
|
'discussion.failed': string;
|
|
324
348
|
'discussion.stopped': string;
|
|
@@ -394,6 +418,8 @@ export declare const en: {
|
|
|
394
418
|
'status.missing': string;
|
|
395
419
|
'panel.resolvedAsk': string;
|
|
396
420
|
'panel.keepInList': string;
|
|
421
|
+
'panel.removalQuiet': string;
|
|
422
|
+
'panel.removalQuietHint': string;
|
|
397
423
|
'panel.fileKeptAsk': string;
|
|
398
424
|
'panel.fileRevertedAsk': string;
|
|
399
425
|
'panel.confirmFileRemove': string;
|
|
@@ -14,6 +14,13 @@
|
|
|
14
14
|
* @module dsh-diff-approval/client/panel-memory
|
|
15
15
|
*/
|
|
16
16
|
import type { Discussion } from './discussion.ts';
|
|
17
|
+
/**
|
|
18
|
+
* Fired on `window` whenever a session's threads change. The threads are written by the file
|
|
19
|
+
* detail (which owns them) and read by the list pane (whose comments tab shows them all), so the
|
|
20
|
+
* event is what keeps the two in step: a reader who posts a comment sees it in the list at once
|
|
21
|
+
* instead of on the next poll.
|
|
22
|
+
*/
|
|
23
|
+
export declare const COMMENTS_CHANGED_EVENT = "dsh-diff-approval:comments-changed";
|
|
17
24
|
/**
|
|
18
25
|
* The comment threads a session's panel is holding.
|
|
19
26
|
* @param sessionId - the session, if any.
|
|
@@ -27,6 +34,32 @@ export declare function rememberedDiscussions(sessionId: string | undefined): Re
|
|
|
27
34
|
* @param byFile - the threads, by pending entry id.
|
|
28
35
|
*/
|
|
29
36
|
export declare function rememberDiscussions(sessionId: string | undefined, byFile: Readonly<Record<string, readonly Discussion[]>>): void;
|
|
37
|
+
/**
|
|
38
|
+
* End one comment, wherever it came from.
|
|
39
|
+
*
|
|
40
|
+
* The threads belong to the file detail, which writes them here on every change — but the list
|
|
41
|
+
* pane shows them all and ends them too, and it has no way to reach into another component's
|
|
42
|
+
* state. So the list asks the memory, and the detail reads the memory back on the event this
|
|
43
|
+
* fires (see `COMMENTS_CHANGED_EVENT`): one record, one action, whichever pane the reader used.
|
|
44
|
+
*
|
|
45
|
+
* @param sessionId - the session the panel is reviewing; nothing is forgotten without one.
|
|
46
|
+
* @param fileId - the pending entry the thread hangs in.
|
|
47
|
+
* @param discussionId - the thread to end; an id that is not there changes nothing.
|
|
48
|
+
*/
|
|
49
|
+
export declare function forgetDiscussion(sessionId: string | undefined, fileId: string, discussionId: string): void;
|
|
50
|
+
/**
|
|
51
|
+
* Whether the reader asked not to be asked again about removing one file.
|
|
52
|
+
* @param sessionId - the session the panel is reviewing; nothing is quiet without one.
|
|
53
|
+
* @param fileId - the pending entry the question was about.
|
|
54
|
+
* @returns true when the action should run without the question.
|
|
55
|
+
*/
|
|
56
|
+
export declare function removalAskQuiet(sessionId: string | undefined, fileId: string): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Stop asking whether one file should leave the list, for the rest of this page.
|
|
59
|
+
* @param sessionId - the session the panel is reviewing; nothing is recorded without one.
|
|
60
|
+
* @param fileId - the pending entry the question was about.
|
|
61
|
+
*/
|
|
62
|
+
export declare function quietenRemovalAsk(sessionId: string | undefined, fileId: string): void;
|
|
30
63
|
/**
|
|
31
64
|
* Remember that a session's panel was left showing one file, and where in it.
|
|
32
65
|
* @param sessionId - the session the panel was reviewing; nothing is recorded
|
package/package.json
CHANGED