@cueloop/client 0.1.0-alpha.76 → 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.identity.test.tsx +78 -0
- package/src/App.plan-review.test.tsx +16 -0
- package/src/App.share-choice.test.tsx +105 -0
- package/src/App.submit-confirm.test.tsx +34 -5
- package/src/App.test.tsx +2 -2
- package/src/App.toast.test.tsx +3 -1
- package/src/App.tsx +180 -5
- 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 +20 -3
- package/src/app-view-model.ts +4 -0
- package/src/components/AnnotatableFileView.tsx +10 -2
- package/src/components/AnnotationCards.tsx +40 -6
- 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 +9 -5
- package/src/components/FileContentsView.tsx +1 -1
- package/src/components/GridTabContent.tsx +2 -0
- package/src/components/ManageAccessDialog.stories.tsx +36 -0
- package/src/components/ManageAccessDialog.test.tsx +94 -0
- package/src/components/ManageAccessDialog.tsx +122 -0
- package/src/components/PromptDialog.test.tsx +2 -2
- package/src/components/PromptDialog.tsx +9 -10
- package/src/components/ShareChoiceDialog.stories.tsx +34 -0
- package/src/components/ShareChoiceDialog.test.tsx +103 -0
- package/src/components/ShareChoiceDialog.tsx +80 -0
- 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 +71 -8
- 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 +277 -61
- 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/config-identity.test.ts +78 -0
- package/src/config.ts +44 -1
- package/src/diff-intraline.test.ts +75 -2
- package/src/diff-intraline.ts +95 -26
- package/src/github-identity.test.ts +58 -0
- package/src/github-identity.ts +41 -0
- package/src/intent-dispatch.test.ts +17 -2
- package/src/intent-dispatch.ts +7 -1
- package/src/keymap.test.ts +2 -2
- package/src/keymap.ts +1 -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 +11 -3
- package/src/slash-palette.test.ts +17 -0
- package/src/slash-palette.ts +11 -0
- package/src/thread-controller.curate.test.ts +1 -0
- package/src/thread-controller.derivation.test.ts +62 -0
- package/src/thread-controller.live-diff.test.ts +1 -0
- package/src/thread-controller.optimistic.test.ts +69 -0
- package/src/thread-controller.share.test.ts +36 -2
- package/src/thread-controller.switch-cache.test.ts +146 -0
- package/src/thread-controller.tree.test.ts +1 -0
- package/src/thread-controller.ts +171 -24
- package/src/use-annotation-surface.tsx +53 -29
- package/src/use-settings-dialog.tsx +24 -0
|
@@ -90,7 +90,6 @@ describe("the command leader", () => {
|
|
|
90
90
|
|
|
91
91
|
// the command fired and no composer opened (a bare "x" would have started a draft)
|
|
92
92
|
expect(commands).toEqual(["x"]);
|
|
93
|
-
expect(setup.captureCharFrame()).not.toContain("enter save");
|
|
94
93
|
|
|
95
94
|
setup.renderer.destroy();
|
|
96
95
|
});
|
|
@@ -13,8 +13,9 @@
|
|
|
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
|
-
import type { Thread } from "@cueloop/schema";
|
|
18
|
+
import type { Annotation, Thread } from "@cueloop/schema";
|
|
18
19
|
import { displayText, type DisplayBlock, type Mark } from "../view-plan";
|
|
19
20
|
import type { TextSpan } from "../thread-selection";
|
|
20
21
|
import type { QuickAction } from "../config";
|
|
@@ -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
|
/**
|
|
@@ -127,6 +132,8 @@ export interface ThreadViewProps {
|
|
|
127
132
|
onAnnotate: (span: TextSpan, body: string) => void;
|
|
128
133
|
onReply: (rootAnnotationId: string, body: string) => void;
|
|
129
134
|
onUpdateAnnotation: (id: string, body: string) => void;
|
|
135
|
+
/** The author's display name for a comment's hover tooltip. */
|
|
136
|
+
resolveAuthorLabel?: (annotation: Annotation) => string | undefined;
|
|
130
137
|
leaderCombos?: readonly string[];
|
|
131
138
|
onLeaderCommand?: (key: KeyEvent) => void;
|
|
132
139
|
onExit: () => void;
|
|
@@ -150,6 +157,7 @@ export function ThreadView({
|
|
|
150
157
|
onAnnotate,
|
|
151
158
|
onReply,
|
|
152
159
|
onUpdateAnnotation,
|
|
160
|
+
resolveAuthorLabel,
|
|
153
161
|
leaderCombos,
|
|
154
162
|
onLeaderCommand,
|
|
155
163
|
onExit,
|
|
@@ -178,24 +186,43 @@ export function ThreadView({
|
|
|
178
186
|
onAnnotate,
|
|
179
187
|
onReply,
|
|
180
188
|
onUpdateAnnotation,
|
|
189
|
+
resolveAuthorLabel,
|
|
181
190
|
leaderCombos,
|
|
182
191
|
onLeaderCommand,
|
|
183
192
|
onExit,
|
|
184
193
|
});
|
|
185
194
|
const { palette, discussions } = surface;
|
|
186
195
|
const scrollRef = useRef<ScrollBoxRenderable | null>(null);
|
|
196
|
+
const { height: terminalHeight } = useTerminalDimensions();
|
|
187
197
|
const viewWidth = useFrameMeasure(
|
|
188
198
|
() => scrollRef.current?.content?.width ?? 0,
|
|
189
199
|
(left, right) => left === right,
|
|
190
200
|
0,
|
|
191
201
|
);
|
|
192
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
|
+
|
|
193
223
|
useEffect(() => {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
} catch {
|
|
197
|
-
// best-effort reveal
|
|
198
|
-
}
|
|
224
|
+
virtual.scrollToIndex(surface.revealBlockIndex);
|
|
225
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
199
226
|
}, [surface.revealBlockIndex]);
|
|
200
227
|
|
|
201
228
|
interface LineContext {
|
|
@@ -291,18 +318,54 @@ export function ThreadView({
|
|
|
291
318
|
</text>
|
|
292
319
|
) : null;
|
|
293
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
|
+
|
|
294
326
|
return (
|
|
295
327
|
<box
|
|
296
328
|
key={`discussion-block-${blockIndex}`}
|
|
297
329
|
id={`discussion-block-${blockIndex}`}
|
|
298
|
-
|
|
330
|
+
ref={virtual.measureRef(blockIndex)}
|
|
331
|
+
style={{ flexDirection: "column" }}
|
|
299
332
|
>
|
|
333
|
+
{leadingGap}
|
|
300
334
|
{languageRow}
|
|
301
335
|
{lineRows}
|
|
302
336
|
</box>
|
|
303
337
|
);
|
|
304
338
|
};
|
|
305
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
|
+
|
|
306
369
|
return (
|
|
307
370
|
<box style={{ flexGrow: 1, flexDirection: "row" }} {...surface.rootMouseProps}>
|
|
308
371
|
<box style={{ flexGrow: 1, flexDirection: "column" }}>
|
|
@@ -320,7 +383,7 @@ export function ThreadView({
|
|
|
320
383
|
focused={false}
|
|
321
384
|
verticalScrollbarOptions={{ visible: false }}
|
|
322
385
|
>
|
|
323
|
-
{
|
|
386
|
+
{virtualBlocks()}
|
|
324
387
|
</scrollbox>
|
|
325
388
|
</box>
|
|
326
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
|
<>
|