dsh-diff-approval 0.25.0 → 0.26.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/index.js CHANGED
@@ -3029,6 +3029,50 @@ function apply(ctx, config) {
3029
3029
  duplicates: 0
3030
3030
  }
3031
3031
  };
3032
+ /**
3033
+ * Admit what the add found and answer the wire value. Shared by the two ways in — the
3034
+ * one file a reader named, and the scan of a path — so a named file is listed, counted
3035
+ * and identified exactly like a scanned one.
3036
+ *
3037
+ * @param candidates - the entries to admit (already built, in order).
3038
+ * @param directory - whether the named path was a directory (its own outcome wording).
3039
+ * @param cut - whether the walk hit its cap.
3040
+ * @returns the channel's answer for this add.
3041
+ */
3042
+ const admitNamed = async (candidates, directory, cut) => {
3043
+ const listed = new Set(store.list(target.sessionId).map((entry) => pathIdentity(entry.path)));
3044
+ const fresh = candidates.filter((entry) => !listed.has(pathIdentity(entry.path)));
3045
+ const duplicates = candidates.length - fresh.length;
3046
+ const added = await foldBatch(target.sessionId, fresh, true);
3047
+ const outcome = added > 0 ? "added" : duplicates > 0 ? "duplicate" : directory ? "empty" : "unchanged";
3048
+ const single = directory ? void 0 : fresh[0] ?? store.list(target.sessionId).find((entry) => pathIdentity(entry.path) === pathIdentity(absolute));
3049
+ return {
3050
+ ok: true,
3051
+ value: {
3052
+ outcome,
3053
+ added,
3054
+ duplicates,
3055
+ ...single === void 0 ? {} : { id: single.id },
3056
+ ...cut ? { truncated: true } : {}
3057
+ }
3058
+ };
3059
+ };
3060
+ const now = Date.now();
3061
+ if (target.exact) {
3062
+ const existing = store.list(target.sessionId).find((entry) => pathIdentity(entry.path) === pathIdentity(absolute));
3063
+ if (existing !== void 0) return await admitNamed([existing], false, false);
3064
+ const content = await readTextOrNone(absolute, signal);
3065
+ return await admitNamed(content === void 0 ? [] : [{
3066
+ id: absolute,
3067
+ sessionId: target.sessionId,
3068
+ path: absolute,
3069
+ kind: "edit",
3070
+ oldText: content,
3071
+ newText: content,
3072
+ updatedAt: now,
3073
+ sessionIds: [target.sessionId]
3074
+ }], false, false);
3075
+ }
3032
3076
  const root = detectVcsRoot(workspace.path);
3033
3077
  if (root === void 0) return {
3034
3078
  ok: true,
@@ -3063,7 +3107,6 @@ function apply(ctx, config) {
3063
3107
  }
3064
3108
  };
3065
3109
  }
3066
- const now = Date.now();
3067
3110
  const candidates = changes.map((change) => ({
3068
3111
  id: change.path,
3069
3112
  sessionId: target.sessionId,
@@ -3101,22 +3144,7 @@ function apply(ctx, config) {
3101
3144
  });
3102
3145
  }
3103
3146
  }
3104
- const listed = new Set(store.list(target.sessionId).map((entry) => pathIdentity(entry.path)));
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
- };
3147
+ return await admitNamed(candidates, isDirectory, truncated);
3120
3148
  }
3121
3149
  case "open": {
3122
3150
  const target = openTargetOf(payload);
@@ -22,6 +22,30 @@ export declare function diffPathsMatch(chipPath: string, filePath: string, works
22
22
  * breakpoint so the two stay consistent. */
23
23
  export declare const SIDEBAR_AUTO_COLLAPSE_PX = 1024;
24
24
  export declare function wrapInto(text: string, widthPx: number, measure: (t: string) => number, tabPx: number): string[];
25
+ /**
26
+ * Wrap one line of a turn the way the browser lays it out: the prose by character, each inline-code
27
+ * chip as one box that cannot be broken.
28
+ *
29
+ * `wrapInto` walks characters and nothing else, which is right for the code view and wrong here:
30
+ * a turn's chip is an inline-flex box (the chat's own inline code, see `.discussionCode`), so half
31
+ * of a chip never lands on the next line — it moves over whole, and the text after it starts after
32
+ * the box rather than inside it. Measuring the chip as ordinary text therefore reserves the wrong
33
+ * number of rows on exactly the lines that carry one, and a row too few is a clipped last line.
34
+ *
35
+ * The prose segments are wrapped by `wrapInto` itself, so a line with a chip in it breaks its words
36
+ * and its spaces exactly where a line without one does; only the chips are placed as boxes.
37
+ *
38
+ * @param line - the line's text, markers and all: the runs are read the way the render reads them.
39
+ * @param room - the width the line wraps in.
40
+ * @param measure - the prose measurer.
41
+ * @param chipWidth - the width of one chip's BOX (its text in the chip's own font, plus its padding
42
+ * and its hairline).
43
+ * @param tabPx - one tab stop, in px.
44
+ * @returns the row count for the line.
45
+ */
46
+ export declare function wrapChipRows(line: string, room: number, measure: (text: string) => number, chipWidth: (text: string) => number, tabPx: number): number;
47
+ /** Create a measurer bound to `font`; falls back to a rough char estimate. */
48
+ export declare function makeMeasurer(font: string | undefined): ((text: string) => number) | undefined;
25
49
  /** Full panel props composed by the sidebar footer-action slot. */
26
50
  export type PendingPanelProps = PropsRuntime<'sidebar.footer.action'> & InjectFace<PendingPanelFace> & PropsLocale<'diff-approval'> & PendingPanelDockProps;
27
51
  /** How the panel is hosted when it is not the footer's floating overlay: the
@@ -264,6 +264,22 @@ export declare function discussionOverlapping(discussions: readonly Discussion[]
264
264
  * @returns the re-anchored discussion (the same object when nothing moved).
265
265
  */
266
266
  export declare function remapDiscussion(discussion: Discussion, lineOf: (row: number) => number | undefined, textOf: (row: number) => string, rowCount: number): Discussion;
267
+ /**
268
+ * Re-anchor a whole file's blocks in one pass.
269
+ *
270
+ * Each block is re-anchored exactly as `remapDiscussion` does it — same rules, same identities,
271
+ * same "nothing moved, same object back" contract — but the row windows every quote is looked up
272
+ * in are joined ONCE for the whole list. A rebuild (a keep, an edit, a switched file) runs this
273
+ * over every block a file has, and joining the rows per block is what made the pass cost grow with
274
+ * the number of comments on a long file.
275
+ *
276
+ * @param discussions - the blocks to re-anchor.
277
+ * @param lineOf - the new-file line number of a row, or `undefined` for a row that has none.
278
+ * @param textOf - the text of a row, for the quote comparison.
279
+ * @param rowCount - how many rows the current model has.
280
+ * @returns the re-anchored blocks, in the order they came in.
281
+ */
282
+ export declare function remapDiscussions(discussions: readonly Discussion[], lineOf: (row: number) => number | undefined, textOf: (row: number) => string, rowCount: number): readonly Discussion[];
267
283
  /** What the selection frame shows for the current selection. */
268
284
  export interface SelectionFrameActions {
269
285
  /** Whether keep/revert apply (the selection fully covers change blocks). */
@@ -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;
@@ -98,8 +104,7 @@ export declare const zh: {
98
104
  'discussion.promptRuleSkill': string;
99
105
  'discussion.thinking': string;
100
106
  'discussion.queued': string;
101
- 'discussion.outdated': string;
102
- 'discussion.quote': string;
107
+ 'discussion.outdatedQuote': string;
103
108
  'discussion.hidden': string;
104
109
  'discussion.failed': string;
105
110
  'discussion.stopped': string;
@@ -230,6 +235,12 @@ export declare const en: {
230
235
  'panel.readFailed': string;
231
236
  'panel.group.current': string;
232
237
  'panel.group.others': string;
238
+ 'panel.tabs': string;
239
+ 'panel.tab.pending': string;
240
+ 'panel.tab.comments': string;
241
+ 'panel.commentsEmpty': string;
242
+ 'panel.commentOutdated': string;
243
+ 'panel.commentEmptyTitle': string;
233
244
  'panel.aria': string;
234
245
  'panel.stats': string;
235
246
  'panel.blockPosition': string;
@@ -317,8 +328,7 @@ export declare const en: {
317
328
  'discussion.promptRuleSkill': string;
318
329
  'discussion.thinking': string;
319
330
  'discussion.queued': string;
320
- 'discussion.outdated': string;
321
- 'discussion.quote': string;
331
+ 'discussion.outdatedQuote': string;
322
332
  'discussion.hidden': string;
323
333
  'discussion.failed': string;
324
334
  'discussion.stopped': 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-diff-approval",
3
- "version": "0.25.0",
3
+ "version": "0.26.0",
4
4
  "description": "DeepSeek Harness plugin: pending-edit review with whole-file diff and Keep/Revert",
5
5
  "packageManager": "pnpm@11.21.0",
6
6
  "author": "Wu Zhiwei",