dsh-diff-approval 0.30.1 → 0.32.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/README.md +11 -23
- package/README.zh.md +9 -21
- package/assets/fonts/README.md +10 -8
- package/assets/fonts/manifest.json +1 -1
- package/lib/client.js +2791 -894
- package/lib/index.js +1755 -269
- package/lib/types/annotate-skill.d.ts +1 -1
- package/lib/types/annotate-tool.d.ts +11 -0
- package/lib/types/annotate.d.ts +28 -0
- package/lib/types/client/PendingPanel.d.ts +31 -21
- package/lib/types/client/category-color.d.ts +62 -0
- package/lib/types/client/chords.d.ts +70 -17
- package/lib/types/client/discussion.d.ts +10 -3
- package/lib/types/client/dock.d.ts +8 -3
- package/lib/types/client/dsh-icons.d.ts +39 -1
- package/lib/types/client/header-entry.d.ts +8 -7
- package/lib/types/client/locales.d.ts +52 -12
- package/lib/types/client/panel-memory.d.ts +24 -6
- package/lib/types/client/path-match.d.ts +50 -0
- package/lib/types/client/port.d.ts +29 -2
- package/lib/types/client/produced-diff.d.ts +83 -6
- package/lib/types/client/reference.d.ts +0 -2
- package/lib/types/client/remap-sync.d.ts +18 -1
- package/lib/types/client/session-seat.d.ts +47 -7
- package/lib/types/client/settings.d.ts +40 -14
- package/lib/types/client/slots.d.ts +115 -3
- package/lib/types/client/store.d.ts +50 -0
- package/lib/types/client/whole-file-diff.d.ts +42 -0
- package/lib/types/comment-ask.d.ts +47 -20
- package/lib/types/comments.d.ts +141 -33
- package/lib/types/index.d.ts +1 -1
- package/lib/types/pending.d.ts +30 -4
- package/lib/types/types.d.ts +186 -4
- package/lib/types/vcs.d.ts +35 -1
- package/lib/types/version.d.ts +162 -0
- package/package.json +4 -2
|
@@ -14,7 +14,6 @@ export declare const zh: {
|
|
|
14
14
|
'panel.tab.pending': string;
|
|
15
15
|
'panel.tab.comments': string;
|
|
16
16
|
'panel.commentsEmpty': string;
|
|
17
|
-
'panel.commentOutdated': string;
|
|
18
17
|
'panel.aria': string;
|
|
19
18
|
'panel.stats': string;
|
|
20
19
|
'panel.blockPosition': string;
|
|
@@ -23,12 +22,22 @@ export declare const zh: {
|
|
|
23
22
|
'panel.blockSingle': string;
|
|
24
23
|
'chip.openDefault': string;
|
|
25
24
|
'chip.reviewInPanel': string;
|
|
25
|
+
'chip.copyPath': string;
|
|
26
|
+
'chip.about': string;
|
|
27
|
+
'chip.about.title': string;
|
|
28
|
+
'panel.confirmFileTitle': string;
|
|
29
|
+
'panel.batchConfirmTitle': string;
|
|
30
|
+
'chip.about.body': string;
|
|
31
|
+
'chip.about.ok': string;
|
|
32
|
+
'update.chip': string;
|
|
33
|
+
'update.body': string;
|
|
34
|
+
'update.npm': string;
|
|
35
|
+
'update.github': string;
|
|
36
|
+
'update.dismiss': string;
|
|
26
37
|
'chip.gone': string;
|
|
27
38
|
'panel.fileNotPending': string;
|
|
28
39
|
'panel.selectHint': string;
|
|
29
40
|
'panel.searchPlaceholder': string;
|
|
30
|
-
'panel.missing': string;
|
|
31
|
-
'panel.missingHint': string;
|
|
32
41
|
'panel.externalChanged': string;
|
|
33
42
|
'panel.dismiss': string;
|
|
34
43
|
'panel.pasteOnCopy': string;
|
|
@@ -76,6 +85,8 @@ export declare const zh: {
|
|
|
76
85
|
'settings.commentDesc': string;
|
|
77
86
|
'settings.diffView': string;
|
|
78
87
|
'settings.diffViewDesc': string;
|
|
88
|
+
'settings.chipMenu': string;
|
|
89
|
+
'settings.chipMenu.help': string;
|
|
79
90
|
'settings.diffPreview': string;
|
|
80
91
|
'panel.keyDesc': string;
|
|
81
92
|
'panel.shortcutNone': string;
|
|
@@ -98,26 +109,29 @@ export declare const zh: {
|
|
|
98
109
|
'panel.key.coverRight': string;
|
|
99
110
|
'panel.key.coverTop': string;
|
|
100
111
|
'settings.tabLabel': string;
|
|
101
|
-
'row.create': string;
|
|
102
112
|
'row.failed': string;
|
|
113
|
+
'panel.unseen': string;
|
|
114
|
+
'panel.commentCategory': string;
|
|
103
115
|
'row.dismiss': string;
|
|
104
116
|
'row.keepListed': string;
|
|
105
117
|
'row.keepRemove': string;
|
|
106
118
|
'row.revertRemove': string;
|
|
119
|
+
'row.deleteRemove': string;
|
|
107
120
|
'panel.batchKeepAsk': string;
|
|
108
121
|
'panel.batchKeepRemoveAsk': string;
|
|
109
122
|
'panel.batchRevertAsk': string;
|
|
110
123
|
'panel.batchRevertRemoveAsk': string;
|
|
111
124
|
'panel.batchKeepAllAsk': string;
|
|
112
125
|
'panel.batchRevertAllAsk': string;
|
|
126
|
+
'panel.batchRevertAllDeletedAsk': string;
|
|
113
127
|
'panel.batchCloseAsk': string;
|
|
114
128
|
'panel.batchDeletes': string;
|
|
115
|
-
'panel.
|
|
129
|
+
'panel.batchDeletesList': string;
|
|
116
130
|
'panel.batchGo': string;
|
|
117
131
|
'panel.removeOneAsk': string;
|
|
118
132
|
'panel.removeCommentsOne': string;
|
|
119
133
|
'panel.removeCommentsMany': string;
|
|
120
|
-
'panel.
|
|
134
|
+
'panel.batchCommentsList': string;
|
|
121
135
|
'row.added': string;
|
|
122
136
|
'row.removed': string;
|
|
123
137
|
'action.keep': string;
|
|
@@ -214,8 +228,13 @@ export declare const zh: {
|
|
|
214
228
|
'panel.refreshDone': string;
|
|
215
229
|
'panel.refreshUnchanged': string;
|
|
216
230
|
'panel.refreshNone': string;
|
|
231
|
+
'panel.refreshCommitted': string;
|
|
217
232
|
'panel.refreshUntrackedHint': string;
|
|
218
233
|
'panel.refreshFailed': string;
|
|
234
|
+
'panel.batchRefreshVcsAsk': string;
|
|
235
|
+
'panel.refreshManyDone': string;
|
|
236
|
+
'panel.refreshManyMixed': string;
|
|
237
|
+
'panel.refreshManyFailed': string;
|
|
219
238
|
'action.addPath': string;
|
|
220
239
|
'panel.addTitle': string;
|
|
221
240
|
'panel.addPathPlaceholder': string;
|
|
@@ -232,6 +251,7 @@ export declare const zh: {
|
|
|
232
251
|
'panel.addNoEntries': string;
|
|
233
252
|
'panel.addMissing': string;
|
|
234
253
|
'panel.addOutside': string;
|
|
254
|
+
'panel.addOtherSession': string;
|
|
235
255
|
'panel.addNotAFile': string;
|
|
236
256
|
'panel.addFailed': string;
|
|
237
257
|
'action.cancel': string;
|
|
@@ -263,7 +283,6 @@ export declare const en: {
|
|
|
263
283
|
'panel.tab.pending': string;
|
|
264
284
|
'panel.tab.comments': string;
|
|
265
285
|
'panel.commentsEmpty': string;
|
|
266
|
-
'panel.commentOutdated': string;
|
|
267
286
|
'panel.aria': string;
|
|
268
287
|
'panel.stats': string;
|
|
269
288
|
'panel.blockPosition': string;
|
|
@@ -272,12 +291,22 @@ export declare const en: {
|
|
|
272
291
|
'panel.blockSingle': string;
|
|
273
292
|
'chip.openDefault': string;
|
|
274
293
|
'chip.reviewInPanel': string;
|
|
294
|
+
'chip.copyPath': string;
|
|
295
|
+
'chip.about': string;
|
|
296
|
+
'chip.about.title': string;
|
|
297
|
+
'panel.confirmFileTitle': string;
|
|
298
|
+
'panel.batchConfirmTitle': string;
|
|
299
|
+
'chip.about.body': string;
|
|
300
|
+
'chip.about.ok': string;
|
|
301
|
+
'update.chip': string;
|
|
302
|
+
'update.body': string;
|
|
303
|
+
'update.npm': string;
|
|
304
|
+
'update.github': string;
|
|
305
|
+
'update.dismiss': string;
|
|
275
306
|
'chip.gone': string;
|
|
276
307
|
'panel.fileNotPending': string;
|
|
277
308
|
'panel.selectHint': string;
|
|
278
309
|
'panel.searchPlaceholder': string;
|
|
279
|
-
'panel.missing': string;
|
|
280
|
-
'panel.missingHint': string;
|
|
281
310
|
'panel.externalChanged': string;
|
|
282
311
|
'panel.dismiss': string;
|
|
283
312
|
'panel.pasteOnCopy': string;
|
|
@@ -325,6 +354,8 @@ export declare const en: {
|
|
|
325
354
|
'settings.commentDesc': string;
|
|
326
355
|
'settings.diffView': string;
|
|
327
356
|
'settings.diffViewDesc': string;
|
|
357
|
+
'settings.chipMenu': string;
|
|
358
|
+
'settings.chipMenu.help': string;
|
|
328
359
|
'settings.diffPreview': string;
|
|
329
360
|
'panel.keyDesc': string;
|
|
330
361
|
'panel.shortcutNone': string;
|
|
@@ -347,26 +378,29 @@ export declare const en: {
|
|
|
347
378
|
'panel.key.coverRight': string;
|
|
348
379
|
'panel.key.coverTop': string;
|
|
349
380
|
'settings.tabLabel': string;
|
|
350
|
-
'row.create': string;
|
|
351
381
|
'row.failed': string;
|
|
382
|
+
'panel.unseen': string;
|
|
383
|
+
'panel.commentCategory': string;
|
|
352
384
|
'row.dismiss': string;
|
|
353
385
|
'row.keepListed': string;
|
|
354
386
|
'row.keepRemove': string;
|
|
355
387
|
'row.revertRemove': string;
|
|
388
|
+
'row.deleteRemove': string;
|
|
356
389
|
'panel.batchKeepAsk': string;
|
|
357
390
|
'panel.batchKeepRemoveAsk': string;
|
|
358
391
|
'panel.batchRevertAsk': string;
|
|
359
392
|
'panel.batchRevertRemoveAsk': string;
|
|
360
393
|
'panel.batchKeepAllAsk': string;
|
|
361
394
|
'panel.batchRevertAllAsk': string;
|
|
395
|
+
'panel.batchRevertAllDeletedAsk': string;
|
|
362
396
|
'panel.batchCloseAsk': string;
|
|
363
397
|
'panel.batchDeletes': string;
|
|
364
|
-
'panel.
|
|
398
|
+
'panel.batchDeletesList': string;
|
|
365
399
|
'panel.batchGo': string;
|
|
366
400
|
'panel.removeOneAsk': string;
|
|
367
401
|
'panel.removeCommentsOne': string;
|
|
368
402
|
'panel.removeCommentsMany': string;
|
|
369
|
-
'panel.
|
|
403
|
+
'panel.batchCommentsList': string;
|
|
370
404
|
'row.added': string;
|
|
371
405
|
'row.removed': string;
|
|
372
406
|
'action.keep': string;
|
|
@@ -463,8 +497,13 @@ export declare const en: {
|
|
|
463
497
|
'panel.refreshDone': string;
|
|
464
498
|
'panel.refreshUnchanged': string;
|
|
465
499
|
'panel.refreshNone': string;
|
|
500
|
+
'panel.refreshCommitted': string;
|
|
466
501
|
'panel.refreshUntrackedHint': string;
|
|
467
502
|
'panel.refreshFailed': string;
|
|
503
|
+
'panel.batchRefreshVcsAsk': string;
|
|
504
|
+
'panel.refreshManyDone': string;
|
|
505
|
+
'panel.refreshManyMixed': string;
|
|
506
|
+
'panel.refreshManyFailed': string;
|
|
468
507
|
'action.addPath': string;
|
|
469
508
|
'panel.addTitle': string;
|
|
470
509
|
'panel.addPathPlaceholder': string;
|
|
@@ -481,6 +520,7 @@ export declare const en: {
|
|
|
481
520
|
'panel.addNoEntries': string;
|
|
482
521
|
'panel.addMissing': string;
|
|
483
522
|
'panel.addOutside': string;
|
|
523
|
+
'panel.addOtherSession': string;
|
|
484
524
|
'panel.addNotAFile': string;
|
|
485
525
|
'panel.addFailed': string;
|
|
486
526
|
'action.cancel': string;
|
|
@@ -125,18 +125,36 @@ export declare function rememberPlacedThreads(sessionId: string | undefined, thr
|
|
|
125
125
|
*/
|
|
126
126
|
export declare function forgetPlacedThreadsNotIn(sessionId: string | undefined, listed: readonly string[]): void;
|
|
127
127
|
/**
|
|
128
|
-
* Whether the reader asked not to be asked again about
|
|
128
|
+
* Whether the reader asked not to be asked again about rows leaving THIS session's list.
|
|
129
129
|
* @param sessionId - the session the panel is reviewing; nothing is quiet without one.
|
|
130
|
-
* @param fileId - the pending entry the question was about.
|
|
131
130
|
* @returns true when the action should run without the question.
|
|
132
131
|
*/
|
|
133
|
-
export declare function removalAskQuiet(sessionId: string | undefined
|
|
132
|
+
export declare function removalAskQuiet(sessionId: string | undefined): boolean;
|
|
134
133
|
/**
|
|
135
|
-
* Stop asking whether
|
|
134
|
+
* Stop asking whether rows should leave the list, for the rest of this page: the whole session, every
|
|
135
|
+
* file in it.
|
|
136
136
|
* @param sessionId - the session the panel is reviewing; nothing is recorded without one.
|
|
137
|
-
* @param fileId - the pending entry the question was about.
|
|
138
137
|
*/
|
|
139
|
-
export declare function quietenRemovalAsk(sessionId: string | undefined
|
|
138
|
+
export declare function quietenRemovalAsk(sessionId: string | undefined): void;
|
|
139
|
+
/**
|
|
140
|
+
* Window event a dismissal dispatches, so every mounted seat hears it.
|
|
141
|
+
*
|
|
142
|
+
* The notice's chip lives in the open file's status bar, and the panel that draws it can be mounted more than
|
|
143
|
+
* once (the footer seat and the right sidebar's docked tab are separate mounts): the state above is the fact,
|
|
144
|
+
* and this event is how the OTHER mount learns about it NOW rather than at its next mount. One state, one
|
|
145
|
+
* event — so whichever seat is on screen cannot disagree about whether the reader has said "got it".
|
|
146
|
+
*/
|
|
147
|
+
export declare const UPDATE_DISMISSED_EVENT = "diff-approval:update-dismissed";
|
|
148
|
+
/**
|
|
149
|
+
* The release whose notice the reader dismissed on this page.
|
|
150
|
+
* @returns the dismissed version, or `''` when none has been dismissed here.
|
|
151
|
+
*/
|
|
152
|
+
export declare function dismissedUpdateVersion(): string;
|
|
153
|
+
/**
|
|
154
|
+
* Remember that the reader dismissed the notice for one version on this page, and tell the other mounts.
|
|
155
|
+
* @param version - the version that notice was about.
|
|
156
|
+
*/
|
|
157
|
+
export declare function dismissUpdate(version: string): void;
|
|
140
158
|
/**
|
|
141
159
|
* Remember that a session's panel was left showing one file, and where in it.
|
|
142
160
|
* @param sessionId - the session the panel was reviewing; nothing is recorded
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path comparison for the review's own references: is the path a press named the same file as one the
|
|
3
|
+
* pending list holds?
|
|
4
|
+
*
|
|
5
|
+
* Its own module because three places ask it and they must not disagree — the panel's jump to a file, the
|
|
6
|
+
* plugin's produced-file press bridge, and the store's "does the page hold this path" reader — while the
|
|
7
|
+
* panel itself is a React component module that a data-layer reader should not have to import.
|
|
8
|
+
*
|
|
9
|
+
* @module dsh-diff-approval/client/path-match
|
|
10
|
+
*/
|
|
11
|
+
/** Normalize a path for comparison: forward slashes, no trailing slash. */
|
|
12
|
+
export declare function normalizeDiffPath(p: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* The directory a file path lives in, as the path was spelled.
|
|
15
|
+
*
|
|
16
|
+
* @param p - the file's own path.
|
|
17
|
+
* @returns the directory, or undefined when the path names no directory (empty, or a bare file name).
|
|
18
|
+
*/
|
|
19
|
+
export declare function directoryOfPath(p: string | undefined): string | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* The absolute path a Markdown link names, resolved the way Markdown means it: against the directory of
|
|
22
|
+
* the file the preview is showing.
|
|
23
|
+
*
|
|
24
|
+
* A link in a document is relative to THAT DOCUMENT — `../src/foo.ts` in `docs/a.md` is `src/foo.ts` at the
|
|
25
|
+
* workspace root, not `../src/foo.ts` of the page's own URL — which is why the base is the previewed file's
|
|
26
|
+
* own directory and not the workspace root. The href is percent-decoded, its `#line`/`?query` suffix is
|
|
27
|
+
* dropped, `.`/`..` are resolved, and both separators are accepted. An href that is already absolute
|
|
28
|
+
* (drive letter or a leading slash) needs no base.
|
|
29
|
+
*
|
|
30
|
+
* Exported so the panel's preview click and its tests share one implementation and cannot disagree about
|
|
31
|
+
* what a link points at.
|
|
32
|
+
*
|
|
33
|
+
* @param baseDirectory - the directory of the file being previewed, or the workspace root when that file's
|
|
34
|
+
* own path is unknown.
|
|
35
|
+
* @param href - the anchor's href, as written in the Markdown.
|
|
36
|
+
* @returns the absolute path, or undefined when it cannot be named (an empty href, or a relative href with
|
|
37
|
+
* no base to resolve against).
|
|
38
|
+
*/
|
|
39
|
+
export declare function resolveMarkdownHref(baseDirectory: string | undefined, href: string): string | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* Whether a produced-file chip path and a pending file path refer to the same file, tolerant of separator
|
|
42
|
+
* style (\\ vs /) and of a workspace-relative vs absolute form. `chipPath` is typically the harness's
|
|
43
|
+
* workspace-relative forward-slash path; `filePath` is the host's absolute native-separator path. Matching
|
|
44
|
+
* is case-insensitive so a Windows drive/segment case difference does not miss the file the user clicked.
|
|
45
|
+
* @param chipPath - the path a press (or a reference) named.
|
|
46
|
+
* @param filePath - the path the pending list holds.
|
|
47
|
+
* @param workspacePath - the session's workspace root, when it is known.
|
|
48
|
+
* @returns whether the two name one file.
|
|
49
|
+
*/
|
|
50
|
+
export declare function diffPathsMatch(chipPath: string, filePath: string, workspacePath: string | undefined): boolean;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* @module dsh-diff-approval/client/port
|
|
6
6
|
*/
|
|
7
7
|
import type { ClientConnectionRpc, SessionId } from '@deepseek-ai/dsh-client-connection/client';
|
|
8
|
-
import type { CommentAnchor, CommentQuoteLine, DiffApprovalActionValue, DiffApprovalAddValue, DiffApprovalBlockRange, DiffApprovalBrowseValue, DiffApprovalBulkValue, DiffApprovalCommentAddValue, DiffApprovalCommentAskValue, DiffApprovalCommentRemoveManyValue, DiffApprovalCommentRemoveValue, DiffApprovalListValue, DiffApprovalOpenAction, DiffApprovalOpenValue, DiffApprovalPreviewImageValue, DiffApprovalRefreshValue, VcsImportValue } from '../types.ts';
|
|
8
|
+
import type { CommentAnchor, CommentQuoteLine, DiffApprovalActionValue, DiffApprovalAddValue, DiffApprovalBlockRange, DiffApprovalBrowseValue, DiffApprovalBulkValue, DiffApprovalCommentAddValue, DiffApprovalCommentAskValue, DiffApprovalCommentRemoveManyValue, DiffApprovalCommentRemoveValue, DiffApprovalListCountValue, DiffApprovalListValue, DiffApprovalOpenAction, DiffApprovalOpenValue, DiffApprovalPreviewImageValue, DiffApprovalRefreshValue, DiffApprovalUpdateValue, VcsImportValue } from '../types.ts';
|
|
9
9
|
/** The channel the host half registers and this port calls. */
|
|
10
10
|
export declare const DIFF_APPROVAL_CHANNEL = "/diff-approval";
|
|
11
11
|
/**
|
|
@@ -33,6 +33,18 @@ export interface CommentDraft {
|
|
|
33
33
|
export interface DiffApprovalPort {
|
|
34
34
|
/** Read one session's pending entries (plus its workspace root), oldest capture first. */
|
|
35
35
|
list(sessionId: SessionId): Promise<DiffApprovalListValue>;
|
|
36
|
+
/**
|
|
37
|
+
* Ask HOW MANY rows a full `list` read would carry for this session, reading no file content.
|
|
38
|
+
*
|
|
39
|
+
* This is the badge's own question: the full read ships every visible entry's whole old/new text
|
|
40
|
+
* (megabytes on a long-lived session), while the count costs one stat per visible row on the host and
|
|
41
|
+
* puts a single number on the wire. It is the SAME visibility rule as `list` on the host's side.
|
|
42
|
+
*
|
|
43
|
+
* A host that predates the endpoint throws instead — its handler answers `unknown endpoint
|
|
44
|
+
* "list-count"` — and the caller is expected to fall back to the full read (see
|
|
45
|
+
* `PendingDiffStore.refreshCount`), which is exactly the behaviour a page had before this verb existed.
|
|
46
|
+
*/
|
|
47
|
+
listCount(sessionId: SessionId): Promise<DiffApprovalListCountValue>;
|
|
36
48
|
/** Keep one operation. `keepListed` leaves the resolved entry in the list. */
|
|
37
49
|
keep(sessionId: SessionId, id: string, keepListed?: boolean): Promise<DiffApprovalActionValue>;
|
|
38
50
|
/** Revert one operation. `keepListed` leaves the resolved entry in the list. */
|
|
@@ -56,6 +68,8 @@ export interface DiffApprovalPort {
|
|
|
56
68
|
* path must be one regular file: a directory is refused instead of being scanned.
|
|
57
69
|
*/
|
|
58
70
|
addPath(sessionId: SessionId, path: string, includeUnchanged: boolean, exact?: boolean): Promise<DiffApprovalAddValue>;
|
|
71
|
+
/** Tell the host the reader is looking at a file: the row's unseen dot goes out. */
|
|
72
|
+
markSeen(sessionId: SessionId, id: string): Promise<void>;
|
|
59
73
|
/** Open one file with its default application or reveal it in the folder. */
|
|
60
74
|
open(sessionId: SessionId, id: string, action: DiffApprovalOpenAction): Promise<DiffApprovalOpenValue>;
|
|
61
75
|
/** Keep every pending entry of one session in a single host call (one batch). */
|
|
@@ -90,8 +104,21 @@ export interface DiffApprovalPort {
|
|
|
90
104
|
* calls is a different thing — N requests, N writes — which is what this endpoint exists to avoid.
|
|
91
105
|
*/
|
|
92
106
|
commentRemoveMany(sessionId: SessionId, ids: readonly string[]): Promise<DiffApprovalCommentRemoveManyValue>;
|
|
93
|
-
/**
|
|
107
|
+
/** Asks one stored comment as its own turn of the session. */
|
|
94
108
|
commentAsk(sessionId: SessionId, id: string, prompt: string, text: string): Promise<DiffApprovalCommentAskValue>;
|
|
109
|
+
/** Tell the host the reader is looking at a comment: the card's unseen dot goes out. */
|
|
110
|
+
commentSeen(sessionId: SessionId, id: string): Promise<void>;
|
|
111
|
+
/**
|
|
112
|
+
* Ask whether a newer release of this plugin is published, and what changed since the
|
|
113
|
+
* installed one.
|
|
114
|
+
*
|
|
115
|
+
* No session is named: the answer is about the PACKAGE, not about a review. The host owns
|
|
116
|
+
* the whole question — it reads its own `package.json`, reaches the registry (a browser
|
|
117
|
+
* may not: no CORS header there) and decides `newer` — so a caller only has to render the
|
|
118
|
+
* answer. A host that predates the endpoint answers `unknown endpoint "update-check"`, and
|
|
119
|
+
* the caller is expected to ignore that failure exactly as it ignores a network one.
|
|
120
|
+
*/
|
|
121
|
+
checkUpdate(): Promise<DiffApprovalUpdateValue>;
|
|
95
122
|
}
|
|
96
123
|
/** Build the port over one generic RPC caller.
|
|
97
124
|
* @param rpc - the connection's channel caller.
|
|
@@ -13,35 +13,89 @@
|
|
|
13
13
|
* `a[href]` any other link naming a file `href` = the path
|
|
14
14
|
*
|
|
15
15
|
* Everything else is left entirely alone: a link to a URL, an in-page anchor, a chip naming something
|
|
16
|
-
* that is not a file, a file this panel does not hold, a modified press (Ctrl/Cmd/Shift/
|
|
17
|
-
* middle click — gestures the harness may give its own meaning later), and any press inside this
|
|
16
|
+
* that is not a file, a file LINK whose file this panel does not hold, a modified press (Ctrl/Cmd/Shift/
|
|
17
|
+
* Alt, or a middle click — gestures the harness may give its own meaning later), and any press inside this
|
|
18
18
|
* plugin's own surface, whose buttons carry paths too and belong to the panel that is already showing
|
|
19
|
-
* the file.
|
|
19
|
+
* the file. A file-list ROW is the one press taken over whether or not the panel holds its file (see
|
|
20
|
+
* `ROW_SELECTOR`); "we do not hold it" then decides what the menu offers, not whether it appears.
|
|
20
21
|
*
|
|
21
22
|
* The name of the module (and of the event it raises) dates from when the produced-file card was the
|
|
22
23
|
* only press of this kind; `data-presented-files-row` is that same list under 0.1.7's name for it.
|
|
23
24
|
*/
|
|
25
|
+
import type { PendingDiffSnapshot } from './slots.ts';
|
|
24
26
|
/** Window event dispatched when a pending file's press is taken over; the panel listens for it. */
|
|
25
27
|
export declare const CHIP_MENU_EVENT = "diff-approval:chip-menu";
|
|
28
|
+
/**
|
|
29
|
+
* Whether the page HOLDS this path — the `held` label a press carries into the menu.
|
|
30
|
+
*
|
|
31
|
+
* It is a LABEL now, not a gate: every file-list row's press becomes the menu regardless, and `held` only
|
|
32
|
+
* decides whether "在审批面板中查看" opens the row directly or adds it first (see `PendingPanel`). It is
|
|
33
|
+
* answered from the views the page is holding, i.e. from the last FULL read of each session: while every
|
|
34
|
+
* surface is shut nothing re-reads the list, so a row edited since then is labelled not-held. That is
|
|
35
|
+
* acceptable rather than hidden: the add answers the truth (a path already listed comes back `duplicate` and
|
|
36
|
+
* selects the same entry), so the item still opens the file — one request later. What the label must not do
|
|
37
|
+
* is claim a row exists that does not: `false` errs towards adding, never towards acting on nothing.
|
|
38
|
+
* @param views - the views this page is holding, one per session it has read (see `PendingDiffStore.views`).
|
|
39
|
+
* @param path - the path a press named.
|
|
40
|
+
* @returns whether any held view holds it.
|
|
41
|
+
*/
|
|
42
|
+
export declare function panelHolds(views: readonly PendingDiffSnapshot[], path: string): boolean;
|
|
26
43
|
/** Window event dispatched by the panel to open a file in the panel; PendingPanel listens for it. */
|
|
27
44
|
export declare const OPEN_FILE_EVENT = "diff-approval:open-file";
|
|
28
|
-
/** What a press tells the panel: which file,
|
|
45
|
+
/** What a press tells the panel: which file, where the press was (the menu hangs under it), and whether
|
|
46
|
+
* the review list already holds that file. */
|
|
29
47
|
export interface ProducedChipMenuDetail {
|
|
30
48
|
/** The path the pressed element names. */
|
|
31
49
|
path: string;
|
|
32
50
|
/** The pressed element's own box, in viewport coordinates. */
|
|
33
51
|
x: number;
|
|
34
52
|
y: number;
|
|
53
|
+
/**
|
|
54
|
+
* Whether the review list holds this path right now.
|
|
55
|
+
*
|
|
56
|
+
* It decides what the menu's items DO, never whether the menu appears: a row press always becomes the
|
|
57
|
+
* menu, and "not held" is what makes "在审批面板中查看" add the path first (see `PendingPanel`). It is
|
|
58
|
+
* also what a future item that NEEDS a listed entry has to gate on — pressing keep or revert for a file
|
|
59
|
+
* the host is not holding would simply fail.
|
|
60
|
+
*/
|
|
61
|
+
held: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* The session the press belongs to, when its own surface says so.
|
|
64
|
+
*
|
|
65
|
+
* WITHOUT THIS the panel can only ask the session IT is showing to add the path, and the host resolves that
|
|
66
|
+
* path against that session's workspace: a press in session A's transcript while the panel is still bound
|
|
67
|
+
* to session B answers `outside` ("路径不在当前工作区内") — measured, and it was the reader's bug: the first
|
|
68
|
+
* press refused and the second worked, because by then the panel had followed the shell's selection.
|
|
69
|
+
*
|
|
70
|
+
* Read from the ANCESTORS of the pressed element only (see `sessionOfPress`), and `undefined` when none of
|
|
71
|
+
* them names a session. That is the honest half: the panel then asks its own session and, if the host
|
|
72
|
+
* refuses, says so with the wording that names what was checked rather than blaming the path.
|
|
73
|
+
*/
|
|
74
|
+
sessionId?: string | undefined;
|
|
35
75
|
}
|
|
36
76
|
/** What the bridge needs from the host to decide, and to report. */
|
|
37
77
|
export interface ProducedChipBridge {
|
|
38
78
|
/**
|
|
39
|
-
* Whether the
|
|
40
|
-
*
|
|
79
|
+
* Whether the review list holds this path right now.
|
|
80
|
+
*
|
|
81
|
+
* A LABEL, not a gate: it rides every press this bridge takes over (a file-list row and a message's file
|
|
82
|
+
* link alike) and decides only what the menu's items DO — see `held` above. It is called synchronously on
|
|
83
|
+
* every press because that is when the menu is raised, and there is no second chance at a press.
|
|
41
84
|
*/
|
|
42
85
|
isPending: (path: string) => boolean;
|
|
43
86
|
/** A pending file's press was taken over: open the menu the press asked for. */
|
|
44
87
|
onMenu: (detail: ProducedChipMenuDetail) => void;
|
|
88
|
+
/**
|
|
89
|
+
* Whether this plugin's file menu is MOUNTED at all — the reader's setting, read at press time.
|
|
90
|
+
*
|
|
91
|
+
* `false` leaves every press exactly as DSH has it: no menu, no `preventDefault`, not even a path lookup,
|
|
92
|
+
* so the plugin's presence is indistinguishable from it never having loaded. It is read per press rather
|
|
93
|
+
* than latched, because the setting can be switched while the page is open.
|
|
94
|
+
*
|
|
95
|
+
* ABSENT means enabled: a host, or a test, that offers no setting keeps the behaviour this bridge has
|
|
96
|
+
* always had.
|
|
97
|
+
*/
|
|
98
|
+
enabled?: (() => boolean) | undefined;
|
|
45
99
|
}
|
|
46
100
|
/**
|
|
47
101
|
* Press a file's own press again — the shell's open, run the only way this plugin can run it
|
|
@@ -55,6 +109,29 @@ export interface ProducedChipBridge {
|
|
|
55
109
|
* @returns whether an element naming it was found to press.
|
|
56
110
|
*/
|
|
57
111
|
export declare function replayFilePress(path: string): boolean;
|
|
112
|
+
/**
|
|
113
|
+
* The session a press belongs to, read from the pressed element's OWN surface.
|
|
114
|
+
*
|
|
115
|
+
* Ancestors only, and the NEAREST ancestor that names a session wins — whichever spelling it uses:
|
|
116
|
+
*
|
|
117
|
+
* `data-row-key="session:<id>"` a session-LIST item, measured by this repo's e2e harness. Right for a
|
|
118
|
+
* press raised inside the sidebar, and NOT an ancestor of a transcript
|
|
119
|
+
* chip (measured on the live shell: exactly one such element in the whole
|
|
120
|
+
* document, and it does not contain the chip).
|
|
121
|
+
* `data-conversation-session` the conversation BODY, measured on the live shell — this is the one that
|
|
122
|
+
* covers the reader's press, a chip inside a message.
|
|
123
|
+
* `data-session-id` unmeasured alternative spelling, kept as a last resort.
|
|
124
|
+
*
|
|
125
|
+
* Walking the chain rather than asking three separate `closest` queries is what makes "nearest wins" true
|
|
126
|
+
* across spellings: a chip inside a session row that ALSO nests a conversation body takes the nearer
|
|
127
|
+
* body's session. A document-wide search is still deliberately NOT done — an identity hit may be a SIBLING,
|
|
128
|
+
* an `href` or an `aria-controls`, and guessing which session a press belongs to would be worse than not
|
|
129
|
+
* knowing: a wrong guess would ask a THIRD session to add the file, which this can never do.
|
|
130
|
+
*
|
|
131
|
+
* @param press - the element the press landed on.
|
|
132
|
+
* @returns the session id, or undefined when no ancestor names one.
|
|
133
|
+
*/
|
|
134
|
+
export declare function sessionOfPress(press: Element): string | undefined;
|
|
58
135
|
/**
|
|
59
136
|
* Start routing the presses that would open a file in the shell's own viewer.
|
|
60
137
|
*
|
|
@@ -5,8 +5,6 @@
|
|
|
5
5
|
* the panel.
|
|
6
6
|
* @module dsh-diff-approval/client/reference
|
|
7
7
|
*/
|
|
8
|
-
/** The marker that replaces a reference's line number when its lines are gone. */
|
|
9
|
-
export declare const LINE_MISSING_LABEL = "LINE_MISSING";
|
|
10
8
|
/**
|
|
11
9
|
* Last path segment of a file path, any separator style.
|
|
12
10
|
* @param path - the path to shorten.
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* `reference.ts`.
|
|
7
7
|
* @module dsh-diff-approval/client/remap-sync
|
|
8
8
|
*/
|
|
9
|
+
import type { SessionId } from '@deepseek-ai/dsh-client-connection/client';
|
|
9
10
|
import type { PendingDiffStore } from './store.ts';
|
|
10
11
|
/** One queued message's minimal content block; text blocks carry `text`, and every other kind passes through verbatim. */
|
|
11
12
|
interface QueueBlock {
|
|
@@ -28,6 +29,22 @@ export interface ReferenceRemapOpts {
|
|
|
28
29
|
readQueue: () => readonly QueueMessage[];
|
|
29
30
|
/** Apply an edit mutation to one queued message's full content. */
|
|
30
31
|
writeQueue: (itemId: string, content: readonly QueueBlock[]) => void;
|
|
32
|
+
/**
|
|
33
|
+
* The session whose list this sync follows and whose view supplies the workspace path, read per
|
|
34
|
+
* observation.
|
|
35
|
+
*
|
|
36
|
+
* The pending list is per session now, and the draft and the queue this sync rewrites belong to the
|
|
37
|
+
* session the page is SHOWING (see `shownSessionId`): observing another session's files would remap one
|
|
38
|
+
* session's references inside another session's composer. It must not be a pointer to whichever mount
|
|
39
|
+
* refreshed last either — with a dock tab on one session and the header on another, alternating polls
|
|
40
|
+
* moved that pointer about once a second and dropped these baselines with it, so a file that really
|
|
41
|
+
* changed was only ever re-seeded and never remapped. The baselines are still dropped when this answers
|
|
42
|
+
* a DIFFERENT session, so a session switch seeds fresh baselines instead of reading as "file changed".
|
|
43
|
+
*
|
|
44
|
+
* Absent, the page-wide view is observed instead — the same fallback a reader with no session gets
|
|
45
|
+
* (`viewFor(undefined)`, which answers the newest read).
|
|
46
|
+
*/
|
|
47
|
+
sessionId?: (() => SessionId | undefined) | undefined;
|
|
31
48
|
}
|
|
32
49
|
/**
|
|
33
50
|
* Subscribe to the pending store and remap references as each file's content
|
|
@@ -39,6 +56,6 @@ export interface ReferenceRemapOpts {
|
|
|
39
56
|
*/
|
|
40
57
|
export declare function attachReferenceRemap(opts: ReferenceRemapOpts): {
|
|
41
58
|
unsubscribe: () => void;
|
|
42
|
-
remapFile: (path: string, oldText: string, newText: string) => void;
|
|
59
|
+
remapFile: (sessionId: SessionId, path: string, oldText: string, newText: string) => void;
|
|
43
60
|
};
|
|
44
61
|
export {};
|
|
@@ -18,12 +18,27 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import type { SessionId } from '@deepseek-ai/dsh-client-connection/client';
|
|
20
20
|
/**
|
|
21
|
-
* Publish the session the header entry is showing.
|
|
21
|
+
* Publish the session the header entry is showing. Call it from an EFFECT, never during render.
|
|
22
|
+
*
|
|
23
|
+
* Writing this during the header's render was the cross-talk's other half: the value a render read
|
|
24
|
+
* depended on whether another component had already rendered, so two mounts could disagree about which
|
|
25
|
+
* session was showing — and, in concurrent rendering, a discarded render could publish a session that
|
|
26
|
+
* was never committed. From an effect the value only ever describes a committed render.
|
|
22
27
|
* @param id - the id, or `undefined` when the shell named none.
|
|
23
28
|
*/
|
|
24
29
|
export declare function publishSessionId(id: SessionId | undefined): void;
|
|
25
30
|
/** The session the header entry last showed, or `undefined` when it never named one. */
|
|
26
31
|
export declare function publishedSessionId(): SessionId | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* The published session as a React value: the root-scoped mounts (the sidebar footer's badge) have no
|
|
34
|
+
* session prop, so this is how they follow the one the Session header is showing.
|
|
35
|
+
*
|
|
36
|
+
* It subscribes rather than reading the module global at render time, because the write now happens in
|
|
37
|
+
* an effect: a reader that only read the global would keep whatever the value was on its first render
|
|
38
|
+
* and never notice the reader switching sessions.
|
|
39
|
+
* @returns the session the header entry is showing, or `undefined`.
|
|
40
|
+
*/
|
|
41
|
+
export declare function usePublishedSessionId(): SessionId | undefined;
|
|
27
42
|
/**
|
|
28
43
|
* The selected session a shell's session-list state names, by every field the shells have used.
|
|
29
44
|
* @param state - the store state handed to the selector.
|
|
@@ -44,10 +59,35 @@ export declare function selectedSessionOf(state: unknown): SessionId | undefined
|
|
|
44
59
|
*/
|
|
45
60
|
export declare function sessionIsBlank(state: unknown, id: SessionId | undefined): boolean;
|
|
46
61
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
62
|
+
* Whether a seat has NO reviewable session: it cannot name one at all, or the shell says the one it names
|
|
63
|
+
* is blank (a brand-new session with nothing said in it yet).
|
|
64
|
+
*
|
|
65
|
+
* The two halves are ONE question and must be asked of the SAME id — the id the seat would read its view
|
|
66
|
+
* for. Splitting them is what this helper exists to stop: a seat that drew its view for the published
|
|
67
|
+
* session while testing `sessionIsBlank` against a different (absent) id stayed enabled on a blank session
|
|
68
|
+
* and drew the previous session's list — from another workspace as readily as from its own, because nothing
|
|
69
|
+
* in this client compares workspaces.
|
|
70
|
+
*
|
|
71
|
+
* @param state - the session-list state handed to the selector.
|
|
72
|
+
* @param id - the session this seat would read its view for.
|
|
73
|
+
* @returns true when there is nothing to review.
|
|
74
|
+
*/
|
|
75
|
+
export declare function unreviewableSession(state: unknown, id: SessionId | undefined): boolean;
|
|
76
|
+
/**
|
|
77
|
+
* The session the PAGE is showing, resolved the way a whole-page reader has to resolve it: the
|
|
78
|
+
* session-list store's own selection first, else what the Session header entry published.
|
|
79
|
+
*
|
|
80
|
+
* `readSessionsSelection` is supplied by the plugin body (it owns the `sessions` service), and may
|
|
81
|
+
* return `undefined` for a shell whose state names none — 0.1.7's store carries no selected session and
|
|
82
|
+
* hands the id to its own session-scoped seats instead, which is why the published id is read here too
|
|
83
|
+
* (`publishSessionId` runs from the header entry's effect). This is a plain read for non-React code: the
|
|
84
|
+
* component that publishes it reads the SUBSCRIBED value ({@link usePublishedSessionId}).
|
|
85
|
+
*
|
|
86
|
+
* It is deliberately NOT every session anyone has read: the page-wide readers here (the reference remap,
|
|
87
|
+
* which rewrites the visible composer's draft) mean the session the reader is looking at, and taking the
|
|
88
|
+
* newest session some other mount polled would address a composer that is not on screen.
|
|
89
|
+
*
|
|
90
|
+
* @param readSessionsSelection - the store's own selected session, when its shape names one.
|
|
91
|
+
* @returns the id, or `undefined` when nothing on this page names a session.
|
|
52
92
|
*/
|
|
53
|
-
export declare function
|
|
93
|
+
export declare function shownSessionId(readSessionsSelection: () => SessionId | undefined): SessionId | undefined;
|