dsh-diff-approval 0.36.1 → 0.37.1

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.
@@ -0,0 +1,163 @@
1
+ /** One node of a parsed Markdown document. */
2
+ export interface MarkdownTreeNode {
3
+ /** Stable path inside the document (`0.2.1`), for debugging and tests. */
4
+ id: string;
5
+ /** `marked`'s token type (`heading`, `paragraph`, `list_item`, `text`, …), or a synthetic one. */
6
+ type: string;
7
+ /** The node's own SOURCE spelling (what was written), used for span and exact-match comparisons. */
8
+ raw: string;
9
+ /** The node's plain text where the token has one (`text`, `code`, heading text, …). */
10
+ text: string;
11
+ /** The token's own attributes: heading depth, fence language, link href/title, list style, cell align. */
12
+ attrs: Record<string, string | number | boolean>;
13
+ children: MarkdownTreeNode[];
14
+ /** Absolute character offsets into the side's source: `[start, end)`. */
15
+ start: number;
16
+ end: number;
17
+ /** 1-based inclusive source lines the node covers. */
18
+ lines: {
19
+ start: number;
20
+ end: number;
21
+ };
22
+ }
23
+ /** One matched old/new node pair. */
24
+ export interface TreeMatchPair {
25
+ old: MarkdownTreeNode;
26
+ new: MarkdownTreeNode;
27
+ /** Same source spelling for the whole node: nothing changed at or under it. */
28
+ exact: boolean;
29
+ /** How alike the two nodes' visible text is (1 = identical, 0 = nothing in common). */
30
+ similarity: number;
31
+ /** The match operations inside the pair, in reading order. */
32
+ children: TreeMatchOp[];
33
+ }
34
+ /** One alignment step: a pair, or a node that exists on one side only. */
35
+ export type TreeMatchOp = {
36
+ kind: 'pair';
37
+ pair: TreeMatchPair;
38
+ } | {
39
+ kind: 'oldOnly';
40
+ node: MarkdownTreeNode;
41
+ } | {
42
+ kind: 'newOnly';
43
+ node: MarkdownTreeNode;
44
+ };
45
+ /** The whole-document match. */
46
+ export interface TreeMatchResult {
47
+ oldRoot: MarkdownTreeNode[];
48
+ newRoot: MarkdownTreeNode[];
49
+ /** The outermost alignment, in reading order. */
50
+ ops: TreeMatchOp[];
51
+ }
52
+ /** What `flattenTreeMatch` returns: every pair and every one-sided node, pairs before their children. */
53
+ export interface FlatTreeMatch {
54
+ pairs: TreeMatchPair[];
55
+ oldOnly: MarkdownTreeNode[];
56
+ newOnly: MarkdownTreeNode[];
57
+ }
58
+ /** Similarity of two pieces of visible text: longest common subsequence over the longer one (0..1). */
59
+ export declare function textSimilarity(left: string, right: string): number;
60
+ /**
61
+ * The visible text a node contributes: inline text and code spans verbatim, containers by their children,
62
+ * images by their alt, structure with no text (rules, comments) as `''`.
63
+ */
64
+ export declare function nodeVisibleText(node: MarkdownTreeNode): string;
65
+ /** The visible text of a node, whitespace-folded: what the preview's similarity compares. */
66
+ export declare function nodeMatchText(node: MarkdownTreeNode): string;
67
+ /** Parse one side into a forest, dropping the `space` tokens that only separate blocks. */
68
+ export declare function parseMarkdownTree(text: string): MarkdownTreeNode[];
69
+ /** How alike two nodes are: the similarity of their visible text (1 = identical, 0 = nothing in common). */
70
+ export declare function nodeSimilarity(oldNode: MarkdownTreeNode, newNode: MarkdownTreeNode): number;
71
+ /** Match two whole documents tree-first. */
72
+ export declare function matchMarkdownTrees(oldText: string, newText: string): TreeMatchResult;
73
+ /** Every pair and one-sided node of a match, pairs before their own children (pre-order). */
74
+ export declare function flattenTreeMatch(ops: readonly TreeMatchOp[]): FlatTreeMatch;
75
+ /**
76
+ * Whether a pair's whole subtree is UNCHANGED: the same node type and attributes, and every child fully
77
+ * matched (no one-sided child anywhere).
78
+ *
79
+ * This is the recursive definition the merge rules use. A table row is fully matched only when EVERY cell is;
80
+ * a table is fully matched only when its header cells and every body row are — so a table with one changed
81
+ * cell is not merged as a whole, while its matched rows still can be (see `siblingRuns`).
82
+ */
83
+ export declare function pairFullyMatched(pair: TreeMatchPair): boolean;
84
+ /**
85
+ * One run of adjacent siblings at ONE level, and whether the run is "merged" — i.e. every node in it is a
86
+ * fully-matched pair, so the reader can treat them as one unchanged stretch.
87
+ */
88
+ export interface MatchedRun {
89
+ ops: TreeMatchOp[];
90
+ /** The whole run is fully-matched pairs: merge these siblings into one unchanged piece. */
91
+ merged: boolean;
92
+ }
93
+ /**
94
+ * Group a level's alignment into runs: adjacent FULLY-MATCHED siblings merge into one run; a run ends at any
95
+ * change (a modified pair, a node that exists on one side only). The caller can then treat each merged run as
96
+ * one unchanged piece and every other run as a difference to draw.
97
+ */
98
+ export declare function siblingRuns(ops: readonly TreeMatchOp[]): MatchedRun[];
99
+ /** One matched table's header run and body-row runs, kept apart (see `tableRowRuns`). */
100
+ export interface TableRowRuns {
101
+ /** The header is ONE structural piece: merged only when EVERY header cell is fully matched. */
102
+ header: MatchedRun;
103
+ /** Body rows: adjacent fully-matched rows merge; a changed/inserted row ends the run. */
104
+ body: MatchedRun[];
105
+ }
106
+ /**
107
+ * A table's matched runs, HEADER and BODY kept apart.
108
+ *
109
+ * The header is one structural piece: it is either wholly matched (all cells) or not, and it never merges with
110
+ * the body rows into "one matched stretch" — a table whose header changed is a changed table even when every
111
+ * body row matches. Body rows follow the general rule: adjacent fully-matched rows merge; a row matches only
112
+ * when EVERY cell of it matches (a row with one changed cell is a difference row, not a merged one).
113
+ *
114
+ * @param pair - a matched `table` pair.
115
+ * @returns its header run and body runs, in reading order.
116
+ */
117
+ export declare function tableRowRuns(pair: TreeMatchPair): TableRowRuns;
118
+ /**
119
+ * Whether a node renders NOTHING at all, so a change to it can only be reported as invisible content (the
120
+ * eye-with-a-slash marker placed at its document position): front matter, an HTML comment, a link definition.
121
+ */
122
+ export declare function isInvisibleNode(node: MarkdownTreeNode): boolean;
123
+ /**
124
+ * One piece of the merge plan at one tree level: a CONTEXT stretch of fully-matched siblings (merged into one
125
+ * unchanged run) or a DIFFERENCE (a changed pair, or a node that exists on one side only).
126
+ */
127
+ export interface MergePlanPiece {
128
+ kind: 'context' | 'diff';
129
+ /** For a context piece: the fully-matched sibling ops it merged (in order). */
130
+ ops?: TreeMatchOp[];
131
+ /** For a difference: the op it stands for. */
132
+ op?: TreeMatchOp;
133
+ /** The changes INSIDE a changed pair, as pieces of the next level down (same rules recursively). */
134
+ inner?: MergePlanPiece[];
135
+ /** The difference has no rendering (html comment, front matter, link definition): draw the icon marker. */
136
+ invisible?: boolean;
137
+ /** A node that exists on one side only. */
138
+ pure?: 'old' | 'new';
139
+ }
140
+ /**
141
+ * Plan the merge of one tree level: fully-matched adjacent siblings become CONTEXT runs; everything else is a
142
+ * DIFFERENCE piece, recursing into its type-specific internals.
143
+ *
144
+ * The rules this implements (user's decisions):
145
+ * - at any level, adjacent FULLY-MATCHED siblings merge into one unchanged run;
146
+ * - a fully-matched pair disappears into the context; a one-sided node is a pure deletion/addition;
147
+ * - table: the header is one piece (partial header → per-cell differences), body rows merge when every cell
148
+ * matches, and a row with any changed cell is a difference;
149
+ * - list: a container whose own attributes changed is a difference, but its items are still matched and
150
+ * planned item by item;
151
+ * - code: the fence is one difference when its language or any code line changed, with its lines planned
152
+ * inside (matched lines become context);
153
+ * - blockquote: its children are planned with the same rules;
154
+ * - an invisible node (html comment, front matter, link definition) is a difference flagged `invisible`.
155
+ *
156
+ * @param ops - the match operations of one tree level, in reading order.
157
+ * @returns the plan pieces of that level, in reading order.
158
+ */
159
+ export declare function planMergedSiblings(ops: readonly TreeMatchOp[]): MergePlanPiece[];
160
+ /** Whether a plan is entirely context (nothing to draw): true for a wholly unchanged tree level. */
161
+ export declare function planIsAllContext(pieces: readonly MergePlanPiece[]): boolean;
162
+ /** The node a character offset belongs to, deepest first: for tests and later span mapping. */
163
+ export declare function nodeAtOffset(root: readonly MarkdownTreeNode[], offset: number): MarkdownTreeNode | undefined;
@@ -239,6 +239,20 @@ export declare function commentModeEnabled(): boolean;
239
239
  export declare const COMMENT_MODE_CHANGED_EVENT = "diff-approval:comment-mode-changed";
240
240
  /** Persist the comment-mode preference (`'1'` on, `'0'` off — see {@link commentModeEnabled}). */
241
241
  export declare function setCommentModeEnabled(value: boolean): void;
242
+ /** How the Markdown preview draws a diff (see `renderMarkdownPreview`). */
243
+ export type MarkdownDiffStyle = 'structure' | 'blocks';
244
+ /** Window event: the temporary structure-render switch was flipped (see {@link setTempPreviewV2Enabled}). */
245
+ export declare const TEMP_PREVIEW_V2_CHANGED_EVENT = "diff-approval:temp-preview-v2-changed";
246
+ /**
247
+ * Whether the temporary switch for the structure-rendered Markdown diff is on.
248
+ *
249
+ * Absent means OFF — the old preview keeps working exactly as before for a reader who never flips it.
250
+ *
251
+ * @returns whether the new preview is enabled.
252
+ */
253
+ export declare function tempPreviewV2Enabled(): boolean;
254
+ /** Persist the temporary structure-render switch, and tell the open panel to redraw. */
255
+ export declare function setTempPreviewV2Enabled(value: boolean): void;
242
256
  /**
243
257
  * Whether the diff's code uses the font this plugin bundles.
244
258
  *
@@ -152,6 +152,21 @@ export declare class CommentAsker {
152
152
  * @returns what the request did.
153
153
  */
154
154
  ask(sessionId: SessionId, commentId: string, prompt: string, text: string, signal: AbortSignal, inScope: CommentScope): Promise<DiffApprovalCommentAskValue>;
155
+ /**
156
+ * Deliver one comment through the host's WAKE-UP seam, so the shell draws it as a trigger card.
157
+ *
158
+ * The seam is the one the host's own reminders use: resolve the live agent, publish a user message on
159
+ * it, then flush. The message's `source` is what the shell's closed switch turns into a card, and this
160
+ * plugin's client retitles its own source on the rendered DOM (an unknown kind falls back to a generic
161
+ * title). A host without the seam — or any failure asking for it — answers false, and the caller falls
162
+ * back to an ordinary prompt rather than losing the question.
163
+ *
164
+ * @param sessionId - the session whose live agent wakes.
165
+ * @param prompt - the prompt text to deliver verbatim.
166
+ * @param source - the comment's source marker, carried onto the message.
167
+ * @returns whether the message was published on the live agent.
168
+ */
169
+ private deliverAsCard;
155
170
  /**
156
171
  * One scope's questions, grouped by THE TRANSCRIPT each was submitted into.
157
172
  *
@@ -263,6 +263,24 @@ export interface DiffApprovalBlockRange {
263
263
  newStart: number;
264
264
  /** Last new-file line; `newStart - 1` when the block has no new side. */
265
265
  newEnd: number;
266
+ /**
267
+ * Character offsets into the two files, 0-based and end-exclusive, for a change finer than a whole line.
268
+ *
269
+ * Only the rendered-Markdown preview produces these, and only for changes that share a line with another
270
+ * change (a table row whose cell and whose link target both changed): such a change is kept or reverted on
271
+ * its own characters, while every line-based caller leaves these undefined and the host does exactly what
272
+ * it always did.
273
+ */
274
+ oldChars?: CharSpan;
275
+ /** The same change's span in the new file — see `oldChars`. */
276
+ newChars?: CharSpan;
277
+ }
278
+ /** One side's character span inside a file: `[start, end)`, 0-based. */
279
+ export interface CharSpan {
280
+ /** First character offset of the change. */
281
+ start: number;
282
+ /** Offset just past the last changed character; equal to `start` for a pure insertion. */
283
+ end: number;
266
284
  }
267
285
  /** Target of one block-level keep/revert: the entry plus the block range. */
268
286
  export interface DiffApprovalBlockTarget {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-diff-approval",
3
- "version": "0.36.1",
3
+ "version": "0.37.1",
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",
@@ -62,6 +62,7 @@
62
62
  "compat": "node scripts/dsh-compat.mjs",
63
63
  "compat:newer": "node scripts/dsh-compat.mjs --newer",
64
64
  "verify:release": "node scripts/verify-release.mjs",
65
+ "screenshots": "node scripts/screenshots.mjs",
65
66
  "fonts:check": "python -m scripts.fonts.build --check",
66
67
  "fonts:plan": "python -m scripts.fonts.build --plan",
67
68
  "prepare": "tsc -b tsconfig.json && tsdown --env.DSH_BUILD_FACE client",
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file