@cueloop/client 0.1.0-alpha.64 → 0.1.0-alpha.66

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 (109) hide show
  1. package/package.json +5 -5
  2. package/src/App.autoclose.test.tsx +2 -2
  3. package/src/App.diff.test.tsx +8 -73
  4. package/src/App.export.test.tsx +2 -2
  5. package/src/App.plan-review.test.tsx +60 -412
  6. package/src/App.prototype.test.tsx +1 -8
  7. package/src/App.readonly.test.tsx +85 -62
  8. package/src/App.rendering.test.tsx +5 -6
  9. package/src/App.submit-confirm.test.tsx +32 -70
  10. package/src/App.test.tsx +136 -66
  11. package/src/App.toast.test.tsx +17 -8
  12. package/src/App.tsx +243 -284
  13. package/src/App.walk.test.tsx +1 -1
  14. package/src/app-screens.tsx +67 -52
  15. package/src/app-view-model.ts +6 -100
  16. package/src/components/AnnotationCard.stories.tsx +1 -1
  17. package/src/components/AnnotationCard.tsx +2 -2
  18. package/src/components/AppShell.stories.tsx +84 -0
  19. package/src/components/AppShell.tsx +127 -0
  20. package/src/components/Breadcrumb.stories.tsx +1 -1
  21. package/src/components/ChangesColumn.stories.tsx +39 -0
  22. package/src/components/ChangesColumn.tsx +147 -0
  23. package/src/components/CodeBlock.stories.tsx +1 -1
  24. package/src/components/CompletionOverlay.stories.tsx +1 -1
  25. package/src/components/ConfirmCard.stories.tsx +1 -1
  26. package/src/components/ConfirmCard.tsx +3 -4
  27. package/src/components/ConfirmDialog.stories.tsx +1 -1
  28. package/src/components/DiffSheet.stories.tsx +1 -1
  29. package/src/components/InboxList.stories.tsx +23 -8
  30. package/src/components/InboxList.tsx +45 -19
  31. package/src/components/KeybindsDialog.stories.tsx +1 -1
  32. package/src/components/KeybindsDialog.tsx +61 -17
  33. package/src/components/MarkerPopover.stories.tsx +1 -1
  34. package/src/components/MenuBar.stories.tsx +3 -12
  35. package/src/components/MenuBar.tsx +25 -56
  36. package/src/components/PromptDialog.stories.tsx +1 -1
  37. package/src/components/PrototypeSheet.stories.tsx +1 -1
  38. package/src/components/ReviewPanel.stories.tsx +15 -1
  39. package/src/components/ReviewRail.stories.tsx +15 -1
  40. package/src/components/ReviewRail.tsx +13 -6
  41. package/src/components/SettingsDialog.stories.tsx +7 -1
  42. package/src/components/SettingsDialog.tsx +112 -66
  43. package/src/components/SettingsRows.stories.tsx +1 -1
  44. package/src/components/ShellHeader.stories.tsx +50 -0
  45. package/src/components/ShellHeader.tsx +137 -0
  46. package/src/components/ThreadFooter.stories.tsx +30 -0
  47. package/src/components/ThreadFooter.tsx +49 -0
  48. package/src/components/ThreadView.selection.test.tsx +812 -0
  49. package/src/components/ThreadView.slash.test.tsx +113 -0
  50. package/src/components/ThreadView.stories.tsx +26 -0
  51. package/src/components/ThreadView.tsx +1708 -0
  52. package/src/components/ThreadsSidebar.stories.tsx +36 -0
  53. package/src/components/ThreadsSidebar.tsx +57 -0
  54. package/src/components/Toast.stories.tsx +1 -1
  55. package/src/components/TreePane.stories.tsx +106 -0
  56. package/src/components/TreePane.tsx +156 -0
  57. package/src/components/WalkWizard.stories.tsx +1 -1
  58. package/src/components/__snapshots__/stories.test.tsx.snap +463 -305
  59. package/src/components/agent-launcher.stories.tsx +1 -1
  60. package/src/components/file-tree.test.ts +45 -0
  61. package/src/components/file-tree.ts +57 -0
  62. package/src/components/pages/Workspace.stories.tsx +181 -0
  63. package/src/components/primitives/Button.stories.tsx +1 -1
  64. package/src/components/primitives/Card.stories.tsx +1 -1
  65. package/src/components/primitives/Dialog.stories.tsx +1 -1
  66. package/src/components/primitives/IconButton.stories.tsx +36 -0
  67. package/src/components/primitives/IconButton.tsx +43 -0
  68. package/src/components/primitives/StatusBar.stories.tsx +1 -1
  69. package/src/components/primitives/Tabs.stories.tsx +1 -1
  70. package/src/components/primitives/Toolbar.stories.tsx +1 -1
  71. package/src/components/primitives/Tree.stories.tsx +124 -0
  72. package/src/components/primitives/Tree.tsx +87 -0
  73. package/src/components/primitives/icons.ts +38 -0
  74. package/src/components/primitives/tree-model.test.ts +99 -0
  75. package/src/components/primitives/tree-model.ts +137 -0
  76. package/src/components/quick-actions-editor.stories.tsx +1 -1
  77. package/src/components/session-tree.test.ts +114 -0
  78. package/src/components/session-tree.ts +94 -0
  79. package/src/components/stories-app.tsx +142 -82
  80. package/src/components/story-tree.test.ts +100 -0
  81. package/src/components/story.ts +55 -0
  82. package/src/components/theme-context.stories.tsx +1 -1
  83. package/src/diff-hunk-curate.ts +4 -104
  84. package/src/index.ts +1 -0
  85. package/src/intent-dispatch.test.ts +136 -3
  86. package/src/intent-dispatch.ts +92 -24
  87. package/src/key-bindings.test.ts +1 -1
  88. package/src/keymap.test.ts +4 -4
  89. package/src/keymap.ts +14 -7
  90. package/src/session-controller.curate.test.ts +89 -2
  91. package/src/session-controller.share.test.ts +195 -55
  92. package/src/session-controller.tree.test.ts +228 -0
  93. package/src/session-controller.ts +331 -72
  94. package/src/share.ts +114 -16
  95. package/src/test-support.test.ts +193 -1
  96. package/src/test-support.ts +64 -0
  97. package/src/theme.ts +4 -4
  98. package/src/thread-chords.test.ts +154 -0
  99. package/src/thread-chords.ts +144 -0
  100. package/src/thread-selection.test.ts +210 -0
  101. package/src/thread-selection.ts +243 -0
  102. package/src/tree-view.test.ts +128 -0
  103. package/src/tree-view.ts +148 -0
  104. package/src/use-settings-dialog.tsx +9 -0
  105. package/src/view-plan.ts +29 -12
  106. package/src/App.review-panel.test.tsx +0 -143
  107. package/src/components/PlanSheet.stories.tsx +0 -113
  108. package/src/components/PlanSheet.tsx +0 -419
  109. package/src/components/plan-sheet-run-style.ts +0 -88
@@ -0,0 +1,36 @@
1
+ import React from "react";
2
+ import { DARK } from "../theme";
3
+ import type { Story, StoryMeta } from "./story";
4
+ import { ThreadsSidebar } from "./ThreadsSidebar";
5
+ import { groupInbox } from "./session-tree";
6
+ import { fixtureDiffSession, fixturePlanSession } from "./story-fixtures";
7
+
8
+ export const meta: StoryMeta = { title: "Surfaces/ThreadsSidebar" };
9
+
10
+ function Frame({ children }: { children: React.ReactNode }): React.ReactNode {
11
+ return <box style={{ width: "100%", height: "100%", flexDirection: "row" }}>{children}</box>;
12
+ }
13
+
14
+ const plan = fixturePlanSession();
15
+ const projectPlan = {
16
+ ...plan,
17
+ workspace: { ...plan.workspace, rootCommit: "root-1", remote: "git@github.com:acme/widget.git" },
18
+ };
19
+ const { rows, ordered } = groupInbox([projectPlan, fixtureDiffSession()]);
20
+
21
+ export const Open: Story = {
22
+ render: () => (
23
+ <Frame>
24
+ <ThreadsSidebar
25
+ open
26
+ rows={rows}
27
+ cursor={0}
28
+ activeId={ordered[0]!.id}
29
+ onSelect={() => {}}
30
+ theme={DARK}
31
+ />
32
+ </Frame>
33
+ ),
34
+ expectedColors: [DARK.accent, DARK.border],
35
+ size: { width: 60, height: 12 },
36
+ };
@@ -0,0 +1,57 @@
1
+ // The left Projects and Threads column: a collapsible, scrollable tree beside the
2
+ // thread. A click jumps to that thread; the open thread stays highlighted. Renders
3
+ // nothing when collapsed so the thread reclaims the width.
4
+
5
+ import React from "react";
6
+ import type { Theme } from "../theme";
7
+ import { InboxList } from "./InboxList";
8
+ import type { InboxRow } from "./session-tree";
9
+
10
+ export interface ThreadsSidebarProps {
11
+ open: boolean;
12
+ rows: InboxRow[];
13
+ cursor: number;
14
+ /** The open thread's id, highlighted in the list. */
15
+ activeId?: string;
16
+ onSelect: (sessionId: string) => void;
17
+ /** Ask to delete a thread (the selected row's [delete] button). */
18
+ onRequestDelete?: (id: string, title: string) => void;
19
+ width?: number;
20
+ theme?: Theme;
21
+ }
22
+
23
+ export function ThreadsSidebar({
24
+ open,
25
+ rows,
26
+ cursor,
27
+ activeId,
28
+ onSelect,
29
+ onRequestDelete,
30
+ width = 30,
31
+ theme,
32
+ }: ThreadsSidebarProps): React.ReactNode {
33
+ if (!open) return null;
34
+
35
+ return (
36
+ <box
37
+ style={{
38
+ width,
39
+ flexDirection: "column",
40
+ borderStyle: "single",
41
+ border: ["right"],
42
+ borderColor: theme?.border,
43
+ }}
44
+ >
45
+ <scrollbox style={{ flexGrow: 1 }} focused={false}>
46
+ <InboxList
47
+ rows={rows}
48
+ cursor={cursor}
49
+ activeId={activeId}
50
+ onSelect={onSelect}
51
+ onRequestDelete={onRequestDelete}
52
+ theme={theme}
53
+ />
54
+ </scrollbox>
55
+ </box>
56
+ );
57
+ }
@@ -3,7 +3,7 @@ import { DARK } from "../theme";
3
3
  import type { Story, StoryMeta } from "./story";
4
4
  import { Toast } from "./Toast";
5
5
 
6
- export const meta: StoryMeta = { title: "Toast" };
6
+ export const meta: StoryMeta = { title: "Overlays/Toast" };
7
7
 
8
8
  export const ShareLink: Story = {
9
9
  render: () => <Toast title="share link copied" body="ssh review-4f2a@cueloop.dev" />,
@@ -0,0 +1,106 @@
1
+ import React from "react";
2
+ import { DARK } from "../theme";
3
+ import type { Story, StoryMeta } from "./story";
4
+ import { TreePane } from "./TreePane";
5
+ import type { TreeRow } from "../tree-view";
6
+
7
+ export const meta: StoryMeta = { title: "Surfaces/TreePane" };
8
+
9
+ const callbacks = {
10
+ onSelect: () => {},
11
+ onGo: () => {},
12
+ onBranch: () => {},
13
+ onLabel: () => {},
14
+ onFork: () => {},
15
+ onForkAndShare: () => {},
16
+ };
17
+
18
+ /** rev1 -> comment (labelled, a branch hangs off it) -> the branch's comment -> rev2 as main's tip. */
19
+ const ROWS: TreeRow[] = [
20
+ {
21
+ entryId: "e1",
22
+ depth: 0,
23
+ glyph: "◉",
24
+ text: "revision 1",
25
+ onPath: true,
26
+ tips: [],
27
+ isCurrentTip: false,
28
+ },
29
+ {
30
+ entryId: "e2",
31
+ depth: 0,
32
+ glyph: "·",
33
+ text: "comment",
34
+ onPath: true,
35
+ tips: [],
36
+ isCurrentTip: false,
37
+ label: "start",
38
+ },
39
+ {
40
+ entryId: "e3",
41
+ depth: 1,
42
+ glyph: "·",
43
+ text: "comment",
44
+ onPath: false,
45
+ tips: ["alt"],
46
+ isCurrentTip: false,
47
+ },
48
+ {
49
+ entryId: "e4",
50
+ depth: 0,
51
+ glyph: "◉",
52
+ text: "revision 2",
53
+ onPath: true,
54
+ tips: ["main"],
55
+ isCurrentTip: true,
56
+ },
57
+ ];
58
+
59
+ /** The rail expects a height-constrained parent, like the app's main row. */
60
+ function PaneFrame({ children }: { children: React.ReactNode }): React.ReactNode {
61
+ return (
62
+ <box
63
+ style={{ flexDirection: "column", width: 40, height: "100%", backgroundColor: DARK.panel }}
64
+ >
65
+ {children}
66
+ </box>
67
+ );
68
+ }
69
+
70
+ export const OwnerWithBranch: Story = {
71
+ render: () => (
72
+ <PaneFrame>
73
+ <TreePane rows={ROWS} selectedEntryId="e2" canMove canFork {...callbacks} />
74
+ </PaneFrame>
75
+ ),
76
+ expectedColors: [DARK.accent],
77
+ size: { width: 40, height: 16 },
78
+ };
79
+
80
+ export const CollaboratorReadsOnly: Story = {
81
+ render: () => (
82
+ <PaneFrame>
83
+ <TreePane rows={ROWS} canMove={false} canFork={false} {...callbacks} />
84
+ </PaneFrame>
85
+ ),
86
+ size: { width: 40, height: 16 },
87
+ };
88
+
89
+ /** A resolved review: the owner can still fork, not move. */
90
+ export const ResolvedForkOnly: Story = {
91
+ render: () => (
92
+ <PaneFrame>
93
+ <TreePane rows={ROWS} canMove={false} canFork {...callbacks} />
94
+ </PaneFrame>
95
+ ),
96
+ size: { width: 40, height: 16 },
97
+ };
98
+
99
+ export const NoHistory: Story = {
100
+ render: () => (
101
+ <PaneFrame>
102
+ <TreePane rows={[]} canMove canFork {...callbacks} />
103
+ </PaneFrame>
104
+ ),
105
+ size: { width: 40, height: 8 },
106
+ };
@@ -0,0 +1,156 @@
1
+ /**
2
+ * The rail's Tree tab: the session's history as one row per entry. The active
3
+ * path reads bright, everything else dim; a branch tip carries its name, the
4
+ * current tip a marker, a checkpoint its label. Clicking a row selects it,
5
+ * clicking the selected row goes there; the buttons underneath are the mouse
6
+ * path to the same primitives the option chords reach.
7
+ */
8
+
9
+ import React, { useEffect, useRef } from "react";
10
+ import { createTextAttributes, type ScrollBoxRenderable } from "@opentui/core";
11
+ import type { Theme } from "../theme";
12
+ import type { TreeRow } from "../tree-view";
13
+ import { useComponentTheme } from "./theme-context";
14
+ import { Button } from "./primitives/Button";
15
+ import { Toolbar } from "./primitives/Toolbar";
16
+
17
+ export interface TreePaneProps {
18
+ rows: TreeRow[];
19
+ selectedEntryId?: string;
20
+ /** The owner of an open review may move the tree; everyone else reads it. */
21
+ canMove: boolean;
22
+ /** The owner may fork, a resolved review included. */
23
+ canFork: boolean;
24
+ onSelect: (entryId: string) => void;
25
+ onGo: (entryId: string) => void;
26
+ onBranch: () => void;
27
+ onLabel: () => void;
28
+ onFork: () => void;
29
+ onForkAndShare: () => void;
30
+ theme?: Theme;
31
+ }
32
+
33
+ const DIM = createTextAttributes({ dim: true });
34
+ const BOLD = createTextAttributes({ bold: true });
35
+
36
+ /** The marker column: the current tip stands out, every other row keeps its kind's glyph. */
37
+ function markerFor(row: TreeRow): string {
38
+ return row.isCurrentTip ? "●" : row.glyph;
39
+ }
40
+
41
+ /** What follows the entry's name: the branches whose tip it is, and its checkpoint. */
42
+ function trailerFor(row: TreeRow): string {
43
+ const parts: string[] = [];
44
+
45
+ if (row.tips.length) parts.push(`← ${row.tips.join(", ")}`);
46
+ if (row.label !== undefined) parts.push(`⚑ ${row.label}`);
47
+
48
+ return parts.length ? ` ${parts.join(" ")}` : "";
49
+ }
50
+
51
+ export function TreePane({
52
+ rows,
53
+ selectedEntryId,
54
+ canMove,
55
+ canFork,
56
+ onSelect,
57
+ onGo,
58
+ onBranch,
59
+ onLabel,
60
+ onFork,
61
+ onForkAndShare,
62
+ theme,
63
+ }: TreePaneProps): React.ReactNode {
64
+ const tokens = useComponentTheme(theme);
65
+ const scrollRef = useRef<ScrollBoxRenderable | null>(null);
66
+ const revealId = selectedEntryId ?? rows.find((row) => row.isCurrentTip)?.entryId;
67
+
68
+ // the trunk draws last, so a long history opens with its tip below the fold: keep the row in view
69
+ useEffect(() => {
70
+ if (revealId === undefined) return;
71
+ try {
72
+ scrollRef.current?.scrollChildIntoView(`tree-row-${revealId}`);
73
+ } catch {
74
+ // reveal is best-effort; the selection is already correct
75
+ }
76
+ }, [revealId]);
77
+
78
+ if (rows.length === 0)
79
+ return (
80
+ <box style={{ flexGrow: 1, alignItems: "center", justifyContent: "center" }}>
81
+ <text fg={tokens.textDim}>no history yet</text>
82
+ </box>
83
+ );
84
+
85
+ return (
86
+ <box style={{ flexGrow: 1, flexDirection: "column" }}>
87
+ <scrollbox ref={scrollRef} style={{ flexGrow: 1 }} focused={false}>
88
+ {rows.map((row) => {
89
+ const selected = row.entryId === selectedEntryId;
90
+ const foreground = row.onPath ? tokens.text : tokens.textDim;
91
+
92
+ return (
93
+ <box
94
+ key={row.entryId}
95
+ id={`tree-row-${row.entryId}`}
96
+ style={{
97
+ flexDirection: "row",
98
+ paddingLeft: 1 + row.depth * 2,
99
+ backgroundColor: selected ? tokens.elevated : undefined,
100
+ }}
101
+ onMouseUp={() => (selected && canMove ? onGo(row.entryId) : onSelect(row.entryId))}
102
+ >
103
+ <text
104
+ fg={row.isCurrentTip ? tokens.accent : foreground}
105
+ attributes={row.onPath ? undefined : DIM}
106
+ >
107
+ {markerFor(row)} {row.text}
108
+ </text>
109
+ <text fg={tokens.textMuted} attributes={row.label === undefined ? undefined : BOLD}>
110
+ {trailerFor(row)}
111
+ </text>
112
+ </box>
113
+ );
114
+ })}
115
+ </scrollbox>
116
+ {canMove || canFork ? (
117
+ <box style={{ paddingLeft: 1 }}>
118
+ <Toolbar>
119
+ {canMove ? (
120
+ <Button
121
+ onPress={() => {
122
+ if (selectedEntryId !== undefined) onGo(selectedEntryId);
123
+ }}
124
+ isDisabled={selectedEntryId === undefined}
125
+ marginRight={1}
126
+ theme={theme}
127
+ >
128
+ Go
129
+ </Button>
130
+ ) : null}
131
+ {canMove ? (
132
+ <Button onPress={onBranch} marginRight={1} theme={theme}>
133
+ Branch
134
+ </Button>
135
+ ) : null}
136
+ {canMove ? (
137
+ <Button onPress={onLabel} marginRight={1} theme={theme}>
138
+ Label
139
+ </Button>
140
+ ) : null}
141
+ {canFork ? (
142
+ <Button onPress={onFork} marginRight={1} theme={theme}>
143
+ Fork
144
+ </Button>
145
+ ) : null}
146
+ {canFork ? (
147
+ <Button onPress={onForkAndShare} theme={theme}>
148
+ Fork+share
149
+ </Button>
150
+ ) : null}
151
+ </Toolbar>
152
+ </box>
153
+ ) : null}
154
+ </box>
155
+ );
156
+ }
@@ -4,7 +4,7 @@ import type { WalkFile } from "../walk";
4
4
  import type { Story, StoryMeta } from "./story";
5
5
  import { WalkWizard } from "./WalkWizard";
6
6
 
7
- export const meta: StoryMeta = { title: "WalkWizard" };
7
+ export const meta: StoryMeta = { title: "Overlays/WalkWizard" };
8
8
 
9
9
  const FILES: WalkFile[] = [
10
10
  {