@astryxdesign/lab 0.7.1-canary.f2beffa → 0.7.1-canary.f789da0

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,65 @@
1
+ /**
2
+ * @file diffTypes.ts
3
+ * @input None (type-only module)
4
+ * @output Exports the structured diff model DiffViewer renders: DiffViewerFile,
5
+ * DiffViewerHunk, DiffViewerLine, DiffViewerLineKind, DiffViewerFileStatus,
6
+ * DiffViewerSide, DiffViewerLineRef
7
+ * @position Lab data model; produced by parseUnifiedDiff (or built by hand) and
8
+ * consumed by DiffViewer
9
+ *
10
+ * SYNC: When modified, update:
11
+ * - /packages/lab/src/DiffViewer/index.ts (type exports)
12
+ */
13
+ /** What one line of a hunk does: adds, removes, or keeps a line. */
14
+ export type DiffViewerLineKind = 'added' | 'removed' | 'context';
15
+ /** How the file changed as a whole. */
16
+ export type DiffViewerFileStatus = 'added' | 'deleted' | 'modified' | 'renamed';
17
+ /**
18
+ * Which version of the file a line number belongs to: `old` is the original
19
+ * (left in split view), `new` the changed version (right in split view).
20
+ */
21
+ export type DiffViewerSide = 'old' | 'new';
22
+ /** One line of a hunk, without its leading `+`/`-`/space glyph. */
23
+ export interface DiffViewerLine {
24
+ kind: DiffViewerLineKind;
25
+ /** The line's text with the diff glyph removed. */
26
+ content: string;
27
+ /** 1-based line number in the old file; `null` for an added line. */
28
+ oldLineNumber: number | null;
29
+ /** 1-based line number in the new file; `null` for a removed line. */
30
+ newLineNumber: number | null;
31
+ }
32
+ /** One `@@` hunk: a run of changed lines with their surrounding context. */
33
+ export interface DiffViewerHunk {
34
+ /** The hunk header as written, e.g. `@@ -12,7 +12,9 @@ function save()`. */
35
+ header: string;
36
+ oldStart: number;
37
+ oldLines: number;
38
+ newStart: number;
39
+ newLines: number;
40
+ lines: DiffViewerLine[];
41
+ }
42
+ /** One file's changes. */
43
+ export interface DiffViewerFile {
44
+ /** Path before the change; `null` when the file was added. */
45
+ oldPath: string | null;
46
+ /** Path after the change; `null` when the file was deleted. */
47
+ newPath: string | null;
48
+ status: DiffViewerFileStatus;
49
+ /** True when the patch reports a binary change with no line hunks. */
50
+ isBinary: boolean;
51
+ hunks: DiffViewerHunk[];
52
+ }
53
+ /**
54
+ * Identifies one line of a rendered diff for annotations and line-number
55
+ * clicks. Every line has exactly one ref, the same in unified and split view,
56
+ * so `side` + `lineNumber` is a stable anchor (e.g. for a review comment): an
57
+ * added line is `new`, a removed line is `old`, and a context line is `new`.
58
+ */
59
+ export interface DiffViewerLineRef {
60
+ side: DiffViewerSide;
61
+ lineNumber: number;
62
+ line: DiffViewerLine;
63
+ hunkIndex: number;
64
+ }
65
+ //# sourceMappingURL=diffTypes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diffTypes.d.ts","sourceRoot":"","sources":["../../src/DiffViewer/diffTypes.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AAEH,oEAAoE;AACpE,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,CAAC;AAEjE,uCAAuC;AACvC,MAAM,MAAM,oBAAoB,GAAG,OAAO,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,CAAC;AAEhF;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,KAAK,CAAC;AAE3C,mEAAmE;AACnE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,kBAAkB,CAAC;IACzB,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAC;IAChB,qEAAqE;IACrE,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,sEAAsE;IACtE,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED,4EAA4E;AAC5E,MAAM,WAAW,cAAc;IAC7B,4EAA4E;IAC5E,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,cAAc,EAAE,CAAC;CACzB;AAED,0BAA0B;AAC1B,MAAM,WAAW,cAAc;IAC7B,8DAA8D;IAC9D,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,+DAA+D;IAC/D,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,MAAM,EAAE,oBAAoB,CAAC;IAC7B,sEAAsE;IACtE,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,cAAc,EAAE,CAAC;CACzB;AAED;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,cAAc,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,cAAc,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @file index.ts
3
+ * @input Imports from parseUnifiedDiff.ts and diffTypes.ts
4
+ * @output Exports the structured diff model and the unified-diff parser
5
+ * @position Entry point for the DiffViewer folder; re-exported by
6
+ * /packages/lab/src/index.ts
7
+ */
8
+ export { parseUnifiedDiff } from './parseUnifiedDiff';
9
+ export type { DiffViewerFile, DiffViewerFileStatus, DiffViewerHunk, DiffViewerLine, DiffViewerLineKind, DiffViewerLineRef, DiffViewerSide, } from './diffTypes';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/DiffViewer/index.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AAEH,OAAO,EAAC,gBAAgB,EAAC,MAAM,oBAAoB,CAAC;AACpD,YAAY,EACV,cAAc,EACd,oBAAoB,EACpB,cAAc,EACd,cAAc,EACd,kBAAkB,EAClB,iBAAiB,EACjB,cAAc,GACf,MAAM,aAAa,CAAC"}
@@ -0,0 +1,11 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file index.ts
5
+ * @input Imports from parseUnifiedDiff.ts and diffTypes.ts
6
+ * @output Exports the structured diff model and the unified-diff parser
7
+ * @position Entry point for the DiffViewer folder; re-exported by
8
+ * /packages/lab/src/index.ts
9
+ */
10
+
11
+ export { parseUnifiedDiff } from "./parseUnifiedDiff.js";
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @file parseUnifiedDiff.ts
3
+ * @input A unified diff / patch string (`git diff`, `diff -u`, or a bare hunk)
4
+ * @output Exports parseUnifiedDiff(patch) → DiffViewerFile[]
5
+ * @position Lab utility; turns patch text into the structured model DiffViewer
6
+ * renders
7
+ *
8
+ * Tolerant by design: unknown header lines are skipped, a hunk whose counts
9
+ * run short ends where its lines end, and input with no recognizable file or
10
+ * hunk returns an empty array. It never throws.
11
+ *
12
+ * SYNC: When modified, update:
13
+ * - /packages/lab/src/DiffViewer/parseUnifiedDiff.test.ts
14
+ */
15
+ import type { DiffViewerFile } from './diffTypes';
16
+ /**
17
+ * Parse a unified diff into one {@link DiffViewerFile} per file.
18
+ *
19
+ * Accepts `git diff` output (with `diff --git`, mode, rename, copy, and
20
+ * binary lines), classic `diff -u` output (`---`/`+++` pairs), several files in one
21
+ * patch, and a bare `@@` hunk with no file header (returned as one file with
22
+ * null paths). Input it cannot read yields `[]`; it never throws.
23
+ *
24
+ * @example
25
+ * ```
26
+ * const files = parseUnifiedDiff(patch);
27
+ * files.map((file) => <DiffViewer key={file.newPath ?? file.oldPath} file={file} />);
28
+ * ```
29
+ */
30
+ export declare function parseUnifiedDiff(patch: string): DiffViewerFile[];
31
+ //# sourceMappingURL=parseUnifiedDiff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseUnifiedDiff.d.ts","sourceRoot":"","sources":["../../src/DiffViewer/parseUnifiedDiff.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EACV,cAAc,EAGf,MAAM,aAAa,CAAC;AAkOrB;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,EAAE,CA8KhE"}
@@ -0,0 +1,379 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file parseUnifiedDiff.ts
5
+ * @input A unified diff / patch string (`git diff`, `diff -u`, or a bare hunk)
6
+ * @output Exports parseUnifiedDiff(patch) → DiffViewerFile[]
7
+ * @position Lab utility; turns patch text into the structured model DiffViewer
8
+ * renders
9
+ *
10
+ * Tolerant by design: unknown header lines are skipped, a hunk whose counts
11
+ * run short ends where its lines end, and input with no recognizable file or
12
+ * hunk returns an empty array. It never throws.
13
+ *
14
+ * SYNC: When modified, update:
15
+ * - /packages/lab/src/DiffViewer/parseUnifiedDiff.test.ts
16
+ */
17
+
18
+ const HUNK_HEADER = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
19
+ const GIT_HEADER_PREFIX = 'diff --git ';
20
+ const BINARY_PREFIX = 'Binary files ';
21
+ const BINARY_SUFFIX = ' differ';
22
+ const BINARY_SEPARATOR = ' and ';
23
+
24
+ /**
25
+ * The two paths of a `Binary files X and Y differ` line, or null when the
26
+ * line is not one. Plain string matching, not a regex, so any line (however
27
+ * long, whatever characters it holds) is read in one pass. Like the greedy
28
+ * pattern it replaces, the separator is the last ` and `.
29
+ */
30
+ function parseBinaryLine(line) {
31
+ if (!line.startsWith(BINARY_PREFIX) || !line.endsWith(BINARY_SUFFIX)) {
32
+ return null;
33
+ }
34
+ const middle = line.slice(BINARY_PREFIX.length, line.length - BINARY_SUFFIX.length);
35
+ const split = middle.lastIndexOf(BINARY_SEPARATOR);
36
+ if (split <= 0 || split + BINARY_SEPARATOR.length >= middle.length) {
37
+ return null;
38
+ }
39
+ return {
40
+ oldPath: middle.slice(0, split),
41
+ newPath: middle.slice(split + BINARY_SEPARATOR.length)
42
+ };
43
+ }
44
+ const C_ESCAPES = {
45
+ a: 0x07,
46
+ b: 0x08,
47
+ t: 0x09,
48
+ n: 0x0a,
49
+ v: 0x0b,
50
+ f: 0x0c,
51
+ r: 0x0d,
52
+ '"': 0x22,
53
+ '\\': 0x5c
54
+ };
55
+ const utf8 = new TextDecoder();
56
+ const utf8Encoder = new TextEncoder();
57
+
58
+ /**
59
+ * Read a git C-style quoted path starting at `start` (the opening quote).
60
+ * Git quotes a path that holds non-ASCII bytes or special characters and
61
+ * writes those bytes as octal escapes (`"caf\303\251.md"`), so the escapes
62
+ * are collected as bytes and decoded as UTF-8. Returns the path and the index
63
+ * just past the closing quote, or null when the quote never closes.
64
+ */
65
+ function readQuoted(text, start) {
66
+ const bytes = [];
67
+ let i = start + 1;
68
+ while (i < text.length) {
69
+ const ch = text[i];
70
+ if (ch === '"') {
71
+ return {
72
+ value: utf8.decode(new Uint8Array(bytes)),
73
+ end: i + 1
74
+ };
75
+ }
76
+ if (ch === '\\' && i + 1 < text.length) {
77
+ const next = text[i + 1];
78
+ const octal = /^[0-7]{3}/.exec(text.slice(i + 1, i + 4));
79
+ if (octal != null) {
80
+ bytes.push(parseInt(octal[0], 8));
81
+ i += 4;
82
+ continue;
83
+ }
84
+ if (next in C_ESCAPES) {
85
+ bytes.push(C_ESCAPES[next]);
86
+ i += 2;
87
+ continue;
88
+ }
89
+ }
90
+ // Any other character stands for itself (as UTF-8 bytes).
91
+ const codePoint = text.codePointAt(i) ?? 0;
92
+ const char = String.fromCodePoint(codePoint);
93
+ bytes.push(...utf8Encoder.encode(char));
94
+ i += char.length;
95
+ }
96
+ return null;
97
+ }
98
+
99
+ /** The path written in `raw`, unquoted and decoded when git quoted it. */
100
+ function unquotePath(raw) {
101
+ if (raw.startsWith('"')) {
102
+ const quoted = readQuoted(raw, 0);
103
+ if (quoted != null) {
104
+ return quoted.value;
105
+ }
106
+ }
107
+ return raw;
108
+ }
109
+ function stripPrefix(path, prefix) {
110
+ return path.startsWith(prefix) ? path.slice(prefix.length) : path;
111
+ }
112
+
113
+ /**
114
+ * The two paths of a `diff --git` line. Git quotes a path that holds special
115
+ * or non-ASCII characters; quoted paths are read whole and decoded. An
116
+ * unquoted path may hold spaces, so a space can look like the separator: when
117
+ * both sides name the same path (every change except a rename) the separator
118
+ * is the middle space. A rename writes `rename from` / `rename to` lines,
119
+ * which replace these paths, so the first-space split is only a fallback.
120
+ */
121
+ function parseGitHeader(line) {
122
+ if (!line.startsWith(GIT_HEADER_PREFIX)) {
123
+ return null;
124
+ }
125
+ const rest = line.slice(GIT_HEADER_PREFIX.length);
126
+ const paths = (left, right) => ({
127
+ oldPath: stripPrefix(left, 'a/'),
128
+ newPath: stripPrefix(right, 'b/')
129
+ });
130
+ if (rest.startsWith('"')) {
131
+ const left = readQuoted(rest, 0);
132
+ if (left != null && rest[left.end] === ' ') {
133
+ return paths(left.value, unquotePath(rest.slice(left.end + 1)));
134
+ }
135
+ }
136
+ if (rest.endsWith('"')) {
137
+ // Only the new side is quoted: it starts at the last ` "` that reads to
138
+ // the end of the line as one quoted path.
139
+ for (let split = rest.lastIndexOf(' "'); split > 0; split = rest.lastIndexOf(' "', split - 1)) {
140
+ const right = readQuoted(rest, split + 1);
141
+ if (right != null && right.end === rest.length) {
142
+ return paths(rest.slice(0, split), right.value);
143
+ }
144
+ }
145
+ }
146
+ if (rest.length % 2 === 1) {
147
+ const middle = (rest.length - 1) / 2;
148
+ const left = rest.slice(0, middle);
149
+ const right = rest.slice(middle + 1);
150
+ if (rest[middle] === ' ' && stripPrefix(left, 'a/') === stripPrefix(right, 'b/')) {
151
+ return paths(left, right);
152
+ }
153
+ }
154
+ const space = rest.indexOf(' ');
155
+ return space <= 0 ? null : paths(rest.slice(0, space), rest.slice(space + 1));
156
+ }
157
+ function emptyFile() {
158
+ return {
159
+ oldPath: null,
160
+ newPath: null,
161
+ status: null,
162
+ isBinary: false,
163
+ hunks: []
164
+ };
165
+ }
166
+
167
+ /**
168
+ * `a/src/x.ts` → `src/x.ts`; `/dev/null` → null; drops a trailing
169
+ * tab+timestamp and decodes a quoted path.
170
+ */
171
+ function cleanPath(raw, prefix) {
172
+ let path = unquotePath(raw.split('\t')[0].trim());
173
+ if (path === '/dev/null') {
174
+ return null;
175
+ }
176
+ if (path.startsWith(prefix)) {
177
+ path = path.slice(prefix.length);
178
+ }
179
+ return path;
180
+ }
181
+ function finalize(file) {
182
+ let status = file.status;
183
+ if (status == null) {
184
+ if (file.oldPath == null && file.newPath != null) {
185
+ status = 'added';
186
+ } else if (file.newPath == null && file.oldPath != null) {
187
+ status = 'deleted';
188
+ } else {
189
+ // Only git's rename lines say a file moved. Two different paths
190
+ // without them (`diff -u a.orig a`, `diff -ruN old new`, a copy) are an
191
+ // edit between two files, so both paths are kept.
192
+ status = 'modified';
193
+ }
194
+ }
195
+ return {
196
+ oldPath: status === 'added' ? null : file.oldPath,
197
+ newPath: status === 'deleted' ? null : file.newPath,
198
+ status,
199
+ isBinary: file.isBinary,
200
+ hunks: file.hunks
201
+ };
202
+ }
203
+ function hasContent(file) {
204
+ return file.hunks.length > 0 || file.isBinary || file.oldPath != null || file.newPath != null || file.status != null;
205
+ }
206
+
207
+ /**
208
+ * Parse a unified diff into one {@link DiffViewerFile} per file.
209
+ *
210
+ * Accepts `git diff` output (with `diff --git`, mode, rename, copy, and
211
+ * binary lines), classic `diff -u` output (`---`/`+++` pairs), several files in one
212
+ * patch, and a bare `@@` hunk with no file header (returned as one file with
213
+ * null paths). Input it cannot read yields `[]`; it never throws.
214
+ *
215
+ * @example
216
+ * ```
217
+ * const files = parseUnifiedDiff(patch);
218
+ * files.map((file) => <DiffViewer key={file.newPath ?? file.oldPath} file={file} />);
219
+ * ```
220
+ */
221
+ export function parseUnifiedDiff(patch) {
222
+ const lines = patch.split('\n').map(line => line.replace(/\r$/, ''));
223
+ const files = [];
224
+ let file = null;
225
+ let hunk = null;
226
+ let oldRemaining = 0;
227
+ let newRemaining = 0;
228
+ let oldLine = 0;
229
+ let newLine = 0;
230
+ const flush = () => {
231
+ if (file != null && hasContent(file)) {
232
+ files.push(finalize(file));
233
+ }
234
+ file = null;
235
+ hunk = null;
236
+ };
237
+ for (let i = 0; i < lines.length; i++) {
238
+ const line = lines[i];
239
+
240
+ // Inside a hunk: consume body lines until both counts are spent.
241
+ if (hunk != null && (oldRemaining > 0 || newRemaining > 0)) {
242
+ const glyph = line[0];
243
+ if (glyph === '\\') {
244
+ // "" — annotates the previous line.
245
+ continue;
246
+ }
247
+ if (glyph === '+') {
248
+ hunk.lines.push({
249
+ kind: 'added',
250
+ content: line.slice(1),
251
+ oldLineNumber: null,
252
+ newLineNumber: newLine++
253
+ });
254
+ newRemaining--;
255
+ continue;
256
+ }
257
+ if (glyph === '-') {
258
+ hunk.lines.push({
259
+ kind: 'removed',
260
+ content: line.slice(1),
261
+ oldLineNumber: oldLine++,
262
+ newLineNumber: null
263
+ });
264
+ oldRemaining--;
265
+ continue;
266
+ }
267
+ if (glyph === ' ' || line === '') {
268
+ // Some tools strip the trailing space of an empty context line.
269
+ if (line === '' && i === lines.length - 1) {
270
+ continue;
271
+ }
272
+ hunk.lines.push({
273
+ kind: 'context',
274
+ content: line.slice(1),
275
+ oldLineNumber: oldLine++,
276
+ newLineNumber: newLine++
277
+ });
278
+ oldRemaining--;
279
+ newRemaining--;
280
+ continue;
281
+ }
282
+ // Anything else ends a short hunk; fall through and read it as a header.
283
+ hunk = null;
284
+ }
285
+ if (hunk != null && line.startsWith('\\')) {
286
+ continue;
287
+ }
288
+ const gitPaths = parseGitHeader(line);
289
+ if (gitPaths != null) {
290
+ flush();
291
+ file = emptyFile();
292
+ file.oldPath = gitPaths.oldPath;
293
+ file.newPath = gitPaths.newPath;
294
+ continue;
295
+ }
296
+ if (line.startsWith('--- ') && (lines[i + 1]?.startsWith('+++ ') ?? false)) {
297
+ // A `---`/`+++` pair opens a new file unless it belongs to a
298
+ // `diff --git` header whose hunks have not started yet. A binary entry
299
+ // never takes a pair: in `diff -r` output it was a file of its own.
300
+ if (file == null || file.hunks.length > 0 || file.isBinary) {
301
+ flush();
302
+ file = emptyFile();
303
+ }
304
+ const current = file;
305
+ current.oldPath = cleanPath(line.slice(4), 'a/');
306
+ current.newPath = cleanPath(lines[i + 1].slice(4), 'b/');
307
+ if (current.oldPath == null && current.status == null) {
308
+ current.status = 'added';
309
+ } else if (current.newPath == null && current.status == null) {
310
+ current.status = 'deleted';
311
+ }
312
+ i++;
313
+ continue;
314
+ }
315
+ const header = HUNK_HEADER.exec(line);
316
+ if (header) {
317
+ file ??= emptyFile();
318
+ const oldStart = Number(header[1]);
319
+ const oldLines = header[2] == null ? 1 : Number(header[2]);
320
+ const newStart = Number(header[3]);
321
+ const newLines = header[4] == null ? 1 : Number(header[4]);
322
+ const next = {
323
+ header: line,
324
+ oldStart,
325
+ oldLines,
326
+ newStart,
327
+ newLines,
328
+ lines: []
329
+ };
330
+ file.hunks.push(next);
331
+ hunk = next;
332
+ oldRemaining = oldLines;
333
+ newRemaining = newLines;
334
+ // A zero-length side's start names the line *before* the hunk.
335
+ oldLine = oldLines === 0 ? oldStart + 1 : oldStart;
336
+ newLine = newLines === 0 ? newStart + 1 : newStart;
337
+ continue;
338
+ }
339
+
340
+ // GNU `diff -r` reports a binary file with this one line and no header,
341
+ // so unless it belongs to a `diff --git` header still being read (no
342
+ // hunks yet, not already binary), it is a file of its own.
343
+ const binary = parseBinaryLine(line);
344
+ if (binary != null && (file == null || file.hunks.length > 0 || file.isBinary)) {
345
+ flush();
346
+ const own = emptyFile();
347
+ own.oldPath = cleanPath(binary.oldPath, 'a/');
348
+ own.newPath = cleanPath(binary.newPath, 'b/');
349
+ own.isBinary = true;
350
+ file = own;
351
+ continue;
352
+ }
353
+ if (file == null) {
354
+ continue;
355
+ }
356
+ const current = file;
357
+ if (line.startsWith('new file mode')) {
358
+ current.status = 'added';
359
+ } else if (line.startsWith('deleted file mode')) {
360
+ current.status = 'deleted';
361
+ } else if (line.startsWith('rename from ')) {
362
+ current.oldPath = unquotePath(line.slice('rename from '.length));
363
+ current.status = 'renamed';
364
+ } else if (line.startsWith('rename to ')) {
365
+ current.newPath = unquotePath(line.slice('rename to '.length));
366
+ current.status = 'renamed';
367
+ } else if (line.startsWith('copy from ')) {
368
+ // A copy keeps its source, so it reads as an edit from the source path
369
+ // to the new one; these lines also fix paths the header split wrongly.
370
+ current.oldPath = unquotePath(line.slice('copy from '.length));
371
+ } else if (line.startsWith('copy to ')) {
372
+ current.newPath = unquotePath(line.slice('copy to '.length));
373
+ } else if (line.startsWith('Binary files ') || line === 'GIT binary patch') {
374
+ current.isBinary = true;
375
+ }
376
+ }
377
+ flush();
378
+ return files;
379
+ }
package/dist/index.d.ts CHANGED
@@ -28,5 +28,6 @@ export { SankeyChart, type SankeyChartProps, SankeyLink, type SankeyLinkProps, S
28
28
  export * from './CircularProgress';
29
29
  export * from './ListInput';
30
30
  export { LogStream, type LogStreamProps, type LogEntry, type LogStreamLevel, } from './LogStream';
31
+ export { parseUnifiedDiff, type DiffViewerFile, type DiffViewerFileStatus, type DiffViewerHunk, type DiffViewerLine, type DiffViewerLineKind, type DiffViewerLineRef, type DiffViewerSide, } from './DiffViewer';
31
32
  export { MobileTokenizer, type MobileTokenizerProps, type MobileTokenizerChange, } from './MobileTokenizer';
32
33
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -66,12 +66,14 @@ export * from "./ListInput/index.js";
66
66
  // LogStream — experimental streaming log viewer
67
67
  export { LogStream } from "./LogStream/index.js";
68
68
 
69
+ // DiffViewer — the structured diff model and the unified-diff parser
70
+ export { parseUnifiedDiff } from "./DiffViewer/index.js";
71
+
69
72
  // MobileTokenizer — Lab prototype (stack layer 1) of the touch Tokenizer
70
73
  // flow: summary trigger + stacked manage/add BottomSheets. Graduates to
71
74
  // Core Tokenizer presentation="bottom-sheet" (stack layer 2).
72
75
  export { MobileTokenizer } from "./MobileTokenizer/index.js";
73
76
 
74
- // RichTextEditor (RFC facebook/astryx#3899) has graduated out of @astryxdesign/lab
75
- // into its own canary-only package, @astryxdesign/richtext, so it can be canaried
76
- // independently by an adopting application. Import it from there:
77
+ // RichTextEditor (RFC facebook/astryx#3899) moved out of @astryxdesign/lab into
78
+ // its own package, @astryxdesign/richtext. Import it from there:
77
79
  // import {RichTextEditor, RichTextView} from '@astryxdesign/richtext';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astryxdesign/lab",
3
- "version": "0.7.1-canary.f2beffa",
3
+ "version": "0.7.1-canary.f789da0",
4
4
  "description": "Experimental Astryx components — published to npm only under the @canary dist-tag for early testing; never released as a stable (latest) version.",
5
5
  "author": "Meta Open Source",
6
6
  "license": "MIT",
@@ -47,7 +47,7 @@
47
47
  },
48
48
  "peerDependencies": {
49
49
  "@stylexjs/stylex": ">=0.10.0",
50
- "@astryxdesign/core": "0.7.1-canary.f2beffa",
50
+ "@astryxdesign/core": "0.7.1-canary.f789da0",
51
51
  "react": ">=19.0.0",
52
52
  "react-dom": ">=19.0.0"
53
53
  },
@@ -57,8 +57,8 @@
57
57
  "d3-shape": "^3.2.0"
58
58
  },
59
59
  "devDependencies": {
60
- "@astryxdesign/a11y-spec": "0.7.1-canary.f2beffa",
61
- "@astryxdesign/cli": "0.7.1-canary.f2beffa",
60
+ "@astryxdesign/a11y-spec": "0.7.1-canary.f789da0",
61
+ "@astryxdesign/cli": "0.7.1-canary.f789da0",
62
62
  "@babel/cli": "^7.29.7",
63
63
  "@babel/core": "^7.29.7",
64
64
  "@babel/preset-react": "^7.29.7",
@@ -0,0 +1,73 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file diffTypes.ts
5
+ * @input None (type-only module)
6
+ * @output Exports the structured diff model DiffViewer renders: DiffViewerFile,
7
+ * DiffViewerHunk, DiffViewerLine, DiffViewerLineKind, DiffViewerFileStatus,
8
+ * DiffViewerSide, DiffViewerLineRef
9
+ * @position Lab data model; produced by parseUnifiedDiff (or built by hand) and
10
+ * consumed by DiffViewer
11
+ *
12
+ * SYNC: When modified, update:
13
+ * - /packages/lab/src/DiffViewer/index.ts (type exports)
14
+ */
15
+
16
+ /** What one line of a hunk does: adds, removes, or keeps a line. */
17
+ export type DiffViewerLineKind = 'added' | 'removed' | 'context';
18
+
19
+ /** How the file changed as a whole. */
20
+ export type DiffViewerFileStatus = 'added' | 'deleted' | 'modified' | 'renamed';
21
+
22
+ /**
23
+ * Which version of the file a line number belongs to: `old` is the original
24
+ * (left in split view), `new` the changed version (right in split view).
25
+ */
26
+ export type DiffViewerSide = 'old' | 'new';
27
+
28
+ /** One line of a hunk, without its leading `+`/`-`/space glyph. */
29
+ export interface DiffViewerLine {
30
+ kind: DiffViewerLineKind;
31
+ /** The line's text with the diff glyph removed. */
32
+ content: string;
33
+ /** 1-based line number in the old file; `null` for an added line. */
34
+ oldLineNumber: number | null;
35
+ /** 1-based line number in the new file; `null` for a removed line. */
36
+ newLineNumber: number | null;
37
+ }
38
+
39
+ /** One `@@` hunk: a run of changed lines with their surrounding context. */
40
+ export interface DiffViewerHunk {
41
+ /** The hunk header as written, e.g. `@@ -12,7 +12,9 @@ function save()`. */
42
+ header: string;
43
+ oldStart: number;
44
+ oldLines: number;
45
+ newStart: number;
46
+ newLines: number;
47
+ lines: DiffViewerLine[];
48
+ }
49
+
50
+ /** One file's changes. */
51
+ export interface DiffViewerFile {
52
+ /** Path before the change; `null` when the file was added. */
53
+ oldPath: string | null;
54
+ /** Path after the change; `null` when the file was deleted. */
55
+ newPath: string | null;
56
+ status: DiffViewerFileStatus;
57
+ /** True when the patch reports a binary change with no line hunks. */
58
+ isBinary: boolean;
59
+ hunks: DiffViewerHunk[];
60
+ }
61
+
62
+ /**
63
+ * Identifies one line of a rendered diff for annotations and line-number
64
+ * clicks. Every line has exactly one ref, the same in unified and split view,
65
+ * so `side` + `lineNumber` is a stable anchor (e.g. for a review comment): an
66
+ * added line is `new`, a removed line is `old`, and a context line is `new`.
67
+ */
68
+ export interface DiffViewerLineRef {
69
+ side: DiffViewerSide;
70
+ lineNumber: number;
71
+ line: DiffViewerLine;
72
+ hunkIndex: number;
73
+ }