dsh-diff-approval 0.29.1 → 0.31.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.
Files changed (40) hide show
  1. package/README.md +7 -4
  2. package/README.zh.md +7 -4
  3. package/assets/fonts/README.md +10 -8
  4. package/assets/fonts/manifest.json +1 -1
  5. package/lib/client.js +5161 -1972
  6. package/lib/index.js +4331 -442
  7. package/lib/types/annotate-skill.d.ts +34 -0
  8. package/lib/types/annotate-tool.d.ts +227 -0
  9. package/lib/types/annotate.d.ts +186 -0
  10. package/lib/types/atomic-write.d.ts +77 -0
  11. package/lib/types/client/PendingPanel.d.ts +92 -24
  12. package/lib/types/client/category-color.d.ts +62 -0
  13. package/lib/types/client/chords.d.ts +70 -17
  14. package/lib/types/client/discussion.d.ts +76 -16
  15. package/lib/types/client/dock.d.ts +8 -8
  16. package/lib/types/client/dsh-icons.d.ts +98 -0
  17. package/lib/types/client/header-entry.d.ts +27 -11
  18. package/lib/types/client/ids.d.ts +18 -0
  19. package/lib/types/client/locales.d.ts +88 -36
  20. package/lib/types/client/panel-memory.d.ts +118 -35
  21. package/lib/types/client/path-match.d.ts +50 -0
  22. package/lib/types/client/port.d.ts +77 -1
  23. package/lib/types/client/presentation-menu.d.ts +1 -1
  24. package/lib/types/client/produced-diff.d.ts +136 -22
  25. package/lib/types/client/reference.d.ts +0 -2
  26. package/lib/types/client/remap-sync.d.ts +18 -1
  27. package/lib/types/client/session-seat.d.ts +93 -0
  28. package/lib/types/client/settings.d.ts +40 -14
  29. package/lib/types/client/slots.d.ts +184 -12
  30. package/lib/types/client/store.d.ts +85 -2
  31. package/lib/types/comment-ask.d.ts +236 -0
  32. package/lib/types/comment-lines.d.ts +131 -0
  33. package/lib/types/comments.d.ts +465 -0
  34. package/lib/types/index.d.ts +7 -2
  35. package/lib/types/pending.d.ts +53 -4
  36. package/lib/types/types.d.ts +356 -4
  37. package/lib/types/vcs.d.ts +53 -26
  38. package/lib/types/version.d.ts +162 -0
  39. package/package.json +7 -2
  40. package/lib/types/client/chat-bridge.d.ts +0 -70
@@ -20,14 +20,15 @@
20
20
  import type { ReactNode } from 'react';
21
21
  import type { HostObservable, InjectFace, PropsLocale } from '@deepseek-ai/dsh-client-ui-slots';
22
22
  import type { SessionId } from '@deepseek-ai/dsh-client-connection/client';
23
- import type { PendingDiffSnapshot } from './slots.ts';
23
+ import type { PendingDiffSnapshot, PendingViewHooks } from './slots.ts';
24
24
  import type { DockSnapshot } from './dock.tsx';
25
- /** What the header entry reads from this plugin's own face: the two observables.
26
- * Everything else it needs (the button's own click) goes through the window
27
- * events, so the entry never holds a copy of the panel's state. */
28
- export interface HeaderEntryFace {
25
+ /** What the header entry reads from this plugin's own face: the two observables, and the per-session
26
+ * view reader (`pendingView`) that keeps its count on ITS session. Everything else it needs (the
27
+ * button's own click) goes through the window events, so the entry never holds a copy of the panel's
28
+ * state. */
29
+ export interface HeaderEntryFace extends PendingViewHooks {
29
30
  hooks: {
30
- /** Live pending-diff snapshot: the count on the button. */
31
+ /** Live pending-diff snapshot: the fallback count for a face with no per-session reader. */
31
32
  pending: HostObservable<PendingDiffSnapshot>;
32
33
  /** The dock's state, absent in a build without a right sidebar. */
33
34
  dock?: HostObservable<DockSnapshot>;
@@ -38,15 +39,30 @@ export interface HeaderEntryFace {
38
39
  export interface HeaderEntrySeat {
39
40
  /** The selected session and its rows; the entry is inert without one. */
40
41
  useSessions?: ((select: (state: HeaderEntrySessions) => boolean) => boolean) | undefined;
42
+ /**
43
+ * The session this seat is about, composed by the shell.
44
+ *
45
+ * This is where 0.1.7 hands it over: the session-list store it gives `useSessions` carries
46
+ * `{ ids, byId, phase, projectionsBySession }` and no selected session at all, while the shell's
47
+ * own session-scoped seats take the id from their props and ask the store about that row
48
+ * (`useSessions(s => s.byId[sessionId]?.blank)`). Older shells put `current` in the store, which is
49
+ * why both are read (see `session-seat.ts`).
50
+ */
51
+ sessionId?: SessionId | undefined;
41
52
  }
42
- /** The slice of the session list this entry reads. */
53
+ /** The slice of the session list this entry reads: whichever of these a shell carries. */
43
54
  export interface HeaderEntrySessions {
44
- /** The session the app is showing. */
55
+ /** The session the app is showing, on the shells that keep it here (0.1.5 and earlier). */
45
56
  current?: SessionId | undefined;
46
57
  /** Known sessions by id, for the blank-session check. */
47
- byId: Record<string, {
58
+ byId?: Record<string, {
48
59
  blank?: boolean;
49
- } | undefined>;
60
+ } | undefined> | undefined;
61
+ /** A blank flag carried by the STATE itself on the shells that place it there. */
62
+ blank?: boolean | undefined;
63
+ /** Every other field a shell may carry: this plugin reads none of them, and a shape it does not
64
+ * know must not disable the entry (see `session-seat.ts`). */
65
+ [field: string]: unknown;
50
66
  }
51
67
  /** Full props of the header entry: the injected face's hooks bound as
52
68
  * `usePending`/`useDock`, the framework seats, and the locale `t`. */
@@ -56,4 +72,4 @@ export type DiffApprovalHeaderEntryProps = InjectFace<HeaderEntryFace> & HeaderE
56
72
  * @param props - the bound hooks, the session seat, and the translator.
57
73
  * @returns the icon button, with the pending count while there is one.
58
74
  */
59
- export declare function DiffApprovalHeaderEntry({ usePending, useSessions, useDock, t }: DiffApprovalHeaderEntryProps): ReactNode;
75
+ export declare function DiffApprovalHeaderEntry({ usePending, useSessions, useDock, pendingView, sessionId, t }: DiffApprovalHeaderEntryProps): ReactNode;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * A fresh identifier for a record the browser half mints.
3
+ *
4
+ * `crypto.randomUUID` is a SECURE CONTEXT API. Reaching this host from a phone or a tablet over the
5
+ * LAN means plain HTTP, where `crypto.randomUUID` is undefined and calling it throws a TypeError —
6
+ * synchronously, before the code around it does anything. That is how writing a comment died on a
7
+ * tablet: the composer stayed open with the reader's words in it, nothing was sent, and no failure was
8
+ * shown, because the throw happened before the request and outside the `try` that would have said so.
9
+ * The desktop was never affected: `localhost` IS a secure context.
10
+ *
11
+ * `crypto.getRandomValues` carries no such restriction, so the fallback builds the id from it — a
12
+ * version-4 UUID, the shape the host and the client both treat as opaque but which reads as one
13
+ * everywhere it is printed. `randomUUID` is still preferred when it is there: it is the same thing
14
+ * done by the platform.
15
+ *
16
+ * @returns an identifier, unique for practical purposes.
17
+ */
18
+ export declare function newId(): string;
@@ -9,25 +9,35 @@ export declare const zh: {
9
9
  'panel.dockFailed': string;
10
10
  'panel.loading': string;
11
11
  'panel.readFailed': string;
12
- 'panel.group.current': string;
13
12
  'panel.group.others': string;
14
13
  'panel.tabs': string;
15
14
  'panel.tab.pending': string;
16
15
  'panel.tab.comments': string;
17
16
  'panel.commentsEmpty': string;
18
- 'panel.commentOutdated': string;
19
17
  'panel.aria': string;
20
18
  'panel.stats': string;
21
19
  'panel.blockPosition': string;
22
20
  'panel.blockAtEnd': string;
23
21
  'panel.blockAtStart': string;
24
22
  'panel.blockSingle': string;
25
- 'panel.viewDiff': string;
23
+ 'chip.openDefault': string;
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;
37
+ 'chip.gone': string;
26
38
  'panel.fileNotPending': string;
27
39
  'panel.selectHint': string;
28
40
  'panel.searchPlaceholder': string;
29
- 'panel.missing': string;
30
- 'panel.missingHint': string;
31
41
  'panel.externalChanged': string;
32
42
  'panel.dismiss': string;
33
43
  'panel.pasteOnCopy': string;
@@ -67,12 +77,16 @@ export declare const zh: {
67
77
  'action.gotoCancel': string;
68
78
  'panel.gotoMissing': string;
69
79
  'panel.persistFailed': string;
80
+ 'panel.commentPersistFailed': string;
81
+ 'panel.undoFailed': string;
70
82
  'settings.keybindings': string;
71
83
  'settings.keybindingsDesc': string;
72
84
  'settings.comment': string;
73
85
  'settings.commentDesc': string;
74
86
  'settings.diffView': string;
75
87
  'settings.diffViewDesc': string;
88
+ 'settings.chipMenu': string;
89
+ 'settings.chipMenu.help': string;
76
90
  'settings.diffPreview': string;
77
91
  'panel.keyDesc': string;
78
92
  'panel.shortcutNone': string;
@@ -95,12 +109,29 @@ export declare const zh: {
95
109
  'panel.key.coverRight': string;
96
110
  'panel.key.coverTop': string;
97
111
  'settings.tabLabel': string;
98
- 'row.create': string;
99
112
  'row.failed': string;
113
+ 'panel.unseen': string;
114
+ 'panel.commentCategory': string;
100
115
  'row.dismiss': string;
101
116
  'row.keepListed': string;
102
117
  'row.keepRemove': string;
103
118
  'row.revertRemove': string;
119
+ 'row.deleteRemove': string;
120
+ 'panel.batchKeepAsk': string;
121
+ 'panel.batchKeepRemoveAsk': string;
122
+ 'panel.batchRevertAsk': string;
123
+ 'panel.batchRevertRemoveAsk': string;
124
+ 'panel.batchKeepAllAsk': string;
125
+ 'panel.batchRevertAllAsk': string;
126
+ 'panel.batchRevertAllDeletedAsk': string;
127
+ 'panel.batchCloseAsk': string;
128
+ 'panel.batchDeletes': string;
129
+ 'panel.batchDeletesList': string;
130
+ 'panel.batchGo': string;
131
+ 'panel.removeOneAsk': string;
132
+ 'panel.removeCommentsOne': string;
133
+ 'panel.removeCommentsMany': string;
134
+ 'panel.batchCommentsList': string;
104
135
  'row.added': string;
105
136
  'row.removed': string;
106
137
  'action.keep': string;
@@ -110,15 +141,17 @@ export declare const zh: {
110
141
  'action.discussionCollapse': string;
111
142
  'discussion.placeholder': string;
112
143
  'discussion.marker': string;
144
+ 'discussion.frameLegend': string;
145
+ 'discussion.deletedLabel': string;
113
146
  'discussion.promptRule': string;
114
147
  'discussion.promptRuleSkill': string;
115
148
  'discussion.thinking': string;
116
- 'discussion.queued': string;
117
- 'discussion.answering': string;
118
149
  'discussion.outdatedQuote': string;
119
150
  'discussion.hidden': string;
120
151
  'discussion.failed': string;
121
152
  'discussion.stopped': string;
153
+ 'discussion.addMissing': string;
154
+ 'discussion.askMissing': string;
122
155
  'action.revert': string;
123
156
  'action.delete': string;
124
157
  'action.discussionEnd': string;
@@ -138,10 +171,6 @@ export declare const zh: {
138
171
  'action.viewUnified': string;
139
172
  'action.viewPreview': string;
140
173
  'action.viewSource': string;
141
- 'action.viewBefore': string;
142
- 'action.viewAfter': string;
143
- 'action.viewSingle': string;
144
- 'action.viewDouble': string;
145
174
  'panel.mdPreviewFailed': string;
146
175
  'action.copyHint': string;
147
176
  'action.copied': string;
@@ -161,7 +190,6 @@ export declare const zh: {
161
190
  'panel.importFailed': string;
162
191
  'action.search': string;
163
192
  'action.settings': string;
164
- 'action.presentation': string;
165
193
  'action.presentationCurrent': string;
166
194
  'action.cover': string;
167
195
  'cover.left': string;
@@ -186,13 +214,10 @@ export declare const zh: {
186
214
  'action.summonHint': string;
187
215
  'action.retry': string;
188
216
  'panel.crashed': string;
189
- 'status.kept': string;
190
- 'status.reverted': string;
191
- 'status.missing': string;
192
217
  'panel.resolvedAsk': string;
193
218
  'panel.keepInList': string;
194
- 'panel.removalQuiet': string;
195
- 'panel.removalQuietHint': string;
219
+ 'panel.keepInListQuiet': string;
220
+ 'panel.keepInListQuietHint': string;
196
221
  'panel.fileKeptAsk': string;
197
222
  'panel.fileRevertedAsk': string;
198
223
  'panel.confirmFileRemove': string;
@@ -221,6 +246,7 @@ export declare const zh: {
221
246
  'panel.addNoEntries': string;
222
247
  'panel.addMissing': string;
223
248
  'panel.addOutside': string;
249
+ 'panel.addOtherSession': string;
224
250
  'panel.addNotAFile': string;
225
251
  'panel.addFailed': string;
226
252
  'action.cancel': string;
@@ -247,25 +273,35 @@ export declare const en: {
247
273
  'panel.dockFailed': string;
248
274
  'panel.loading': string;
249
275
  'panel.readFailed': string;
250
- 'panel.group.current': string;
251
276
  'panel.group.others': string;
252
277
  'panel.tabs': string;
253
278
  'panel.tab.pending': string;
254
279
  'panel.tab.comments': string;
255
280
  'panel.commentsEmpty': string;
256
- 'panel.commentOutdated': string;
257
281
  'panel.aria': string;
258
282
  'panel.stats': string;
259
283
  'panel.blockPosition': string;
260
284
  'panel.blockAtEnd': string;
261
285
  'panel.blockAtStart': string;
262
286
  'panel.blockSingle': string;
263
- 'panel.viewDiff': string;
287
+ 'chip.openDefault': string;
288
+ 'chip.reviewInPanel': string;
289
+ 'chip.copyPath': string;
290
+ 'chip.about': string;
291
+ 'chip.about.title': string;
292
+ 'panel.confirmFileTitle': string;
293
+ 'panel.batchConfirmTitle': string;
294
+ 'chip.about.body': string;
295
+ 'chip.about.ok': string;
296
+ 'update.chip': string;
297
+ 'update.body': string;
298
+ 'update.npm': string;
299
+ 'update.github': string;
300
+ 'update.dismiss': string;
301
+ 'chip.gone': string;
264
302
  'panel.fileNotPending': string;
265
303
  'panel.selectHint': string;
266
304
  'panel.searchPlaceholder': string;
267
- 'panel.missing': string;
268
- 'panel.missingHint': string;
269
305
  'panel.externalChanged': string;
270
306
  'panel.dismiss': string;
271
307
  'panel.pasteOnCopy': string;
@@ -305,12 +341,16 @@ export declare const en: {
305
341
  'action.gotoCancel': string;
306
342
  'panel.gotoMissing': string;
307
343
  'panel.persistFailed': string;
344
+ 'panel.commentPersistFailed': string;
345
+ 'panel.undoFailed': string;
308
346
  'settings.keybindings': string;
309
347
  'settings.keybindingsDesc': string;
310
348
  'settings.comment': string;
311
349
  'settings.commentDesc': string;
312
350
  'settings.diffView': string;
313
351
  'settings.diffViewDesc': string;
352
+ 'settings.chipMenu': string;
353
+ 'settings.chipMenu.help': string;
314
354
  'settings.diffPreview': string;
315
355
  'panel.keyDesc': string;
316
356
  'panel.shortcutNone': string;
@@ -333,12 +373,29 @@ export declare const en: {
333
373
  'panel.key.coverRight': string;
334
374
  'panel.key.coverTop': string;
335
375
  'settings.tabLabel': string;
336
- 'row.create': string;
337
376
  'row.failed': string;
377
+ 'panel.unseen': string;
378
+ 'panel.commentCategory': string;
338
379
  'row.dismiss': string;
339
380
  'row.keepListed': string;
340
381
  'row.keepRemove': string;
341
382
  'row.revertRemove': string;
383
+ 'row.deleteRemove': string;
384
+ 'panel.batchKeepAsk': string;
385
+ 'panel.batchKeepRemoveAsk': string;
386
+ 'panel.batchRevertAsk': string;
387
+ 'panel.batchRevertRemoveAsk': string;
388
+ 'panel.batchKeepAllAsk': string;
389
+ 'panel.batchRevertAllAsk': string;
390
+ 'panel.batchRevertAllDeletedAsk': string;
391
+ 'panel.batchCloseAsk': string;
392
+ 'panel.batchDeletes': string;
393
+ 'panel.batchDeletesList': string;
394
+ 'panel.batchGo': string;
395
+ 'panel.removeOneAsk': string;
396
+ 'panel.removeCommentsOne': string;
397
+ 'panel.removeCommentsMany': string;
398
+ 'panel.batchCommentsList': string;
342
399
  'row.added': string;
343
400
  'row.removed': string;
344
401
  'action.keep': string;
@@ -348,15 +405,17 @@ export declare const en: {
348
405
  'action.discussionCollapse': string;
349
406
  'discussion.placeholder': string;
350
407
  'discussion.marker': string;
408
+ 'discussion.frameLegend': string;
409
+ 'discussion.deletedLabel': string;
351
410
  'discussion.promptRule': string;
352
411
  'discussion.promptRuleSkill': string;
353
412
  'discussion.thinking': string;
354
- 'discussion.queued': string;
355
- 'discussion.answering': string;
356
413
  'discussion.outdatedQuote': string;
357
414
  'discussion.hidden': string;
358
415
  'discussion.failed': string;
359
416
  'discussion.stopped': string;
417
+ 'discussion.addMissing': string;
418
+ 'discussion.askMissing': string;
360
419
  'action.revert': string;
361
420
  'action.delete': string;
362
421
  'action.discussionEnd': string;
@@ -376,10 +435,6 @@ export declare const en: {
376
435
  'action.viewUnified': string;
377
436
  'action.viewPreview': string;
378
437
  'action.viewSource': string;
379
- 'action.viewBefore': string;
380
- 'action.viewAfter': string;
381
- 'action.viewSingle': string;
382
- 'action.viewDouble': string;
383
438
  'panel.mdPreviewFailed': string;
384
439
  'action.copyHint': string;
385
440
  'action.copied': string;
@@ -399,7 +454,6 @@ export declare const en: {
399
454
  'panel.importFailed': string;
400
455
  'action.search': string;
401
456
  'action.settings': string;
402
- 'action.presentation': string;
403
457
  'action.presentationCurrent': string;
404
458
  'action.cover': string;
405
459
  'cover.left': string;
@@ -424,13 +478,10 @@ export declare const en: {
424
478
  'action.summonHint': string;
425
479
  'action.retry': string;
426
480
  'panel.crashed': string;
427
- 'status.kept': string;
428
- 'status.reverted': string;
429
- 'status.missing': string;
430
481
  'panel.resolvedAsk': string;
431
482
  'panel.keepInList': string;
432
- 'panel.removalQuiet': string;
433
- 'panel.removalQuietHint': string;
483
+ 'panel.keepInListQuiet': string;
484
+ 'panel.keepInListQuietHint': string;
434
485
  'panel.fileKeptAsk': string;
435
486
  'panel.fileRevertedAsk': string;
436
487
  'panel.confirmFileRemove': string;
@@ -459,6 +510,7 @@ export declare const en: {
459
510
  'panel.addNoEntries': string;
460
511
  'panel.addMissing': string;
461
512
  'panel.addOutside': string;
513
+ 'panel.addOtherSession': string;
462
514
  'panel.addNotAFile': string;
463
515
  'panel.addFailed': string;
464
516
  'action.cancel': string;
@@ -1,5 +1,6 @@
1
1
  /**
2
- * Where the review panel was left, for this page's lifetime.
2
+ * Where the review panel was left, and what the reader has typed but not sent, for
3
+ * this page's lifetime.
3
4
  *
4
5
  * Closing the panel is not "done reviewing": reopening it should put the reader
5
6
  * back on the file they were in, at the offset they were at. That is a fact about
@@ -13,65 +14,147 @@
13
14
  *
14
15
  * @module dsh-diff-approval/client/panel-memory
15
16
  */
16
- import type { Discussion } from './discussion.ts';
17
+ import type { Discussion, DiscussionQuoteLine } from './discussion.ts';
17
18
  /**
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.
19
+ * What the panel keeps about one thread that is NOT the host's to keep: what the reader is
20
+ * typing, whether the block is folded, and the body height the panel last measured for it.
21
+ *
22
+ * A thread itself is the host's record now (see `pending.comments`), so nothing here may
23
+ * hold a turn: the panel renders the snapshot, and this only remembers the page-local
24
+ * presentation of it. That split is what makes a poll harmless — a poll replaces the
25
+ * snapshot, and a draft typed a moment ago is not in it, so keeping the draft here is what
26
+ * stops the poll from clearing the field under the reader's hands.
22
27
  */
23
- export declare const COMMENTS_CHANGED_EVENT = "dsh-diff-approval:comments-changed";
28
+ export interface ThreadLocal {
29
+ /** What the reader has typed into the compose row (not sent yet). */
30
+ draft: string;
31
+ /** Folded to the one-row header. */
32
+ collapsed: boolean;
33
+ /**
34
+ * The last send of this thread was refused (no live agent, or the ask itself failed). It is a
35
+ * page-local fact because nothing was stored: the host has no question to record, so the block
36
+ * could only say so from here. Cleared by the next send.
37
+ */
38
+ failed?: boolean | undefined;
39
+ /**
40
+ * How many rows the block's body was last measured to occupy, or absent before the first
41
+ * measurement. The reservation follows the drawing rather than the other way round (see
42
+ * the panel's read-back), and it is kept here so a mount that comes back draws the thread
43
+ * at the size it was already measured at instead of reserving the model's estimate again.
44
+ */
45
+ bodyRows?: number | undefined;
46
+ }
24
47
  /**
25
- * The comment threads a session's panel is holding.
48
+ * The page-local thread state a session's panel is holding.
26
49
  * @param sessionId - the session, if any.
27
- * @returns the threads by pending entry id; empty when there are none.
50
+ * @returns the state by comment id; empty when there is none.
28
51
  */
29
- export declare function rememberedDiscussions(sessionId: string | undefined): Readonly<Record<string, readonly Discussion[]>>;
52
+ export declare function rememberedThreads(sessionId: string | undefined): Readonly<Record<string, ThreadLocal>>;
30
53
  /**
31
- * Remember the comment threads a session's panel holds. Called on every change, so a mount
32
- * that appears later (another presentation, or the panel reopened) starts from them.
54
+ * Remember one session's page-local thread state. Called on every change, so a mount that
55
+ * appears later starts from it.
33
56
  * @param sessionId - the session the panel is reviewing; nothing is recorded without one.
34
- * @param byFile - the threads, by pending entry id.
57
+ * @param threads - the state by comment id.
35
58
  */
36
- export declare function rememberDiscussions(sessionId: string | undefined, byFile: Readonly<Record<string, readonly Discussion[]>>): void;
59
+ export declare function rememberThreads(sessionId: string | undefined, threads: Readonly<Record<string, ThreadLocal>>): void;
37
60
  /**
38
- * End one comment, wherever it came from.
61
+ * A block the reader has PLACED on a file's lines but has not written down yet.
39
62
  *
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.
63
+ * It has no host record (see `sendDiscussion`), so nothing else on this page holds it: without this
64
+ * the reader's complaint was right — placing a comment, typing a first thought and closing the panel
65
+ * threw the whole block away, because only the words were remembered and a comment id to hang them
66
+ * under does not exist until the block is sent. Everything a mount needs to draw the block again is
67
+ * here: the entry it was placed on, the lines, the quote it was placed against, and the words.
68
+ */
69
+ export interface PlacedThread {
70
+ /**
71
+ * The block's page-local id, minted when it was placed. The comment's own id is minted when the
72
+ * block is written (see `sendDiscussion`), and this is only ever the name of an unsent block.
73
+ */
74
+ id: string;
75
+ /** The pending entry whose rows it was placed on. */
76
+ fileId: string;
77
+ /**
78
+ * The rows it was made on, plus the new-file lines that survive a rebuild. The lines are what a
79
+ * mount re-anchors from (see `remapDiscussions`); the row indices are the placement's own hint,
80
+ * stale the moment the model moves.
81
+ */
82
+ anchor: Discussion['anchor'];
83
+ /** The anchored lines as they read when it was placed (the re-anchor fingerprint). */
84
+ quote: string;
85
+ /** `quote` with one line of context on each side, as it read then. */
86
+ quoteContext?: string | undefined;
87
+ /** The gutter numbers of `quote`'s lines, in the same order. */
88
+ quoteLines?: readonly DiscussionQuoteLine[] | undefined;
89
+ /** What the reader had typed into it. */
90
+ draft: string;
91
+ }
92
+ /**
93
+ * The placed-but-unsent blocks a session's panel is holding.
94
+ * @param sessionId - the session, if any.
95
+ * @returns the blocks, oldest placement first; empty when there are none.
96
+ */
97
+ export declare function rememberedPlacedThreads(sessionId: string | undefined): readonly PlacedThread[];
98
+ /**
99
+ * Remember one session's placed-but-unsent blocks. Called on every change, so a mount that comes
100
+ * back draws them again on the same lines with the same words.
44
101
  *
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.
102
+ * A list that says the same thing as the one held is not stored: the caller rebuilds it per render,
103
+ * and only a real change (a placement, a draft, a block written or discarded) may republish it.
104
+ *
105
+ * @param sessionId - the session the panel is reviewing; nothing is recorded without one.
106
+ * @param threads - the placements, oldest first.
48
107
  */
49
- export declare function forgetDiscussion(sessionId: string | undefined, fileId: string, discussionId: string): void;
108
+ export declare function rememberPlacedThreads(sessionId: string | undefined, threads: readonly PlacedThread[]): void;
50
109
  /**
51
- * Drop every thread whose file the list no longer holds.
110
+ * Drop every placed block whose file the list no longer holds.
52
111
  *
53
- * A thread is a block of rows in one file's diff, so a file the reader has kept or reverted out of the
54
- * list leaves nothing for its comments to hang on: they go with it, and the same path coming back later
55
- * in this visit starts clean. A new object is published only when something actually goes, so the panes
56
- * that adopt this record (see `COMMENTS_CHANGED_EVENT`) do not re-adopt an equal object on every poll.
112
+ * A placement is a block of rows in ONE file's diff, so a file the reader has kept or
113
+ * reverted out of the list leaves nothing for its block to hang on: it goes with the file,
114
+ * exactly as the comments on those rows do (the host drops those; see `comments.ts`). The
115
+ * entry ids ARE paths, so a path that comes back later is the same id again — without this
116
+ * the placement was resurrected onto a file the reader had already dealt with, and
117
+ * `discussionOverlapping` went on refusing a fresh comment on those very rows because a
118
+ * block that is not on screen still counted as theirs.
119
+ *
120
+ * Nothing is published when nothing is dropped, so a poll that changes no file is not a
121
+ * change to the page's memory (see the identity discipline in `rememberPlacedThreads`).
57
122
  *
58
123
  * @param sessionId - the session the panel is reviewing; nothing is dropped without one.
59
124
  * @param listed - the pending entry ids the list is holding.
60
125
  */
61
- export declare function forgetDiscussionsNotIn(sessionId: string | undefined, listed: readonly string[]): void;
126
+ export declare function forgetPlacedThreadsNotIn(sessionId: string | undefined, listed: readonly string[]): void;
62
127
  /**
63
- * Whether the reader asked not to be asked again about removing one file.
128
+ * Whether the reader asked not to be asked again about rows leaving THIS session's list.
64
129
  * @param sessionId - the session the panel is reviewing; nothing is quiet without one.
65
- * @param fileId - the pending entry the question was about.
66
130
  * @returns true when the action should run without the question.
67
131
  */
68
- export declare function removalAskQuiet(sessionId: string | undefined, fileId: string): boolean;
132
+ export declare function removalAskQuiet(sessionId: string | undefined): boolean;
69
133
  /**
70
- * Stop asking whether one file should leave the list, for the rest of this page.
134
+ * Stop asking whether rows should leave the list, for the rest of this page: the whole session, every
135
+ * file in it.
71
136
  * @param sessionId - the session the panel is reviewing; nothing is recorded without one.
72
- * @param fileId - the pending entry the question was about.
73
137
  */
74
- export declare function quietenRemovalAsk(sessionId: string | undefined, fileId: string): void;
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;
75
158
  /**
76
159
  * Remember that a session's panel was left showing one file, and where in it.
77
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;