@cueloop/client 0.1.0-alpha.77 → 0.1.0-alpha.78
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.autoclose.test.tsx +3 -3
- package/src/App.diff.test.tsx +14 -2
- package/src/App.export.test.tsx +1 -2
- package/src/App.plan-review.test.tsx +16 -0
- package/src/App.submit-confirm.test.tsx +34 -5
- package/src/App.test.tsx +2 -2
- package/src/App.tsx +4 -0
- package/src/App.walk.test.tsx +2 -2
- package/src/__snapshots__/view-plan.regression.test.ts.snap +1 -1
- package/src/ansi-screen.test.ts +28 -0
- package/src/ansi-screen.ts +72 -0
- package/src/app-screens.tsx +7 -3
- package/src/app-view-model.ts +3 -0
- package/src/components/AnnotatableFileView.tsx +1 -1
- package/src/components/AnnotationCards.tsx +18 -2
- package/src/components/AnsiScreen.stories.tsx +12 -0
- package/src/components/AnsiScreen.tsx +28 -0
- package/src/components/CompletionOverlay.stories.tsx +15 -1
- package/src/components/CompletionOverlay.tsx +22 -5
- package/src/components/ConfirmCard.stories.tsx +2 -0
- package/src/components/ConfirmCard.tsx +45 -18
- package/src/components/ConfirmDialog.tsx +11 -12
- package/src/components/DiffContentView.tsx +4 -4
- package/src/components/FileContentsView.tsx +1 -1
- package/src/components/ManageAccessDialog.tsx +13 -11
- package/src/components/PromptDialog.test.tsx +2 -2
- package/src/components/PromptDialog.tsx +9 -10
- package/src/components/ShareChoiceDialog.tsx +0 -2
- package/src/components/SlashComposer.stories.tsx +37 -0
- package/src/components/SlashComposer.tsx +87 -0
- package/src/components/ThreadTree.tsx +11 -6
- package/src/components/ThreadView.selection.test.tsx +88 -1
- package/src/components/ThreadView.slash.test.tsx +0 -1
- package/src/components/ThreadView.tsx +66 -7
- package/src/components/Toast.tsx +0 -5
- package/src/components/WalkWizard.stories.tsx +2 -2
- package/src/components/WalkWizard.tsx +10 -12
- package/src/components/__snapshots__/stories.test.tsx.snap +219 -89
- package/src/components/primitives/Card.tsx +3 -0
- package/src/components/primitives/DialogActions.stories.tsx +20 -0
- package/src/components/primitives/DialogActions.tsx +39 -0
- package/src/components/session-tree.ts +7 -6
- package/src/components/stories-app.tsx +13 -4
- package/src/components/stories.test.tsx +8 -1
- package/src/components/story-tree.test.ts +32 -24
- package/src/components/story.ts +32 -15
- package/src/diff-intraline.test.ts +75 -2
- package/src/diff-intraline.ts +95 -26
- package/src/keymap.test.ts +2 -2
- package/src/keymap.ts +0 -1
- package/src/pasted-image.test.ts +67 -0
- package/src/pasted-image.ts +60 -0
- package/src/run.ts +31 -19
- package/src/serve.ts +1 -1
- package/src/share.ts +1 -1
- package/src/slash-palette.test.ts +17 -0
- package/src/slash-palette.ts +11 -0
- package/src/thread-controller.derivation.test.ts +62 -0
- package/src/thread-controller.optimistic.test.ts +69 -0
- package/src/thread-controller.switch-cache.test.ts +146 -0
- package/src/thread-controller.ts +157 -24
- package/src/use-annotation-surface.tsx +43 -27
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import React, { useEffect, useRef } from "react";
|
|
16
|
+
import { useTerminalDimensions } from "@opentui/react";
|
|
16
17
|
import type { KeyEvent, ScrollBoxRenderable } from "@opentui/core";
|
|
17
18
|
import type { Annotation, Thread } from "@cueloop/schema";
|
|
18
19
|
import { displayText, type DisplayBlock, type Mark } from "../view-plan";
|
|
@@ -23,10 +24,14 @@ import type { Theme } from "../theme";
|
|
|
23
24
|
import { BOLD, CUT, UNDERLINE } from "../annotation-palette";
|
|
24
25
|
import { lineMarkRanges, runsFor, wrapLines, type MarkRange, type VisualLine } from "../mark-runs";
|
|
25
26
|
import { useFrameMeasure } from "../use-frame-measure";
|
|
27
|
+
import { useTerminalVirtualizer } from "../use-terminal-virtualizer";
|
|
26
28
|
import { useAnnotationSurface, type LineSource } from "../use-annotation-surface";
|
|
27
29
|
import { DiscussionMarkerRail } from "./DiscussionMarkerRail";
|
|
28
30
|
import { useComponentTheme } from "./theme-context";
|
|
29
31
|
|
|
32
|
+
/** Blocks kept mounted beyond the viewport, so a scroll step or a drag past the edge never shows a gap. */
|
|
33
|
+
const OVERSCAN_BLOCKS = 8;
|
|
34
|
+
|
|
30
35
|
export { lighten } from "../annotation-palette";
|
|
31
36
|
|
|
32
37
|
/**
|
|
@@ -188,18 +193,36 @@ export function ThreadView({
|
|
|
188
193
|
});
|
|
189
194
|
const { palette, discussions } = surface;
|
|
190
195
|
const scrollRef = useRef<ScrollBoxRenderable | null>(null);
|
|
196
|
+
const { height: terminalHeight } = useTerminalDimensions();
|
|
191
197
|
const viewWidth = useFrameMeasure(
|
|
192
198
|
() => scrollRef.current?.content?.width ?? 0,
|
|
193
199
|
(left, right) => left === right,
|
|
194
200
|
0,
|
|
195
201
|
);
|
|
196
202
|
|
|
203
|
+
// only the viewport's blocks (plus an overscan) mount; a block is estimated at its wrapped-line
|
|
204
|
+
// count until its box, cards included, is measured. The initial viewport seeds the first render so a
|
|
205
|
+
// freshly mounted thread (a switch from a diff, another project) windows its first paint rather than
|
|
206
|
+
// building every block, and never flashes blank.
|
|
207
|
+
const estimateBlockRows = (index: number): number => {
|
|
208
|
+
const block = display[index];
|
|
209
|
+
|
|
210
|
+
if (!block) return 1;
|
|
211
|
+
const usable = viewWidth > 0 ? Math.max(1, viewWidth - 6) : 40;
|
|
212
|
+
const lines = Math.max(1, Math.ceil(displayText(block).length / usable));
|
|
213
|
+
|
|
214
|
+
return lines + (index === 0 ? 0 : 1);
|
|
215
|
+
};
|
|
216
|
+
const virtual = useTerminalVirtualizer({
|
|
217
|
+
scrollbox: scrollRef,
|
|
218
|
+
count: display.length,
|
|
219
|
+
estimateSize: estimateBlockRows,
|
|
220
|
+
overscan: OVERSCAN_BLOCKS,
|
|
221
|
+
});
|
|
222
|
+
|
|
197
223
|
useEffect(() => {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
} catch {
|
|
201
|
-
// best-effort reveal
|
|
202
|
-
}
|
|
224
|
+
virtual.scrollToIndex(surface.revealBlockIndex);
|
|
225
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
203
226
|
}, [surface.revealBlockIndex]);
|
|
204
227
|
|
|
205
228
|
interface LineContext {
|
|
@@ -295,18 +318,54 @@ export function ThreadView({
|
|
|
295
318
|
</text>
|
|
296
319
|
) : null;
|
|
297
320
|
|
|
321
|
+
// the leading gap rides inside the measured box (not marginTop, which sits outside it) so the
|
|
322
|
+
// virtualizer's spacer math counts it
|
|
323
|
+
const leadingGap =
|
|
324
|
+
blockIndex === 0 || tight ? null : <box key="lead-gap" style={{ height: 1 }} />;
|
|
325
|
+
|
|
298
326
|
return (
|
|
299
327
|
<box
|
|
300
328
|
key={`discussion-block-${blockIndex}`}
|
|
301
329
|
id={`discussion-block-${blockIndex}`}
|
|
302
|
-
|
|
330
|
+
ref={virtual.measureRef(blockIndex)}
|
|
331
|
+
style={{ flexDirection: "column" }}
|
|
303
332
|
>
|
|
333
|
+
{leadingGap}
|
|
304
334
|
{languageRow}
|
|
305
335
|
{lineRows}
|
|
306
336
|
</box>
|
|
307
337
|
);
|
|
308
338
|
};
|
|
309
339
|
|
|
340
|
+
const virtualBlocks = (): React.ReactNode[] => {
|
|
341
|
+
const nodes: React.ReactNode[] = [];
|
|
342
|
+
const firstItem = virtual.items[0];
|
|
343
|
+
const lastItem = virtual.items.at(-1);
|
|
344
|
+
|
|
345
|
+
// the very first render has no measured viewport yet, so the virtualizer's window is empty; paint a
|
|
346
|
+
// viewport-worth from the top rather than blank or the whole document, then it takes over next frame
|
|
347
|
+
if (!firstItem || !lastItem) {
|
|
348
|
+
let mountedRows = 0;
|
|
349
|
+
|
|
350
|
+
for (let index = 0; index < display.length; index++) {
|
|
351
|
+
nodes.push(blockNodeFor(index));
|
|
352
|
+
mountedRows += estimateBlockRows(index);
|
|
353
|
+
if (mountedRows > terminalHeight + OVERSCAN_BLOCKS) break;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return nodes;
|
|
357
|
+
}
|
|
358
|
+
if (firstItem.start > 0) {
|
|
359
|
+
nodes.push(<box key="spacer-above" style={{ height: firstItem.start }} />);
|
|
360
|
+
}
|
|
361
|
+
for (const item of virtual.items) nodes.push(blockNodeFor(item.index));
|
|
362
|
+
const below = virtual.totalSize - lastItem.end;
|
|
363
|
+
|
|
364
|
+
if (below > 0) nodes.push(<box key="spacer-below" style={{ height: below }} />);
|
|
365
|
+
|
|
366
|
+
return nodes;
|
|
367
|
+
};
|
|
368
|
+
|
|
310
369
|
return (
|
|
311
370
|
<box style={{ flexGrow: 1, flexDirection: "row" }} {...surface.rootMouseProps}>
|
|
312
371
|
<box style={{ flexGrow: 1, flexDirection: "column" }}>
|
|
@@ -324,7 +383,7 @@ export function ThreadView({
|
|
|
324
383
|
focused={false}
|
|
325
384
|
verticalScrollbarOptions={{ visible: false }}
|
|
326
385
|
>
|
|
327
|
-
{
|
|
386
|
+
{virtualBlocks()}
|
|
328
387
|
</scrollbox>
|
|
329
388
|
</box>
|
|
330
389
|
<DiscussionMarkerRail
|
package/src/components/Toast.tsx
CHANGED
|
@@ -34,11 +34,6 @@ export function Toast({ title, body, theme }: ToastProps): React.ReactNode {
|
|
|
34
34
|
}}
|
|
35
35
|
>
|
|
36
36
|
<text fg={tokens.text}>{body}</text>
|
|
37
|
-
<box style={{ flexGrow: 1 }} />
|
|
38
|
-
<box style={{ flexDirection: "row" }}>
|
|
39
|
-
<box style={{ flexGrow: 1 }} />
|
|
40
|
-
<text fg={tokens.textDim}>[esc]</text>
|
|
41
|
-
</box>
|
|
42
37
|
</box>
|
|
43
38
|
</Dialog>
|
|
44
39
|
);
|
|
@@ -81,7 +81,7 @@ export const EndCard: Story = {
|
|
|
81
81
|
onBack={noop}
|
|
82
82
|
/>
|
|
83
83
|
),
|
|
84
|
-
//
|
|
84
|
+
// accent border and solid submit; the all-viewed line turns green
|
|
85
85
|
expectedColors: [DARK.green, DARK.accent, DARK.accentInk],
|
|
86
86
|
};
|
|
87
87
|
|
|
@@ -96,5 +96,5 @@ export const EndCardPartialPass: Story = {
|
|
|
96
96
|
onBack={noop}
|
|
97
97
|
/>
|
|
98
98
|
),
|
|
99
|
-
expectedColors: [DARK.
|
|
99
|
+
expectedColors: [DARK.accent, DARK.accentInk],
|
|
100
100
|
};
|
|
@@ -13,8 +13,7 @@ import { useComponentTheme } from "./theme-context";
|
|
|
13
13
|
import type { WalkFile } from "../walk";
|
|
14
14
|
import { viewedCount } from "../walk";
|
|
15
15
|
import { Card } from "./primitives/Card";
|
|
16
|
-
import {
|
|
17
|
-
import { Toolbar } from "./primitives/Toolbar";
|
|
16
|
+
import { DialogActions } from "./primitives/DialogActions";
|
|
18
17
|
|
|
19
18
|
export interface WalkWizardProps {
|
|
20
19
|
files: WalkFile[];
|
|
@@ -75,24 +74,23 @@ export function WalkWizard({
|
|
|
75
74
|
<Card
|
|
76
75
|
title=" walk complete "
|
|
77
76
|
contentRows={3}
|
|
78
|
-
borderColor={tokens.
|
|
77
|
+
borderColor={tokens.accent}
|
|
79
78
|
backgroundColor={tokens.elevated}
|
|
80
79
|
theme={theme}
|
|
81
80
|
>
|
|
82
|
-
<text fg={tokens.text}>
|
|
81
|
+
<text fg={viewed === files.length ? tokens.green : tokens.text}>
|
|
83
82
|
{viewed === files.length
|
|
84
83
|
? `every file viewed (${viewed}/${files.length})`
|
|
85
84
|
: `${viewed} of ${files.length} files viewed`}
|
|
86
85
|
</text>
|
|
87
86
|
<box style={{ height: 1 }} />
|
|
88
|
-
<
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
</Toolbar>
|
|
87
|
+
<DialogActions
|
|
88
|
+
confirmLabel="submit"
|
|
89
|
+
cancelLabel="back"
|
|
90
|
+
onConfirm={onSubmitRequest}
|
|
91
|
+
onCancel={onBack}
|
|
92
|
+
theme={theme}
|
|
93
|
+
/>
|
|
96
94
|
</Card>
|
|
97
95
|
) : (
|
|
98
96
|
<>
|