@cueloop/client 0.1.0-alpha.84 → 0.1.0-alpha.86
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/package.json +5 -5
- package/src/App.diff.test.tsx +63 -0
- package/src/App.tsx +68 -4
- package/src/app-screens.tsx +0 -1
- package/src/app-view-model.ts +1 -0
- package/src/components/AnnotationCards.tsx +1 -1
- package/src/components/DiffContentView.scroll.test.tsx +29 -1
- package/src/components/DiffContentView.tsx +33 -4
- package/src/components/ThreadView.selection.test.tsx +23 -0
- package/src/components/ThreadView.tsx +2 -2
- package/src/components/WelcomePlayground.stories.tsx +1 -1
- package/src/components/WelcomePlayground.test.tsx +10 -8
- package/src/components/WelcomePlayground.tsx +2 -5
- package/src/components/primitives/Button.tsx +5 -2
- package/src/config.test.ts +62 -1
- package/src/config.ts +66 -0
- package/src/index.ts +16 -1
- package/src/intent-dispatch.test.ts +1 -0
- package/src/launch-layout.test.ts +16 -1
- package/src/launch-layout.ts +6 -1
- package/src/prototype-browser.test.ts +19 -2
- package/src/prototype-browser.ts +7 -7
- package/src/theme-presets.ts +5 -0
- package/src/theme.ts +4 -0
- package/src/thread-controller.ts +13 -0
- package/src/use-settings-dialog.tsx +44 -0
- package/src/view-diff.test.ts +145 -3
- package/src/view-diff.ts +201 -11
- package/src/view-plan.ts +2 -0
package/src/view-diff.ts
CHANGED
|
@@ -31,6 +31,10 @@ export interface DiffRow {
|
|
|
31
31
|
newLine?: number;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
function isCodeRow(row: DiffRow | undefined): row is DiffRow {
|
|
35
|
+
return row !== undefined && row.kind !== "file" && row.kind !== "hunk";
|
|
36
|
+
}
|
|
37
|
+
|
|
34
38
|
export function diffRows(patchText: string): DiffRow[] {
|
|
35
39
|
const rows: DiffRow[] = [];
|
|
36
40
|
const patches = parsePatchFiles(patchText);
|
|
@@ -168,6 +172,73 @@ export function marksByRows(
|
|
|
168
172
|
return marksByIndex;
|
|
169
173
|
}
|
|
170
174
|
|
|
175
|
+
function addResolvedMark(
|
|
176
|
+
marksByIndex: Map<number, Mark[]>,
|
|
177
|
+
annotation: Annotation,
|
|
178
|
+
blocks: Block[],
|
|
179
|
+
startBlockIndex: number,
|
|
180
|
+
endBlockIndex: number,
|
|
181
|
+
focusedId?: string,
|
|
182
|
+
): void {
|
|
183
|
+
const startAnchor = annotation.reviewComment?.startAnchor ?? annotation.anchor;
|
|
184
|
+
const span: TextSpan = {
|
|
185
|
+
start: {
|
|
186
|
+
blockIndex: startBlockIndex,
|
|
187
|
+
char: Math.min(startAnchor.start ?? 0, blocks[startBlockIndex]!.text.length),
|
|
188
|
+
},
|
|
189
|
+
end: {
|
|
190
|
+
blockIndex: endBlockIndex,
|
|
191
|
+
char: Math.min(
|
|
192
|
+
annotation.anchor.end ?? annotation.anchor.quote.length,
|
|
193
|
+
blocks[endBlockIndex]!.text.length,
|
|
194
|
+
),
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
for (let rowIndex = startBlockIndex; rowIndex <= endBlockIndex; rowIndex++) {
|
|
199
|
+
const range = spanRangeInBlock(span, rowIndex, blocks[rowIndex]!.text.length);
|
|
200
|
+
|
|
201
|
+
const marksEmptyLine =
|
|
202
|
+
range === null &&
|
|
203
|
+
startBlockIndex === endBlockIndex &&
|
|
204
|
+
rowIndex === startBlockIndex &&
|
|
205
|
+
blocks[rowIndex]!.text.length === 0;
|
|
206
|
+
|
|
207
|
+
if (!range && !marksEmptyLine) continue;
|
|
208
|
+
const marks = marksByIndex.get(rowIndex) ?? [];
|
|
209
|
+
|
|
210
|
+
marks.push({
|
|
211
|
+
start: range?.start ?? 0,
|
|
212
|
+
end: range?.end ?? 0,
|
|
213
|
+
role: annotation.id === focusedId ? "mark-focus" : "mark-comment",
|
|
214
|
+
annotationId: annotation.id,
|
|
215
|
+
span,
|
|
216
|
+
});
|
|
217
|
+
marksByIndex.set(rowIndex, marks);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function addReviewFindingMark(
|
|
222
|
+
marksByIndex: Map<number, Mark[]>,
|
|
223
|
+
annotation: Annotation,
|
|
224
|
+
rows: DiffRow[],
|
|
225
|
+
blocks: Block[],
|
|
226
|
+
focusedId?: string,
|
|
227
|
+
): boolean {
|
|
228
|
+
const finding = annotation.reviewComment;
|
|
229
|
+
|
|
230
|
+
if (!finding || isAddressed(annotation)) return false;
|
|
231
|
+
const end = resolveReviewAnchorRow(rows, annotation.anchor, finding.path, finding.side);
|
|
232
|
+
const start = finding.startAnchor
|
|
233
|
+
? resolveReviewAnchorRow(rows, finding.startAnchor, finding.path, finding.side)
|
|
234
|
+
: end;
|
|
235
|
+
|
|
236
|
+
if (!start || !end || start.rowIndex > end.rowIndex) return false;
|
|
237
|
+
addResolvedMark(marksByIndex, annotation, blocks, start.rowIndex, end.rowIndex, focusedId);
|
|
238
|
+
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
|
|
171
242
|
/**
|
|
172
243
|
* A whole file rendered as context rows, so the diff sheet can show and annotate a plain file the
|
|
173
244
|
* same way it does a diff: every line is unchanged, numbered on both sides (the file view collapses
|
|
@@ -225,7 +296,76 @@ export function fileTargetMarks(
|
|
|
225
296
|
|
|
226
297
|
if (!range) continue;
|
|
227
298
|
const base = range.start;
|
|
228
|
-
const
|
|
299
|
+
const fileRows = rows.slice(range.start, range.end);
|
|
300
|
+
const reviewRootIds = new Set(
|
|
301
|
+
fileAnnotations.flatMap((annotation) =>
|
|
302
|
+
annotation.reviewComment === undefined ? [] : [annotation.id],
|
|
303
|
+
),
|
|
304
|
+
);
|
|
305
|
+
const ordinaryAnnotations = fileAnnotations.filter(
|
|
306
|
+
(annotation) =>
|
|
307
|
+
annotation.reviewComment === undefined &&
|
|
308
|
+
(annotation.replyTo === undefined || !reviewRootIds.has(annotation.replyTo)),
|
|
309
|
+
);
|
|
310
|
+
const fileMarks = marksByRows(ordinaryAnnotations, fileRows, focusedId);
|
|
311
|
+
const blocks = diffRowBlocks(fileRows);
|
|
312
|
+
const resolvedIds = new Set(
|
|
313
|
+
[...fileMarks.values()]
|
|
314
|
+
.flat()
|
|
315
|
+
.flatMap((mark) => (mark.annotationId === undefined ? [] : [mark.annotationId])),
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
for (const annotation of fileAnnotations) {
|
|
319
|
+
const finding = annotation.reviewComment;
|
|
320
|
+
|
|
321
|
+
if (!finding || isAddressed(annotation)) continue;
|
|
322
|
+
if (addReviewFindingMark(fileMarks, annotation, fileRows, blocks, focusedId)) {
|
|
323
|
+
resolvedIds.add(annotation.id);
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
const relativeRow = fileRows.findIndex(
|
|
327
|
+
(row) =>
|
|
328
|
+
(finding.side === "RIGHT" ? row.newLine : row.oldLine) === finding.line &&
|
|
329
|
+
row.kind !== "file" &&
|
|
330
|
+
row.kind !== "hunk",
|
|
331
|
+
);
|
|
332
|
+
const fallbackRow = relativeRow === -1 ? 0 : relativeRow;
|
|
333
|
+
const mark: Mark = {
|
|
334
|
+
start: 0,
|
|
335
|
+
end: 0,
|
|
336
|
+
role: annotation.id === focusedId ? "mark-focus" : "mark-comment",
|
|
337
|
+
annotationId: annotation.id,
|
|
338
|
+
outdated: true,
|
|
339
|
+
span: {
|
|
340
|
+
start: { blockIndex: fallbackRow, char: 0 },
|
|
341
|
+
end: { blockIndex: fallbackRow, char: 0 },
|
|
342
|
+
},
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
fileMarks.set(fallbackRow, [...(fileMarks.get(fallbackRow) ?? []), mark]);
|
|
346
|
+
resolvedIds.add(annotation.id);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
for (const annotation of fileAnnotations) {
|
|
350
|
+
if (!annotation.replyTo || resolvedIds.has(annotation.id) || isAddressed(annotation))
|
|
351
|
+
continue;
|
|
352
|
+
const parent = [...fileMarks.entries()].find(([, marks]) =>
|
|
353
|
+
marks.some((mark) => mark.annotationId === annotation.replyTo),
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
if (!parent) continue;
|
|
357
|
+
const [fallbackRow, parentMarks] = parent;
|
|
358
|
+
const parentMark = parentMarks.find((mark) => mark.annotationId === annotation.replyTo)!;
|
|
359
|
+
const replyMark: Mark = {
|
|
360
|
+
...parentMark,
|
|
361
|
+
role: annotation.id === focusedId ? "mark-focus" : "mark-comment",
|
|
362
|
+
annotationId: annotation.id,
|
|
363
|
+
outdated: parentMark.outdated,
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
fileMarks.set(fallbackRow, [...parentMarks, replyMark]);
|
|
367
|
+
resolvedIds.add(annotation.id);
|
|
368
|
+
}
|
|
229
369
|
|
|
230
370
|
for (const [relativeRow, marks] of fileMarks) {
|
|
231
371
|
result.set(
|
|
@@ -279,8 +419,17 @@ export function changesMarks(
|
|
|
279
419
|
const artifactNotes = session.annotations.filter(
|
|
280
420
|
(annotation) => annotationTarget(annotation).kind === "artifact",
|
|
281
421
|
);
|
|
422
|
+
const marks = marksByRows(artifactNotes, rows, focusedId);
|
|
423
|
+
|
|
424
|
+
// Agent PR findings carry a file target so repeated text cannot bind across files.
|
|
425
|
+
// Plain diff comments still use the artifact target for backward compatibility.
|
|
426
|
+
if (session.artifact.meta.pr !== undefined) {
|
|
427
|
+
for (const [row, fileMarks] of fileTargetMarks(session.annotations, rows, focusedId)) {
|
|
428
|
+
marks.set(row, [...(marks.get(row) ?? []), ...fileMarks]);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
282
431
|
|
|
283
|
-
return
|
|
432
|
+
return marks;
|
|
284
433
|
}
|
|
285
434
|
|
|
286
435
|
return fileTargetMarks(session.annotations, rows, focusedId);
|
|
@@ -308,22 +457,63 @@ export function commentCountsByFile(session: Thread, rows: DiffRow[]): Map<strin
|
|
|
308
457
|
/** Quote-primary anchor for a diff row: neighbors as context selectors. */
|
|
309
458
|
export function diffRowAnchor(rows: DiffRow[], rowIndex: number) {
|
|
310
459
|
const row = rows[rowIndex]!;
|
|
460
|
+
const context = diffRowAnchorContext(rows, rowIndex);
|
|
461
|
+
|
|
462
|
+
return {
|
|
463
|
+
quote: diffRowText(row),
|
|
464
|
+
...context,
|
|
465
|
+
blockIndex: rowIndex,
|
|
466
|
+
endBlockIndex: rowIndex,
|
|
467
|
+
start: 0,
|
|
468
|
+
end: diffRowText(row).length,
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function diffRowAnchorContext(
|
|
473
|
+
rows: DiffRow[],
|
|
474
|
+
rowIndex: number,
|
|
475
|
+
): Pick<Annotation["anchor"], "prefix" | "suffix"> {
|
|
311
476
|
const prev = rows[rowIndex - 1];
|
|
312
477
|
const next = rows[rowIndex + 1];
|
|
313
478
|
|
|
314
479
|
return {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
prev && (prev.kind === "ctx" || prev.kind === "add" || prev.kind === "del")
|
|
318
|
-
? prev.text.slice(-24)
|
|
319
|
-
: "",
|
|
320
|
-
suffix:
|
|
321
|
-
next && (next.kind === "ctx" || next.kind === "add" || next.kind === "del")
|
|
322
|
-
? next.text.slice(0, 24)
|
|
323
|
-
: "",
|
|
480
|
+
prefix: isCodeRow(prev) ? diffRowText(prev).slice(-24) : "",
|
|
481
|
+
suffix: isCodeRow(next) ? diffRowText(next).slice(0, 24) : "",
|
|
324
482
|
};
|
|
325
483
|
}
|
|
326
484
|
|
|
485
|
+
/** Resolve a publishable PR finding exactly; fuzzy matches must remain visibly outdated. */
|
|
486
|
+
export function resolveReviewAnchorRow(
|
|
487
|
+
rows: DiffRow[],
|
|
488
|
+
anchor: Annotation["anchor"],
|
|
489
|
+
path: string,
|
|
490
|
+
side: "LEFT" | "RIGHT",
|
|
491
|
+
): { row: DiffRow; rowIndex: number } | undefined {
|
|
492
|
+
const candidates = rows.flatMap((row, rowIndex) => {
|
|
493
|
+
if (
|
|
494
|
+
row.file !== path ||
|
|
495
|
+
diffRowText(row) !== anchor.quote ||
|
|
496
|
+
(side === "RIGHT" ? row.newLine === undefined : row.oldLine === undefined)
|
|
497
|
+
)
|
|
498
|
+
return [];
|
|
499
|
+
const context = diffRowAnchorContext(rows, rowIndex);
|
|
500
|
+
|
|
501
|
+
return [
|
|
502
|
+
{
|
|
503
|
+
row,
|
|
504
|
+
rowIndex,
|
|
505
|
+
score: Number(context.prefix === anchor.prefix) + Number(context.suffix === anchor.suffix),
|
|
506
|
+
},
|
|
507
|
+
];
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
if (candidates.length === 0) return undefined;
|
|
511
|
+
const bestScore = Math.max(...candidates.map((candidate) => candidate.score));
|
|
512
|
+
const best = candidates.filter((candidate) => candidate.score === bestScore);
|
|
513
|
+
|
|
514
|
+
return best.length === 1 ? best[0] : undefined;
|
|
515
|
+
}
|
|
516
|
+
|
|
327
517
|
/** Location label for the rail and feedback: file:newLine (or old for del). */
|
|
328
518
|
export function diffRowLocation(row: DiffRow): string {
|
|
329
519
|
const line = row.newLine ?? row.oldLine;
|
package/src/view-plan.ts
CHANGED
|
@@ -361,6 +361,8 @@ export interface Mark {
|
|
|
361
361
|
annotationId?: string;
|
|
362
362
|
/** The whole anchored stretch in display coordinates; one thread per span. */
|
|
363
363
|
span?: TextSpan;
|
|
364
|
+
/** The recorded PR line remains visible, but its quote no longer resolves. */
|
|
365
|
+
outdated?: boolean;
|
|
364
366
|
}
|
|
365
367
|
|
|
366
368
|
/**
|