@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.
Files changed (82) hide show
  1. package/package.json +5 -5
  2. package/src/App.autoclose.test.tsx +3 -3
  3. package/src/App.diff.test.tsx +14 -2
  4. package/src/App.export.test.tsx +1 -2
  5. package/src/App.identity.test.tsx +78 -0
  6. package/src/App.plan-review.test.tsx +16 -0
  7. package/src/App.share-choice.test.tsx +105 -0
  8. package/src/App.submit-confirm.test.tsx +34 -5
  9. package/src/App.test.tsx +2 -2
  10. package/src/App.toast.test.tsx +3 -1
  11. package/src/App.tsx +180 -5
  12. package/src/App.walk.test.tsx +2 -2
  13. package/src/__snapshots__/view-plan.regression.test.ts.snap +1 -1
  14. package/src/ansi-screen.test.ts +28 -0
  15. package/src/ansi-screen.ts +72 -0
  16. package/src/app-screens.tsx +20 -3
  17. package/src/app-view-model.ts +4 -0
  18. package/src/components/AnnotatableFileView.tsx +10 -2
  19. package/src/components/AnnotationCards.tsx +40 -6
  20. package/src/components/AnsiScreen.stories.tsx +12 -0
  21. package/src/components/AnsiScreen.tsx +28 -0
  22. package/src/components/CompletionOverlay.stories.tsx +15 -1
  23. package/src/components/CompletionOverlay.tsx +22 -5
  24. package/src/components/ConfirmCard.stories.tsx +2 -0
  25. package/src/components/ConfirmCard.tsx +45 -18
  26. package/src/components/ConfirmDialog.tsx +11 -12
  27. package/src/components/DiffContentView.tsx +9 -5
  28. package/src/components/FileContentsView.tsx +1 -1
  29. package/src/components/GridTabContent.tsx +2 -0
  30. package/src/components/ManageAccessDialog.stories.tsx +36 -0
  31. package/src/components/ManageAccessDialog.test.tsx +94 -0
  32. package/src/components/ManageAccessDialog.tsx +122 -0
  33. package/src/components/PromptDialog.test.tsx +2 -2
  34. package/src/components/PromptDialog.tsx +9 -10
  35. package/src/components/ShareChoiceDialog.stories.tsx +34 -0
  36. package/src/components/ShareChoiceDialog.test.tsx +103 -0
  37. package/src/components/ShareChoiceDialog.tsx +80 -0
  38. package/src/components/SlashComposer.stories.tsx +37 -0
  39. package/src/components/SlashComposer.tsx +87 -0
  40. package/src/components/ThreadTree.tsx +11 -6
  41. package/src/components/ThreadView.selection.test.tsx +88 -1
  42. package/src/components/ThreadView.slash.test.tsx +0 -1
  43. package/src/components/ThreadView.tsx +71 -8
  44. package/src/components/Toast.tsx +0 -5
  45. package/src/components/WalkWizard.stories.tsx +2 -2
  46. package/src/components/WalkWizard.tsx +10 -12
  47. package/src/components/__snapshots__/stories.test.tsx.snap +277 -61
  48. package/src/components/primitives/Card.tsx +3 -0
  49. package/src/components/primitives/DialogActions.stories.tsx +20 -0
  50. package/src/components/primitives/DialogActions.tsx +39 -0
  51. package/src/components/session-tree.ts +7 -6
  52. package/src/components/stories-app.tsx +13 -4
  53. package/src/components/stories.test.tsx +8 -1
  54. package/src/components/story-tree.test.ts +32 -24
  55. package/src/components/story.ts +32 -15
  56. package/src/config-identity.test.ts +78 -0
  57. package/src/config.ts +44 -1
  58. package/src/diff-intraline.test.ts +75 -2
  59. package/src/diff-intraline.ts +95 -26
  60. package/src/github-identity.test.ts +58 -0
  61. package/src/github-identity.ts +41 -0
  62. package/src/intent-dispatch.test.ts +17 -2
  63. package/src/intent-dispatch.ts +7 -1
  64. package/src/keymap.test.ts +2 -2
  65. package/src/keymap.ts +1 -1
  66. package/src/pasted-image.test.ts +67 -0
  67. package/src/pasted-image.ts +60 -0
  68. package/src/run.ts +31 -19
  69. package/src/serve.ts +1 -1
  70. package/src/share.ts +11 -3
  71. package/src/slash-palette.test.ts +17 -0
  72. package/src/slash-palette.ts +11 -0
  73. package/src/thread-controller.curate.test.ts +1 -0
  74. package/src/thread-controller.derivation.test.ts +62 -0
  75. package/src/thread-controller.live-diff.test.ts +1 -0
  76. package/src/thread-controller.optimistic.test.ts +69 -0
  77. package/src/thread-controller.share.test.ts +36 -2
  78. package/src/thread-controller.switch-cache.test.ts +146 -0
  79. package/src/thread-controller.tree.test.ts +1 -0
  80. package/src/thread-controller.ts +171 -24
  81. package/src/use-annotation-surface.tsx +53 -29
  82. package/src/use-settings-dialog.tsx +24 -0
@@ -15,7 +15,7 @@
15
15
  import React, { useContext, useEffect, useRef, useState } from "react";
16
16
  import type { KeyEvent, MouseEvent as TerminalMouseEvent, TextRenderable } from "@opentui/core";
17
17
  import { useKeyboard } from "@opentui/react";
18
- import type { Thread } from "@cueloop/schema";
18
+ import type { Annotation, Thread } from "@cueloop/schema";
19
19
  import type { Mark } from "./view-plan";
20
20
  import type { QuickAction } from "./config";
21
21
  import type { Theme } from "./theme";
@@ -40,6 +40,7 @@ import { printableSequence, type MarkRange, type VisualLine } from "./mark-runs"
40
40
  import { matchesLeader } from "./thread-chords";
41
41
  import {
42
42
  activeSlashToken,
43
+ insertSlashItem,
43
44
  isStandaloneSlashQuery,
44
45
  mergeSlashItems,
45
46
  slashFilter,
@@ -104,6 +105,8 @@ export interface AnnotationSurfaceOptions {
104
105
  onAnnotate: (span: TextSpan, body: string) => void;
105
106
  onReply: (rootAnnotationId: string, body: string) => void;
106
107
  onUpdateAnnotation: (id: string, body: string) => void;
108
+ /** The author's display name for a comment's hover tooltip; the rail resolves it against the participant registry. */
109
+ resolveAuthorLabel?: (annotation: Annotation) => string | undefined;
107
110
  leaderCombos?: readonly string[];
108
111
  onLeaderCommand?: (key: KeyEvent) => void;
109
112
  onExit: () => void;
@@ -179,6 +182,7 @@ export function useAnnotationSurface(options: AnnotationSurfaceOptions): Annotat
179
182
  onAnnotate,
180
183
  onReply,
181
184
  onUpdateAnnotation,
185
+ resolveAuthorLabel,
182
186
  leaderCombos,
183
187
  onLeaderCommand,
184
188
  onExit,
@@ -240,6 +244,11 @@ export function useAnnotationSurface(options: AnnotationSurfaceOptions): Annotat
240
244
  };
241
245
  const [folded, setFolded] = useState<Set<string>>(new Set());
242
246
  const [composeText, setComposeText] = useState("");
247
+ const composeTextRef = useRef("");
248
+ const setDraft = (text: string): void => {
249
+ composeTextRef.current = text;
250
+ setComposeText(text);
251
+ };
243
252
  const [caretOffset, setCaretOffset] = useState(0);
244
253
  const [slashIndex, setSlashIndex] = useState(0);
245
254
  const composerReady = useRef(false);
@@ -311,14 +320,14 @@ export function useAnnotationSurface(options: AnnotationSurfaceOptions): Annotat
311
320
  composerReady.current = false;
312
321
  composeRef.current = state;
313
322
  setCompose(state);
314
- setComposeText(state.seed);
323
+ setDraft(state.seed);
315
324
  setCaretOffset(state.seed.length);
316
325
  setSlashIndex(0);
317
326
  };
318
327
  const closeCompose = (): void => {
319
328
  composeRef.current = null;
320
329
  setCompose(null);
321
- setComposeText("");
330
+ setDraft("");
322
331
  setCaretOffset(0);
323
332
  };
324
333
  const skills = useContext(SlashSkillsContext);
@@ -448,14 +457,10 @@ export function useAnnotationSurface(options: AnnotationSurfaceOptions): Annotat
448
457
  return true;
449
458
  }
450
459
  if (key.name === "return" || key.name === "tab") {
451
- const token = activeSlashToken(composeText, caretOffset) ?? "";
452
- const cut = caretOffset - token.length;
453
- const insertion = `/${slashItems[selected]!.name} `;
454
- const seed = composeText.slice(0, cut) + insertion + composeText.slice(caretOffset);
460
+ const inserted = insertSlashItem(composeText, caretOffset, slashItems[selected]!.name);
455
461
 
456
- openCompose({ ...activeCompose, seed });
457
- // land the caret just after the inserted reference, not at the end of a chained draft
458
- setCaretOffset(cut + insertion.length);
462
+ openCompose({ ...activeCompose, seed: inserted.text });
463
+ setCaretOffset(inserted.caret);
459
464
 
460
465
  return true;
461
466
  }
@@ -470,7 +475,7 @@ export function useAnnotationSurface(options: AnnotationSurfaceOptions): Annotat
470
475
  const grown = { ...activeCompose, seed: `${activeCompose.seed}\n` };
471
476
 
472
477
  composeRef.current = grown;
473
- setComposeText(grown.seed);
478
+ setDraft(grown.seed);
474
479
  setCaretOffset(grown.seed.length);
475
480
 
476
481
  return setCompose(grown);
@@ -481,7 +486,7 @@ export function useAnnotationSurface(options: AnnotationSurfaceOptions): Annotat
481
486
  const grown = { ...activeCompose, seed: activeCompose.seed + sequence };
482
487
 
483
488
  composeRef.current = grown;
484
- setComposeText(grown.seed);
489
+ setDraft(grown.seed);
485
490
  setCaretOffset(grown.seed.length);
486
491
  setCompose(grown);
487
492
  }
@@ -500,7 +505,7 @@ export function useAnnotationSurface(options: AnnotationSurfaceOptions): Annotat
500
505
  // (an edit of an existing comment is never deleted this way)
501
506
  if (
502
507
  key.name === "backspace" &&
503
- composeText.length === 0 &&
508
+ composeTextRef.current.length === 0 &&
504
509
  activeCompose.editAnnotationId === null
505
510
  ) {
506
511
  return closeCompose();
@@ -701,7 +706,7 @@ export function useAnnotationSurface(options: AnnotationSurfaceOptions): Annotat
701
706
  onSave={saveComment}
702
707
  onReady={() => (composerReady.current = true)}
703
708
  onInput={(text, caret) => {
704
- setComposeText(text);
709
+ setDraft(text);
705
710
  setCaretOffset(caret);
706
711
  }}
707
712
  />
@@ -735,7 +740,12 @@ export function useAnnotationSurface(options: AnnotationSurfaceOptions): Annotat
735
740
  node: editingHere ? (
736
741
  composerNode
737
742
  ) : (
738
- <CommentRow key={annotation.id} annotation={annotation} tokens={tokens} />
743
+ <CommentRow
744
+ key={annotation.id}
745
+ annotation={annotation}
746
+ tokens={tokens}
747
+ authorLabel={resolveAuthorLabel?.(annotation)}
748
+ />
739
749
  ),
740
750
  };
741
751
  });
@@ -791,9 +801,36 @@ export function useAnnotationSurface(options: AnnotationSurfaceOptions): Annotat
791
801
  const endsInLine = (end: number): boolean => end - 1 >= line.start && end - 1 < line.end;
792
802
  const nodes: React.ReactNode[] = [];
793
803
  const composeHere = compose && compose.blockIndex === blockIndex;
804
+ const newComposeHere = Boolean(composeHere && compose.discussionKey === null && composerNode);
805
+ const composeAnchoredHere =
806
+ newComposeHere && (compose!.span ? endsInLine(compose!.span.end.char) : isLastLine);
807
+ const composeStart = compose?.span?.start;
808
+
809
+ const pushComposeCard = (): void => {
810
+ nodes.push(
811
+ <DiscussionCard
812
+ key="compose-new"
813
+ tokens={tokens}
814
+ segments={[{ color: palette.cardEdge, node: composerNode }]}
815
+ />,
816
+ );
817
+ nodes.push(paletteNode);
818
+ };
819
+
820
+ // the draft sorts in by its span start, the same order discussionsFrom lands it on save, so a
821
+ // new comment sits in its final position while typing instead of appending to the stack
822
+ let composePending = composeAnchoredHere;
794
823
 
795
824
  for (const discussion of discussions) {
796
825
  if (discussion.blockIndex !== blockIndex || !endsInLine(discussion.span.end.char)) continue;
826
+ if (
827
+ composePending &&
828
+ composeStart &&
829
+ comparePositions(composeStart, discussion.span.start) < 0
830
+ ) {
831
+ pushComposeCard();
832
+ composePending = false;
833
+ }
797
834
  const composingHere = Boolean(composeHere && compose.discussionKey === discussion.key);
798
835
 
799
836
  if (folded.has(discussion.key) && !composingHere) {
@@ -803,20 +840,7 @@ export function useAnnotationSurface(options: AnnotationSurfaceOptions): Annotat
803
840
  nodes.push(discussionCardFor(discussion, composingHere));
804
841
  if (composingHere) nodes.push(paletteNode);
805
842
  }
806
- if (composeHere && compose.discussionKey === null && composerNode) {
807
- const anchoredHere = compose.span ? endsInLine(compose.span.end.char) : isLastLine;
808
-
809
- if (anchoredHere) {
810
- nodes.push(
811
- <DiscussionCard
812
- key="compose-new"
813
- tokens={tokens}
814
- segments={[{ color: palette.cardEdge, node: composerNode }]}
815
- />,
816
- );
817
- nodes.push(paletteNode);
818
- }
819
- }
843
+ if (composePending) pushComposeCard();
820
844
 
821
845
  return nodes;
822
846
  };
@@ -83,6 +83,10 @@ export function useSettingsDialog(params: {
83
83
  quickActions: QuickAction[];
84
84
  setQuickActions: Dispatch<SetStateAction<QuickAction[]>>;
85
85
  setMenuDialog: Dispatch<SetStateAction<"keybinds" | "settings" | null>>;
86
+ identityName?: string;
87
+ identityProvider: "typed" | "github";
88
+ onSyncGithubIdentity: () => void;
89
+ onRenameDisplayName: () => void;
86
90
  }): SettingsDialogModel {
87
91
  const {
88
92
  theme,
@@ -98,6 +102,10 @@ export function useSettingsDialog(params: {
98
102
  quickActions,
99
103
  setQuickActions,
100
104
  setMenuDialog,
105
+ identityName,
106
+ identityProvider,
107
+ onSyncGithubIdentity,
108
+ onRenameDisplayName,
101
109
  } = params;
102
110
 
103
111
  const [settingsNav, setSettingsNav] = useState<SettingsNav>({
@@ -157,6 +165,15 @@ export function useSettingsDialog(params: {
157
165
  },
158
166
  ],
159
167
  },
168
+ {
169
+ id: "account",
170
+ name: "Account",
171
+ rows: [
172
+ { key: "displayName", label: "Display name", kind: "text" },
173
+ { key: "identitySource", label: "Source", kind: "text" },
174
+ { key: "syncGithub", label: "Sync from GitHub", kind: "text" },
175
+ ],
176
+ },
160
177
  {
161
178
  id: "appearance",
162
179
  name: "Appearance",
@@ -202,6 +219,9 @@ export function useSettingsDialog(params: {
202
219
  autoClose: autoClose === "off" ? "off" : `${autoClose}s`,
203
220
  diffView: diffView === "split" ? "Split" : "Stacked",
204
221
  theme: THEME_LABELS[themeName],
222
+ displayName: identityName ?? "-- not set --",
223
+ identitySource: identityProvider === "github" ? "GitHub" : "typed",
224
+ syncGithub: "enter to sync",
205
225
  };
206
226
  const cycleSetting = (rowKey: string): void => {
207
227
  if (rowKey === "autoClose") {
@@ -220,6 +240,10 @@ export function useSettingsDialog(params: {
220
240
  setThemeName(next);
221
241
  setTheme(composeTheme(next, themeOverrides, appearance));
222
242
  persistTheme(next);
243
+ } else if (rowKey === "syncGithub") {
244
+ onSyncGithubIdentity();
245
+ } else if (rowKey === "displayName") {
246
+ onRenameDisplayName();
223
247
  }
224
248
  };
225
249