@cueloop/client 0.1.0-alpha.33 → 0.1.0-alpha.35

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 (94) hide show
  1. package/package.json +27 -26
  2. package/src/App.autoclose.test.tsx +7 -4
  3. package/src/App.diff.test.tsx +130 -4
  4. package/src/App.export.test.tsx +22 -4
  5. package/src/App.plan-review.test.tsx +127 -27
  6. package/src/App.readonly.test.tsx +31 -8
  7. package/src/App.rendering.test.tsx +12 -3
  8. package/src/App.review-panel.test.tsx +11 -3
  9. package/src/App.submit-confirm.test.tsx +12 -7
  10. package/src/App.test.tsx +46 -9
  11. package/src/App.toast.test.tsx +10 -3
  12. package/src/App.tsx +400 -100
  13. package/src/App.walk.test.tsx +30 -10
  14. package/src/attribution.ts +8 -2
  15. package/src/clipboard.ts +5 -1
  16. package/src/components/AnnotationCard.stories.tsx +28 -9
  17. package/src/components/AnnotationCard.tsx +53 -38
  18. package/src/components/Breadcrumb.tsx +7 -1
  19. package/src/components/CodeBlock.stories.tsx +19 -2
  20. package/src/components/CodeBlock.tsx +24 -9
  21. package/src/components/CompletionOverlay.tsx +13 -3
  22. package/src/components/ConfirmCard.stories.tsx +22 -3
  23. package/src/components/ConfirmCard.tsx +22 -5
  24. package/src/components/ConfirmDialog.tsx +9 -1
  25. package/src/components/DiffSheet.scroll.test.tsx +123 -0
  26. package/src/components/DiffSheet.stories.tsx +18 -2
  27. package/src/components/DiffSheet.syntax.test.tsx +62 -0
  28. package/src/components/DiffSheet.tsx +184 -98
  29. package/src/components/InboxList.stories.tsx +16 -3
  30. package/src/components/InboxList.tsx +17 -10
  31. package/src/components/KeybindsDialog.tsx +19 -3
  32. package/src/components/MarkerPopover.stories.tsx +58 -0
  33. package/src/components/MarkerPopover.tsx +102 -0
  34. package/src/components/MenuBar.stories.tsx +5 -1
  35. package/src/components/MenuBar.tsx +18 -2
  36. package/src/components/PlanSheet.stories.tsx +34 -2
  37. package/src/components/PlanSheet.tsx +141 -32
  38. package/src/components/PromptDialog.tsx +18 -2
  39. package/src/components/ReviewPanel.stories.tsx +13 -3
  40. package/src/components/ReviewPanel.tsx +8 -4
  41. package/src/components/ReviewRail.stories.tsx +145 -72
  42. package/src/components/ReviewRail.tsx +177 -38
  43. package/src/components/SettingsDialog.stories.tsx +51 -3
  44. package/src/components/SettingsDialog.tsx +8 -3
  45. package/src/components/SettingsRows.tsx +19 -3
  46. package/src/components/Toast.tsx +17 -2
  47. package/src/components/WalkWizard.stories.tsx +13 -2
  48. package/src/components/WalkWizard.tsx +9 -3
  49. package/src/components/__snapshots__/stories.test.tsx.snap +281 -83
  50. package/src/components/diff-sheet-layout.test.ts +127 -0
  51. package/src/components/diff-sheet-layout.ts +167 -0
  52. package/src/components/primitives/Button.tsx +5 -1
  53. package/src/components/primitives/Dialog.tsx +9 -1
  54. package/src/components/primitives/Tabs.tsx +24 -4
  55. package/src/components/stories-app.tsx +34 -6
  56. package/src/components/story-fixtures.ts +11 -3
  57. package/src/components/story.ts +3 -1
  58. package/src/components/syntax-highlight.ts +47 -12
  59. package/src/components/theme-context.stories.tsx +6 -1
  60. package/src/components/theme-context.tsx +7 -1
  61. package/src/config.test.ts +178 -2
  62. package/src/config.ts +121 -23
  63. package/src/diff-hunk-curate.test.ts +315 -0
  64. package/src/diff-hunk-curate.ts +233 -0
  65. package/src/diff-intraline.test.ts +164 -0
  66. package/src/diff-intraline.ts +132 -0
  67. package/src/diff-syntax.test.ts +96 -0
  68. package/src/diff-syntax.ts +154 -0
  69. package/src/editor.test.ts +13 -4
  70. package/src/editor.ts +31 -5
  71. package/src/herdr.ts +8 -2
  72. package/src/index.ts +9 -1
  73. package/src/integrations.ts +3 -1
  74. package/src/intent-dispatch.test.ts +193 -9
  75. package/src/intent-dispatch.ts +151 -18
  76. package/src/key-bindings.test.ts +19 -9
  77. package/src/key-bindings.ts +36 -14
  78. package/src/keymap.test.ts +171 -26
  79. package/src/keymap.ts +83 -16
  80. package/src/review-panel.test.ts +3 -1
  81. package/src/review-panel.ts +9 -2
  82. package/src/serve.test.ts +11 -3
  83. package/src/session-controller.curate.test.ts +253 -0
  84. package/src/session-controller.share.test.ts +65 -17
  85. package/src/session-controller.ts +301 -26
  86. package/src/share.ts +41 -8
  87. package/src/test-support.ts +16 -6
  88. package/src/theme-presets.ts +168 -0
  89. package/src/theme.ts +1 -3
  90. package/src/view-diff.ts +15 -4
  91. package/src/view-plan.test.ts +40 -6
  92. package/src/view-plan.ts +71 -28
  93. package/src/walk.test.ts +7 -2
  94. package/src/walk.ts +7 -2
@@ -2,14 +2,14 @@
2
2
  * The pending-review inbox: a cursor-driven session list. Selection stays with
3
3
  * the keyboard grammar; the list only renders the snapshot. The selected row
4
4
  * carries a [delete] word-button (click, or `d` in the grammar) that asks the
5
- * caller to confirm removal.
5
+ * caller to confirm removal. App supplies the surrounding header and MenuBar
6
+ * chrome (the same as plan/diff review); this is the flex-growing body.
6
7
  */
7
8
 
8
9
  import React from "react";
9
10
  import type { ReviewSession } from "@cueloop/schema";
10
11
  import type { Theme } from "../theme";
11
12
  import { useComponentTheme } from "./theme-context";
12
- import { StatusBar } from "./primitives/StatusBar";
13
13
  import { Button } from "./primitives/Button";
14
14
 
15
15
  export interface InboxListProps {
@@ -20,12 +20,15 @@ export interface InboxListProps {
20
20
  theme?: Theme;
21
21
  }
22
22
 
23
- export function InboxList({ inbox, cursor, onRequestDelete, theme }: InboxListProps): React.ReactNode {
23
+ export function InboxList({
24
+ inbox,
25
+ cursor,
26
+ onRequestDelete,
27
+ theme,
28
+ }: InboxListProps): React.ReactNode {
24
29
  const tokens = useComponentTheme(theme);
25
30
  return (
26
- <box style={{ flexDirection: "column", width: "100%", height: "100%", backgroundColor: tokens.background, padding: 1 }}>
27
- <text fg={tokens.accent}>cueloop · inbox ({inbox.length} pending)</text>
28
- <text> </text>
31
+ <box style={{ flexGrow: 1, flexDirection: "column", paddingLeft: 1, paddingTop: 1 }}>
29
32
  {inbox.length === 0 ? (
30
33
  <text fg={tokens.textDim}>nothing waiting for review</text>
31
34
  ) : (
@@ -33,9 +36,15 @@ export function InboxList({ inbox, cursor, onRequestDelete, theme }: InboxListPr
33
36
  const selected = index === cursor;
34
37
  const title = session.artifact.meta.title ?? session.id;
35
38
  return (
36
- <box key={session.id} style={{ flexDirection: "row", backgroundColor: selected ? tokens.cursorBackground : undefined }}>
39
+ <box
40
+ key={session.id}
41
+ style={{
42
+ flexDirection: "row",
43
+ backgroundColor: selected ? tokens.cursorBackground : undefined,
44
+ }}
45
+ >
37
46
  <text fg={selected ? tokens.text : tokens.textMuted}>
38
- {selected ? "▸ " : " "}
47
+ {selected ? "> " : " "}
39
48
  {title} · {session.workspace.branch} · {session.artifact.type}
40
49
  </text>
41
50
  <box style={{ flexGrow: 1 }} />
@@ -48,8 +57,6 @@ export function InboxList({ inbox, cursor, onRequestDelete, theme }: InboxListPr
48
57
  );
49
58
  })
50
59
  )}
51
- <box style={{ flexGrow: 1 }} />
52
- <StatusBar theme={theme}>j/k move · enter open · d delete · q quit</StatusBar>
53
60
  </box>
54
61
  );
55
62
  }
@@ -13,12 +13,28 @@ export interface KeybindsDialogProps {
13
13
  export function KeybindsDialog({ sections, theme }: KeybindsDialogProps): React.ReactNode {
14
14
  const tokens = useComponentTheme(theme);
15
15
  const { width: terminalWidth, height: terminalHeight } = useTerminalDimensions();
16
- const contentRows = sections.reduce((sum, section) => sum + 1 + section.entries.length + 1, 0) + 1;
16
+ const contentRows =
17
+ sections.reduce((sum, section) => sum + 1 + section.entries.length + 1, 0) + 1;
17
18
  const width = Math.min(52, terminalWidth - 6);
18
19
  const height = Math.min(terminalHeight - 4, contentRows + 3);
19
20
  return (
20
- <Dialog isOpen title=" keybinds " width={width} height={height} background={tokens.elevated} theme={theme}>
21
- <box style={{ flexDirection: "column", flexGrow: 1, paddingLeft: 1, paddingRight: 1, paddingTop: 1 }}>
21
+ <Dialog
22
+ isOpen
23
+ title=" keybinds "
24
+ width={width}
25
+ height={height}
26
+ background={tokens.elevated}
27
+ theme={theme}
28
+ >
29
+ <box
30
+ style={{
31
+ flexDirection: "column",
32
+ flexGrow: 1,
33
+ paddingLeft: 1,
34
+ paddingRight: 1,
35
+ paddingTop: 1,
36
+ }}
37
+ >
22
38
  {sections.map((section) => (
23
39
  <box key={section.title} style={{ flexDirection: "column" }}>
24
40
  <text fg={tokens.accent}>{section.title}</text>
@@ -0,0 +1,58 @@
1
+ import React from "react";
2
+ import { DARK } from "../theme";
3
+ import { DEFAULT_QUICK_ACTIONS } from "../config";
4
+ import type { Story, StoryMeta } from "./story";
5
+ import { MarkerPopover } from "./MarkerPopover";
6
+
7
+ export const meta: StoryMeta = { title: "MarkerPopover" };
8
+
9
+ const callbacks = {
10
+ onComment: () => {},
11
+ onCut: () => {},
12
+ onOpenActions: () => {},
13
+ onClose: () => {},
14
+ onPickAction: () => {},
15
+ onBack: () => {},
16
+ };
17
+
18
+ export const Toolbar: Story = {
19
+ render: () => (
20
+ <MarkerPopover
21
+ view="toolbar"
22
+ actions={DEFAULT_QUICK_ACTIONS}
23
+ actionIndex={0}
24
+ canCut
25
+ {...callbacks}
26
+ />
27
+ ),
28
+ expectedColors: [DARK.accent, DARK.red, DARK.textMuted, DARK.textDim],
29
+ size: { width: 60, height: 4 },
30
+ };
31
+
32
+ export const ToolbarNoCut: Story = {
33
+ render: () => (
34
+ <MarkerPopover
35
+ view="toolbar"
36
+ actions={DEFAULT_QUICK_ACTIONS}
37
+ actionIndex={0}
38
+ canCut={false}
39
+ {...callbacks}
40
+ />
41
+ ),
42
+ expectedColors: [DARK.accent, DARK.textMuted, DARK.textDim],
43
+ size: { width: 60, height: 4 },
44
+ };
45
+
46
+ export const ActionsList: Story = {
47
+ render: () => (
48
+ <MarkerPopover
49
+ view="actions"
50
+ actions={DEFAULT_QUICK_ACTIONS}
51
+ actionIndex={2}
52
+ canCut
53
+ {...callbacks}
54
+ />
55
+ ),
56
+ expectedColors: [DARK.accent, DARK.textMuted, DARK.textDim],
57
+ size: { width: 60, height: 8 },
58
+ };
@@ -0,0 +1,102 @@
1
+ /**
2
+ * The marker-actions popover: the visible form of plan span mode, floated as a
3
+ * bordered card above the marked block. The toolbar card is always shown -
4
+ * `comment · cut · actions · [x]`, each label keyboard-shortcut-backed and
5
+ * clickable. Pressing `actions` drops a second card below it listing the
6
+ * quick-actions; picking one inserts its preset comment. Plan-only: the diff
7
+ * review has no span.
8
+ */
9
+
10
+ import React from "react";
11
+ import type { Theme } from "../theme";
12
+ import type { QuickAction } from "../config";
13
+ import { useComponentTheme } from "./theme-context";
14
+ import { cardHeight } from "./primitives/Card";
15
+ import { FRAME_BORDER_STYLE } from "./primitives/frame";
16
+
17
+ export interface MarkerPopoverProps {
18
+ /** "toolbar" is the bar alone; "actions" also drops the quick-actions card. */
19
+ view: "toolbar" | "actions";
20
+ actions: QuickAction[];
21
+ /** Highlighted action in the dropdown (accent); ignored in toolbar view. */
22
+ actionIndex: number;
23
+ /** Show the cut affordance: owner-only, like every other plan-edit control. */
24
+ canCut: boolean;
25
+ onComment: () => void;
26
+ onCut: () => void;
27
+ onOpenActions: () => void;
28
+ onClose: () => void;
29
+ onPickAction: (index: number) => void;
30
+ onBack: () => void;
31
+ theme?: Theme;
32
+ }
33
+
34
+ export function MarkerPopover({
35
+ view,
36
+ actions,
37
+ actionIndex,
38
+ canCut,
39
+ onComment,
40
+ onCut,
41
+ onOpenActions,
42
+ onClose,
43
+ onPickAction,
44
+ onBack,
45
+ theme,
46
+ }: MarkerPopoverProps): React.ReactNode {
47
+ const tokens = useComponentTheme(theme);
48
+ const cardStyle = {
49
+ border: true,
50
+ borderStyle: FRAME_BORDER_STYLE,
51
+ borderColor: tokens.border,
52
+ backgroundColor: tokens.elevated,
53
+ flexDirection: "column" as const,
54
+ paddingLeft: 1,
55
+ paddingRight: 1,
56
+ };
57
+ const open = view === "actions";
58
+ return (
59
+ <box style={{ flexDirection: "column", alignItems: "flex-start" }}>
60
+ <box style={{ ...cardStyle, height: cardHeight(1) }}>
61
+ <box style={{ flexDirection: "row" }}>
62
+ <text fg={tokens.accent} onMouseUp={onComment}>
63
+ comment
64
+ </text>
65
+ <text fg={tokens.textDim}> · </text>
66
+ {canCut ? (
67
+ <>
68
+ <text fg={tokens.red} onMouseUp={onCut}>
69
+ cut
70
+ </text>
71
+ <text fg={tokens.textDim}> · </text>
72
+ </>
73
+ ) : null}
74
+ <text fg={open ? tokens.accent : tokens.textMuted} onMouseUp={onOpenActions}>
75
+ actions
76
+ </text>
77
+ <text fg={tokens.textDim}> · </text>
78
+ <text fg={tokens.textDim} onMouseUp={onClose}>
79
+ [x]
80
+ </text>
81
+ </box>
82
+ </box>
83
+ {open ? (
84
+ <box style={{ ...cardStyle, height: cardHeight(actions.length + 1) }}>
85
+ {actions.map((action, index) => (
86
+ <text
87
+ key={index}
88
+ fg={index === actionIndex ? tokens.accent : tokens.textMuted}
89
+ onMouseUp={() => onPickAction(index)}
90
+ >
91
+ {index === actionIndex ? "› " : " "}
92
+ {action.prompt}
93
+ </text>
94
+ ))}
95
+ <text fg={tokens.textDim} onMouseUp={onBack}>
96
+ {" "}‹ back
97
+ </text>
98
+ </box>
99
+ ) : null}
100
+ </box>
101
+ );
102
+ }
@@ -9,7 +9,11 @@ const callbacks = { onToggle: () => {}, onSettings: () => {}, onKeybinds: () =>
9
9
 
10
10
  function BottomFrame({ children }: { children: React.ReactNode }): React.ReactNode {
11
11
  return (
12
- <box style={{ flexDirection: "column", width: "100%", height: "100%", justifyContent: "flex-end" }}>{children}</box>
12
+ <box
13
+ style={{ flexDirection: "column", width: "100%", height: "100%", justifyContent: "flex-end" }}
14
+ >
15
+ {children}
16
+ </box>
13
17
  );
14
18
  }
15
19
 
@@ -17,7 +17,15 @@ export interface MenuBarProps {
17
17
 
18
18
  const DROPUP_OPTION_ROWS = 2;
19
19
 
20
- export function MenuBar({ open, version, status, onToggle, onSettings, onKeybinds, theme }: MenuBarProps): React.ReactNode {
20
+ export function MenuBar({
21
+ open,
22
+ version,
23
+ status,
24
+ onToggle,
25
+ onSettings,
26
+ onKeybinds,
27
+ theme,
28
+ }: MenuBarProps): React.ReactNode {
21
29
  const tokens = useComponentTheme(theme);
22
30
  const { height: terminalHeight } = useTerminalDimensions();
23
31
  const dropupHeight = DROPUP_OPTION_ROWS + 2;
@@ -45,7 +53,15 @@ export function MenuBar({ open, version, status, onToggle, onSettings, onKeybind
45
53
  </box>
46
54
  </box>
47
55
  ) : null}
48
- <box style={{ flexDirection: "row", height: 1, backgroundColor: tokens.panel, paddingLeft: 1, paddingRight: 1 }}>
56
+ <box
57
+ style={{
58
+ flexDirection: "row",
59
+ height: 1,
60
+ backgroundColor: tokens.panel,
61
+ paddingLeft: 1,
62
+ paddingRight: 1,
63
+ }}
64
+ >
49
65
  <box onMouseUp={onToggle}>
50
66
  <text fg={tokens.textDim}>menu</text>
51
67
  </box>
@@ -1,13 +1,17 @@
1
1
  import React from "react";
2
2
  import { DARK } from "../theme";
3
3
  import type { Story, StoryMeta } from "./story";
4
+ import { buildDisplay } from "../view-plan";
4
5
  import { PlanSheet } from "./PlanSheet";
5
- import { fixtureDisplay, fixtureMarks, fixturePlanSession } from "./story-fixtures";
6
+ import { FIXTURE_PLAN, fixtureDisplay, fixtureMarks, fixturePlanSession } from "./story-fixtures";
6
7
 
7
8
  export const meta: StoryMeta = { title: "PlanSheet" };
8
9
 
9
10
  const callbacks = { onLineActivate: () => {} };
10
11
 
12
+ // a working copy with the context paragraph cut, so it renders as a del block
13
+ const CUT_WORKING = FIXTURE_PLAN.replace("The daemon persists sessions to disk atomically.\n", "");
14
+
11
15
  export const AnnotatedPlan: Story = {
12
16
  render: () => (
13
17
  <PlanSheet
@@ -17,11 +21,12 @@ export const AnnotatedPlan: Story = {
17
21
  cursor={2}
18
22
  activeSpan={null}
19
23
  compose={null}
24
+ popover={null}
20
25
  editOrphanCount={0}
21
26
  {...callbacks}
22
27
  />
23
28
  ),
24
- expectedColors: [DARK.markCommentBackground, DARK.markSuggestionBackground, DARK.cursorBackground],
29
+ expectedColors: [DARK.markCommentBackground, DARK.cursorBackground],
25
30
  size: { width: 90, height: 28 },
26
31
  };
27
32
 
@@ -34,6 +39,7 @@ export const SpanSelection: Story = {
34
39
  cursor={2}
35
40
  activeSpan={{ displayIndex: 2, start: 0, end: 10 }}
36
41
  compose={null}
42
+ popover={null}
37
43
  editOrphanCount={0}
38
44
  {...callbacks}
39
45
  />
@@ -56,6 +62,7 @@ export const InlineCompose: Story = {
56
62
  quote: "The daemon",
57
63
  draft: { text: "Which daemon?", onInput: () => {}, onSave: () => {}, onCancel: () => {} },
58
64
  }}
65
+ popover={null}
59
66
  editOrphanCount={0}
60
67
  {...callbacks}
61
68
  />
@@ -63,6 +70,30 @@ export const InlineCompose: Story = {
63
70
  size: { width: 90, height: 30 },
64
71
  };
65
72
 
73
+ /** Span mode shows the marker-actions toolbar inline below the marked block. */
74
+ // The marker popover floats as an absolute overlay; the virtual terminal used
75
+ // for story snapshots cannot composite it, so it is covered by the standalone
76
+ // MarkerPopover stories and a functional App test rather than a PlanSheet snapshot.
77
+
78
+ /** A cut block renders struck-through and grayed inline - no red, no [cut] tag. */
79
+ export const WithCutBlock: Story = {
80
+ render: () => (
81
+ <PlanSheet
82
+ session={fixturePlanSession()}
83
+ display={buildDisplay(FIXTURE_PLAN, CUT_WORKING)}
84
+ marks={new Map()}
85
+ cursor={0}
86
+ activeSpan={null}
87
+ compose={null}
88
+ popover={null}
89
+ editOrphanCount={0}
90
+ {...callbacks}
91
+ />
92
+ ),
93
+ expectedColors: [DARK.textDim],
94
+ size: { width: 90, height: 28 },
95
+ };
96
+
66
97
  export const OrphanBanner: Story = {
67
98
  render: () => (
68
99
  <PlanSheet
@@ -72,6 +103,7 @@ export const OrphanBanner: Story = {
72
103
  cursor={0}
73
104
  activeSpan={null}
74
105
  compose={null}
106
+ popover={null}
75
107
  editOrphanCount={1}
76
108
  {...callbacks}
77
109
  />