dsh-diff-approval 0.26.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 +1680 -551
- package/lib/index.js +23 -4
- package/lib/types/client/PendingPanel.d.ts +21 -0
- package/lib/types/client/discussion.d.ts +23 -2
- package/lib/types/client/dock.d.ts +10 -0
- package/lib/types/client/locales.d.ts +16 -0
- package/lib/types/client/panel-memory.d.ts +26 -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
|
|
@@ -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
|
|
@@ -109,6 +123,7 @@ interface RowRange {
|
|
|
109
123
|
* shared toolbar/keyboard in split mode (its own `focus` is private here). */
|
|
110
124
|
export interface SplitDiffHandle {
|
|
111
125
|
jump: (direction: -1 | 1, wrapGuard?: boolean, singleToast?: boolean) => void;
|
|
126
|
+
land: (row: number, toCard?: boolean, flash?: boolean) => void;
|
|
112
127
|
openSearch: () => void;
|
|
113
128
|
toggleSearch: () => void;
|
|
114
129
|
closeSearch: () => boolean;
|
|
@@ -134,6 +149,12 @@ export declare const SplitDiff: import("react").ForwardRefExoticComponent<{
|
|
|
134
149
|
/** Report which source lines this view is showing, so the parent's windowed
|
|
135
150
|
* highlighter follows this view's own scroller (it has its own virtual window). */
|
|
136
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;
|
|
137
158
|
} & import("react").RefAttributes<SplitDiffHandle>>;
|
|
138
159
|
/**
|
|
139
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,7 @@ 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;
|
|
267
288
|
/**
|
|
268
289
|
* Re-anchor a whole file's blocks in one pass.
|
|
269
290
|
*
|
|
@@ -279,7 +300,7 @@ export declare function remapDiscussion(discussion: Discussion, lineOf: (row: nu
|
|
|
279
300
|
* @param rowCount - how many rows the current model has.
|
|
280
301
|
* @returns the re-anchored blocks, in the order they came in.
|
|
281
302
|
*/
|
|
282
|
-
export declare function remapDiscussions(discussions: readonly Discussion[], lineOf: (row: number) => number | undefined, textOf: (row: number) => string, rowCount: number): readonly Discussion[];
|
|
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[];
|
|
283
304
|
/** What the selection frame shows for the current selection. */
|
|
284
305
|
export interface SelectionFrameActions {
|
|
285
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
|
}
|
|
@@ -60,6 +60,11 @@ export declare const zh: {
|
|
|
60
60
|
'panel.discussionRoundsDesc': string;
|
|
61
61
|
'panel.quickSummonDesc': string;
|
|
62
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;
|
|
63
68
|
'settings.keybindings': string;
|
|
64
69
|
'settings.keybindingsDesc': string;
|
|
65
70
|
'settings.comment': string;
|
|
@@ -104,6 +109,7 @@ export declare const zh: {
|
|
|
104
109
|
'discussion.promptRuleSkill': string;
|
|
105
110
|
'discussion.thinking': string;
|
|
106
111
|
'discussion.queued': string;
|
|
112
|
+
'discussion.answering': string;
|
|
107
113
|
'discussion.outdatedQuote': string;
|
|
108
114
|
'discussion.hidden': string;
|
|
109
115
|
'discussion.failed': string;
|
|
@@ -180,6 +186,8 @@ export declare const zh: {
|
|
|
180
186
|
'status.missing': string;
|
|
181
187
|
'panel.resolvedAsk': string;
|
|
182
188
|
'panel.keepInList': string;
|
|
189
|
+
'panel.removalQuiet': string;
|
|
190
|
+
'panel.removalQuietHint': string;
|
|
183
191
|
'panel.fileKeptAsk': string;
|
|
184
192
|
'panel.fileRevertedAsk': string;
|
|
185
193
|
'panel.confirmFileRemove': string;
|
|
@@ -284,6 +292,11 @@ export declare const en: {
|
|
|
284
292
|
'panel.discussionRoundsDesc': string;
|
|
285
293
|
'panel.quickSummonDesc': string;
|
|
286
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;
|
|
287
300
|
'settings.keybindings': string;
|
|
288
301
|
'settings.keybindingsDesc': string;
|
|
289
302
|
'settings.comment': string;
|
|
@@ -328,6 +341,7 @@ export declare const en: {
|
|
|
328
341
|
'discussion.promptRuleSkill': string;
|
|
329
342
|
'discussion.thinking': string;
|
|
330
343
|
'discussion.queued': string;
|
|
344
|
+
'discussion.answering': string;
|
|
331
345
|
'discussion.outdatedQuote': string;
|
|
332
346
|
'discussion.hidden': string;
|
|
333
347
|
'discussion.failed': string;
|
|
@@ -404,6 +418,8 @@ export declare const en: {
|
|
|
404
418
|
'status.missing': string;
|
|
405
419
|
'panel.resolvedAsk': string;
|
|
406
420
|
'panel.keepInList': string;
|
|
421
|
+
'panel.removalQuiet': string;
|
|
422
|
+
'panel.removalQuietHint': string;
|
|
407
423
|
'panel.fileKeptAsk': string;
|
|
408
424
|
'panel.fileRevertedAsk': string;
|
|
409
425
|
'panel.confirmFileRemove': string;
|
|
@@ -34,6 +34,32 @@ export declare function rememberedDiscussions(sessionId: string | undefined): Re
|
|
|
34
34
|
* @param byFile - the threads, by pending entry id.
|
|
35
35
|
*/
|
|
36
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;
|
|
37
63
|
/**
|
|
38
64
|
* Remember that a session's panel was left showing one file, and where in it.
|
|
39
65
|
* @param sessionId - the session the panel was reviewing; nothing is recorded
|
package/package.json
CHANGED