@cueloop/client 0.1.0-alpha.65 → 0.1.0-alpha.67
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 +6 -5
- package/src/App.autoclose.test.tsx +2 -2
- package/src/App.diff.test.tsx +79 -112
- package/src/App.plan-review.test.tsx +23 -205
- package/src/App.prototype.test.tsx +1 -8
- package/src/App.readonly.test.tsx +9 -27
- package/src/App.submit-confirm.test.tsx +19 -72
- package/src/App.test.tsx +136 -31
- package/src/App.tsx +491 -322
- package/src/App.walk.test.tsx +15 -16
- package/src/App.workbench.test.tsx +390 -0
- package/src/__snapshots__/view-plan.regression.test.ts.snap +1 -1
- package/src/annotation-palette.ts +61 -0
- package/src/app-screens.tsx +197 -77
- package/src/app-view-model.ts +9 -69
- package/src/components/AnnotationCard.stories.tsx +1 -1
- package/src/components/AnnotationCard.tsx +1 -1
- package/src/components/AnnotationCards.stories.tsx +87 -0
- package/src/components/AnnotationCards.tsx +303 -0
- package/src/components/AppHeader.stories.tsx +50 -0
- package/src/components/AppHeader.tsx +83 -0
- package/src/components/AppShell.stories.tsx +89 -0
- package/src/components/AppShell.tsx +239 -0
- package/src/components/AppShell.zoom.test.tsx +94 -0
- package/src/components/Breadcrumb.stories.tsx +1 -1
- package/src/components/ChangesColumn.stories.tsx +39 -0
- package/src/components/ChangesColumn.tsx +170 -0
- package/src/components/CodeBlock.stories.tsx +1 -1
- package/src/components/CompletionOverlay.stories.tsx +1 -1
- package/src/components/ConfirmCard.stories.tsx +1 -1
- package/src/components/ConfirmCard.tsx +3 -4
- package/src/components/ConfirmDialog.stories.tsx +1 -1
- package/src/components/DiffSheet.scroll.test.tsx +38 -55
- package/src/components/DiffSheet.split.test.tsx +140 -0
- package/src/components/DiffSheet.stories.tsx +56 -54
- package/src/components/DiffSheet.syntax.test.tsx +24 -7
- package/src/components/DiffSheet.tsx +607 -276
- package/src/components/DiscussionMarkerRail.stories.tsx +54 -0
- package/src/components/DiscussionMarkerRail.tsx +181 -0
- package/src/components/EditorGrid.stories.tsx +59 -0
- package/src/components/EditorGrid.tabs.test.tsx +105 -0
- package/src/components/EditorGrid.tsx +338 -0
- package/src/components/EditorGrid.zoom.test.tsx +68 -0
- package/src/components/FileContentsView.stories.tsx +16 -0
- package/src/components/FileContentsView.test.tsx +29 -0
- package/src/components/FileContentsView.tsx +78 -0
- package/src/components/InboxList.stories.tsx +23 -8
- package/src/components/InboxList.tsx +188 -33
- package/src/components/KeybindsDialog.stories.tsx +1 -1
- package/src/components/MarkerPopover.stories.tsx +1 -1
- package/src/components/MenuBar.stories.tsx +3 -12
- package/src/components/MenuBar.tsx +25 -56
- package/src/components/PaneGrid.stories.tsx +84 -0
- package/src/components/PaneGrid.tsx +128 -0
- package/src/components/PanelColumn.stories.tsx +25 -0
- package/src/components/PanelColumn.tsx +95 -0
- package/src/components/ProjectTreeView.stories.tsx +25 -0
- package/src/components/ProjectTreeView.test.tsx +32 -0
- package/src/components/ProjectTreeView.tsx +82 -0
- package/src/components/PromptDialog.stories.tsx +1 -1
- package/src/components/PromptDialog.tsx +8 -1
- package/src/components/PrototypeSheet.stories.tsx +1 -1
- package/src/components/RootOverlay.stories.tsx +44 -0
- package/src/components/RootOverlay.tsx +35 -0
- package/src/components/SettingsDialog.stories.tsx +7 -1
- package/src/components/SettingsDialog.tsx +114 -67
- package/src/components/SettingsRows.stories.tsx +1 -1
- package/src/components/ShellHeader.stories.tsx +50 -0
- package/src/components/ShellHeader.tsx +137 -0
- package/src/components/SurfaceScrollbar.stories.tsx +34 -0
- package/src/components/SurfaceScrollbar.test.tsx +111 -0
- package/src/components/SurfaceScrollbar.tsx +93 -0
- package/src/components/ThreadFooter.stories.tsx +30 -0
- package/src/components/ThreadFooter.test.tsx +31 -0
- package/src/components/ThreadFooter.tsx +54 -0
- package/src/components/ThreadView.slash.test.tsx +113 -0
- package/src/components/ThreadView.stories.tsx +1 -1
- package/src/components/ThreadView.tsx +62 -1352
- package/src/components/ThreadsSidebar.stories.tsx +36 -0
- package/src/components/ThreadsSidebar.tsx +70 -0
- package/src/components/Toast.stories.tsx +1 -1
- package/src/components/Tooltip.stories.tsx +26 -0
- package/src/components/Tooltip.tsx +72 -0
- package/src/components/TreePane.stories.tsx +1 -1
- package/src/components/WalkWizard.stories.tsx +1 -1
- package/src/components/WelcomeSurface.stories.tsx +12 -0
- package/src/components/WelcomeSurface.tsx +52 -0
- package/src/components/__snapshots__/stories.test.tsx.snap +732 -572
- package/src/components/agent-launcher.stories.tsx +1 -1
- package/src/components/diff-sheet-layout.test.ts +125 -55
- package/src/components/diff-sheet-layout.ts +3 -103
- package/src/components/editor-grid.test.ts +86 -0
- package/src/components/editor-grid.ts +138 -0
- package/src/components/fade-title.test.ts +49 -0
- package/src/components/fade-title.ts +61 -0
- package/src/components/file-tree.test.ts +60 -0
- package/src/components/file-tree.ts +112 -0
- package/src/components/pages/Workspace.stories.tsx +181 -0
- package/src/components/primitives/Button.stories.tsx +1 -1
- package/src/components/primitives/Card.stories.tsx +1 -1
- package/src/components/primitives/Dialog.stories.tsx +1 -1
- package/src/components/primitives/IconButton.stories.tsx +36 -0
- package/src/components/primitives/IconButton.tsx +62 -0
- package/src/components/primitives/StatusBar.stories.tsx +1 -1
- package/src/components/primitives/Tabs.stories.tsx +1 -1
- package/src/components/primitives/Toolbar.stories.tsx +1 -1
- package/src/components/primitives/Tree.stories.tsx +124 -0
- package/src/components/primitives/Tree.tsx +96 -0
- package/src/components/primitives/icons.ts +61 -0
- package/src/components/primitives/tree-model.test.ts +99 -0
- package/src/components/primitives/tree-model.ts +137 -0
- package/src/components/quick-actions-editor.stories.tsx +1 -1
- package/src/components/session-tree.test.ts +134 -0
- package/src/components/session-tree.ts +108 -0
- package/src/components/stories-app.tsx +142 -82
- package/src/components/story-tree.test.ts +100 -0
- package/src/components/story.ts +55 -0
- package/src/components/theme-context.stories.tsx +1 -1
- package/src/config.test.ts +20 -49
- package/src/config.ts +26 -28
- package/src/diff-fold.test.ts +78 -0
- package/src/diff-fold.ts +126 -0
- package/src/diff-syntax.test.ts +16 -0
- package/src/diff-syntax.ts +6 -3
- package/src/discussions.ts +79 -0
- package/src/intent-dispatch.test.ts +31 -11
- package/src/intent-dispatch.ts +93 -48
- package/src/key-bindings.test.ts +1 -1
- package/src/keymap.test.ts +3 -25
- package/src/keymap.ts +17 -18
- package/src/mark-runs.ts +120 -0
- package/src/pty.ts +10 -2
- package/src/run.ts +17 -7
- package/src/serve.test.ts +4 -8
- package/src/session-controller.curate.test.ts +75 -0
- package/src/session-controller.share.test.ts +1 -0
- package/src/session-controller.tree.test.ts +26 -0
- package/src/session-controller.ts +160 -31
- package/src/slash-palette.ts +83 -0
- package/src/split-diff.test.ts +110 -0
- package/src/split-diff.ts +149 -0
- package/src/test-support.test.ts +3 -3
- package/src/test-support.ts +39 -8
- package/src/theme.ts +4 -4
- package/src/thread-chords.test.ts +25 -9
- package/src/thread-chords.ts +33 -8
- package/src/use-annotation-surface.tsx +837 -0
- package/src/use-changes-workbench.ts +151 -0
- package/src/use-frame-measure.ts +38 -0
- package/src/use-settings-dialog.tsx +23 -24
- package/src/use-terminal-virtualizer.ts +233 -0
- package/src/view-diff.ts +114 -0
- package/src/App.review-panel.test.tsx +0 -140
- package/src/App.tree.test.tsx +0 -224
- package/src/components/ReviewPanel.stories.tsx +0 -100
- package/src/components/ReviewPanel.tsx +0 -103
- package/src/components/ReviewRail.stories.tsx +0 -232
- package/src/components/ReviewRail.tsx +0 -327
- package/src/review-panel.test.ts +0 -113
- package/src/review-panel.ts +0 -83
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
// The one app shell: a single header row over four full-height panes - Threads, Thread, Changes,
|
|
2
|
+
// Project - divided by straight rules; each header cell's thin bottom rule shares the divider gray so
|
|
3
|
+
// the whole header underline reads as one line joining the side rules.
|
|
4
|
+
// The right region (Changes + Project) toggles as a unit: the Project pane is the right sidebar and is
|
|
5
|
+
// always present when the region is on, Changes rides on top of it, and a thin rail holds the sidebar
|
|
6
|
+
// toggle when the region is closed. Each pane owns its own header controls; the thread header never does.
|
|
7
|
+
|
|
8
|
+
import React from "react";
|
|
9
|
+
import { useTerminalDimensions } from "@opentui/react";
|
|
10
|
+
import { DARK, type Theme } from "../theme";
|
|
11
|
+
import { PanelColumn } from "./PanelColumn";
|
|
12
|
+
import { IconButton } from "./primitives/IconButton";
|
|
13
|
+
import { NERD, HEADER_UNDERLINE_CHARS } from "./primitives/icons";
|
|
14
|
+
import { TooltipProvider } from "./Tooltip";
|
|
15
|
+
import { RootOverlayProvider } from "./RootOverlay";
|
|
16
|
+
|
|
17
|
+
export type ProjectPanelMode = "changes" | "tree";
|
|
18
|
+
|
|
19
|
+
export interface AppShellProps {
|
|
20
|
+
sidebarOpen: boolean;
|
|
21
|
+
onToggleSidebar: () => void;
|
|
22
|
+
onOpenMenu: () => void;
|
|
23
|
+
threadsPanel: React.ReactNode;
|
|
24
|
+
/** The Thread header title; mirrors the selected sidebar thread, blank on a bare launch. */
|
|
25
|
+
threadTitle: string;
|
|
26
|
+
/** Owner actions at the right edge of the Thread header (Edit/Share) - the only thread-header controls. */
|
|
27
|
+
threadActions?: React.ReactNode;
|
|
28
|
+
threadPanel: React.ReactNode;
|
|
29
|
+
changesOpen: boolean;
|
|
30
|
+
projectOpen: boolean;
|
|
31
|
+
/** Toggle the Changes editor (also switches the Project tree to changed-files mode). */
|
|
32
|
+
onToggleChanges: () => void;
|
|
33
|
+
/** Toggle the Project tree to the full project view. */
|
|
34
|
+
onToggleProject: () => void;
|
|
35
|
+
/** Open or close the whole right region (the right sidebar). */
|
|
36
|
+
onToggleRight: () => void;
|
|
37
|
+
projectMode: ProjectPanelMode;
|
|
38
|
+
/** The Changes editor grid; rendered only when changesOpen. */
|
|
39
|
+
changesPanel?: React.ReactNode;
|
|
40
|
+
projectPanel: React.ReactNode;
|
|
41
|
+
/** Hide the Thread pane so Changes fills the middle (zoom); the sidebars stay. */
|
|
42
|
+
zoomHideThread?: boolean;
|
|
43
|
+
footer?: React.ReactNode;
|
|
44
|
+
children?: React.ReactNode;
|
|
45
|
+
theme?: Theme;
|
|
46
|
+
threadsWidth?: number;
|
|
47
|
+
projectWidth?: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function AppShell({
|
|
51
|
+
sidebarOpen,
|
|
52
|
+
onToggleSidebar,
|
|
53
|
+
onOpenMenu,
|
|
54
|
+
threadsPanel,
|
|
55
|
+
threadTitle,
|
|
56
|
+
threadActions,
|
|
57
|
+
threadPanel,
|
|
58
|
+
changesOpen,
|
|
59
|
+
projectOpen,
|
|
60
|
+
onToggleChanges,
|
|
61
|
+
onToggleProject,
|
|
62
|
+
onToggleRight,
|
|
63
|
+
projectMode,
|
|
64
|
+
changesPanel,
|
|
65
|
+
projectPanel,
|
|
66
|
+
zoomHideThread,
|
|
67
|
+
footer,
|
|
68
|
+
children,
|
|
69
|
+
theme,
|
|
70
|
+
threadsWidth = 30,
|
|
71
|
+
projectWidth = 32,
|
|
72
|
+
}: AppShellProps): React.ReactNode {
|
|
73
|
+
const tokens = theme ?? DARK;
|
|
74
|
+
const { width: terminalWidth } = useTerminalDimensions();
|
|
75
|
+
// Thread and Changes would otherwise split the middle as two flexBasis-0 items, and Yoga adds the
|
|
76
|
+
// Changes left border on top of its share: at even widths the halves come out fractional, the
|
|
77
|
+
// Changes side rounds up, and the row overflows a cell under the Project border. Sizing the Thread
|
|
78
|
+
// pane to a whole number leaves Changes the lone flex item, which lays out exactly.
|
|
79
|
+
const collapsedRailWidth = 4;
|
|
80
|
+
const middleWidth =
|
|
81
|
+
terminalWidth -
|
|
82
|
+
(sidebarOpen ? threadsWidth : 0) -
|
|
83
|
+
(projectOpen ? projectWidth : collapsedRailWidth);
|
|
84
|
+
const threadPaneWidth =
|
|
85
|
+
changesOpen && !zoomHideThread ? Math.max(20, Math.floor(middleWidth / 2)) : undefined;
|
|
86
|
+
|
|
87
|
+
// gear + sidebar toggle + product mark: global chrome, in the Threads header when open, else at the
|
|
88
|
+
// left of the Thread header when the Threads pane is collapsed
|
|
89
|
+
const brandChrome = (
|
|
90
|
+
<box style={{ flexDirection: "row", flexShrink: 0 }}>
|
|
91
|
+
<box onMouseUp={onOpenMenu} style={{ paddingRight: 2 }}>
|
|
92
|
+
<text fg={tokens.textMuted}>{NERD.settings}</text>
|
|
93
|
+
</box>
|
|
94
|
+
<IconButton
|
|
95
|
+
glyph={sidebarOpen ? NERD.sidebarLeft : NERD.sidebarLeftOff}
|
|
96
|
+
onPress={onToggleSidebar}
|
|
97
|
+
tip="Toggle Threads"
|
|
98
|
+
marginRight={2}
|
|
99
|
+
theme={tokens}
|
|
100
|
+
/>
|
|
101
|
+
<text fg={tokens.accent}>cueloop</text>
|
|
102
|
+
</box>
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const projectToggles = (
|
|
106
|
+
<box style={{ flexDirection: "row", flexShrink: 0, alignItems: "center" }}>
|
|
107
|
+
<IconButton
|
|
108
|
+
glyph="changes"
|
|
109
|
+
active={projectMode === "changes"}
|
|
110
|
+
onPress={onToggleChanges}
|
|
111
|
+
marginRight={2}
|
|
112
|
+
theme={tokens}
|
|
113
|
+
/>
|
|
114
|
+
<IconButton
|
|
115
|
+
glyph="project"
|
|
116
|
+
active={projectMode === "tree"}
|
|
117
|
+
onPress={onToggleProject}
|
|
118
|
+
marginRight={2}
|
|
119
|
+
theme={tokens}
|
|
120
|
+
/>
|
|
121
|
+
<IconButton
|
|
122
|
+
glyph={NERD.sidebarRight}
|
|
123
|
+
onPress={onToggleRight}
|
|
124
|
+
tip="Toggle Right Sidebar"
|
|
125
|
+
theme={tokens}
|
|
126
|
+
/>
|
|
127
|
+
</box>
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<box
|
|
132
|
+
style={{
|
|
133
|
+
flexDirection: "column",
|
|
134
|
+
width: "100%",
|
|
135
|
+
height: "100%",
|
|
136
|
+
backgroundColor: tokens.background,
|
|
137
|
+
}}
|
|
138
|
+
>
|
|
139
|
+
<RootOverlayProvider>
|
|
140
|
+
<TooltipProvider theme={tokens}>
|
|
141
|
+
<box style={{ flexGrow: 1, flexDirection: "row" }}>
|
|
142
|
+
{sidebarOpen ? (
|
|
143
|
+
<PanelColumn width={threadsWidth} border="right" header={brandChrome} theme={tokens}>
|
|
144
|
+
{threadsPanel}
|
|
145
|
+
</PanelColumn>
|
|
146
|
+
) : null}
|
|
147
|
+
{!zoomHideThread ? (
|
|
148
|
+
<PanelColumn
|
|
149
|
+
width={threadPaneWidth}
|
|
150
|
+
header={
|
|
151
|
+
<box style={{ flexDirection: "row" }}>
|
|
152
|
+
{!sidebarOpen ? <box style={{ paddingRight: 2 }}>{brandChrome}</box> : null}
|
|
153
|
+
{threadTitle ? <text fg={tokens.textDim}>{threadTitle}</text> : null}
|
|
154
|
+
</box>
|
|
155
|
+
}
|
|
156
|
+
headerRight={threadActions}
|
|
157
|
+
theme={tokens}
|
|
158
|
+
>
|
|
159
|
+
{threadPanel}
|
|
160
|
+
</PanelColumn>
|
|
161
|
+
) : null}
|
|
162
|
+
{changesOpen ? (
|
|
163
|
+
<box
|
|
164
|
+
style={{
|
|
165
|
+
flexDirection: "column",
|
|
166
|
+
flexGrow: 1,
|
|
167
|
+
flexBasis: 0,
|
|
168
|
+
minWidth: 0,
|
|
169
|
+
borderStyle: "single",
|
|
170
|
+
// zoom drops the Thread pane, whose sidebar rule already divides here, so drop this
|
|
171
|
+
// one then to avoid a double border
|
|
172
|
+
border: zoomHideThread ? [] : ["left"],
|
|
173
|
+
borderColor: tokens.border,
|
|
174
|
+
}}
|
|
175
|
+
>
|
|
176
|
+
{changesPanel}
|
|
177
|
+
</box>
|
|
178
|
+
) : null}
|
|
179
|
+
{projectOpen ? (
|
|
180
|
+
<PanelColumn
|
|
181
|
+
width={projectWidth}
|
|
182
|
+
border="left"
|
|
183
|
+
header={null}
|
|
184
|
+
headerRight={projectToggles}
|
|
185
|
+
theme={tokens}
|
|
186
|
+
>
|
|
187
|
+
{projectPanel}
|
|
188
|
+
</PanelColumn>
|
|
189
|
+
) : (
|
|
190
|
+
// collapsed: a divider tick above the continuous header underline holds the reopen toggle;
|
|
191
|
+
// the rule lives in the header only, never running the pane's full height. The extra column
|
|
192
|
+
// of right padding keeps the reopen icon off the terminal's last column, which squeezes it
|
|
193
|
+
<box style={{ flexDirection: "column", width: 4 }}>
|
|
194
|
+
<box
|
|
195
|
+
customBorderChars={HEADER_UNDERLINE_CHARS}
|
|
196
|
+
style={{
|
|
197
|
+
flexDirection: "row",
|
|
198
|
+
alignItems: "center",
|
|
199
|
+
height: 2,
|
|
200
|
+
paddingRight: 1,
|
|
201
|
+
backgroundColor: tokens.panel,
|
|
202
|
+
borderStyle: "single",
|
|
203
|
+
border: ["bottom"],
|
|
204
|
+
borderColor: tokens.border,
|
|
205
|
+
}}
|
|
206
|
+
>
|
|
207
|
+
<text fg={tokens.border}>{"│"}</text>
|
|
208
|
+
<IconButton
|
|
209
|
+
glyph={NERD.sidebarRightOff}
|
|
210
|
+
onPress={onToggleRight}
|
|
211
|
+
tip="Toggle Right Sidebar"
|
|
212
|
+
marginLeft={1}
|
|
213
|
+
theme={tokens}
|
|
214
|
+
/>
|
|
215
|
+
</box>
|
|
216
|
+
</box>
|
|
217
|
+
)}
|
|
218
|
+
</box>
|
|
219
|
+
{footer !== undefined ? (
|
|
220
|
+
<box
|
|
221
|
+
style={{
|
|
222
|
+
flexDirection: "row",
|
|
223
|
+
height: 1,
|
|
224
|
+
paddingLeft: 1,
|
|
225
|
+
paddingRight: 1,
|
|
226
|
+
borderStyle: "single",
|
|
227
|
+
border: ["top"],
|
|
228
|
+
borderColor: tokens.border,
|
|
229
|
+
}}
|
|
230
|
+
>
|
|
231
|
+
{footer}
|
|
232
|
+
</box>
|
|
233
|
+
) : null}
|
|
234
|
+
{children}
|
|
235
|
+
</TooltipProvider>
|
|
236
|
+
</RootOverlayProvider>
|
|
237
|
+
</box>
|
|
238
|
+
);
|
|
239
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression: the Changes header controls keep their column when zoom hides the Thread
|
|
3
|
+
* pane. Thread and Changes used to split the middle as two flexBasis-0 items, and Yoga
|
|
4
|
+
* added the Changes left border on top of its share, so at even terminal widths the
|
|
5
|
+
* Changes pane overflowed a cell under the Project border and the zoom icon appeared to
|
|
6
|
+
* jump left on zoom. The shell now sizes the Thread pane to a whole number.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, expect, test } from "bun:test";
|
|
10
|
+
import { testRender } from "@opentui/react/test-utils";
|
|
11
|
+
import React from "react";
|
|
12
|
+
import { AppShell } from "./AppShell";
|
|
13
|
+
import { EditorGrid } from "./EditorGrid";
|
|
14
|
+
import { changesTab, makeGroup } from "./editor-grid";
|
|
15
|
+
import { NERD } from "./primitives/icons";
|
|
16
|
+
import { allowEventLoopUpdates } from "../test-support";
|
|
17
|
+
|
|
18
|
+
const grid = makeGroup([changesTab()]);
|
|
19
|
+
|
|
20
|
+
async function zoomIconColumn(
|
|
21
|
+
width: number,
|
|
22
|
+
zoomed: boolean,
|
|
23
|
+
sidebarOpen: boolean,
|
|
24
|
+
): Promise<number> {
|
|
25
|
+
const setup = await testRender(
|
|
26
|
+
<AppShell
|
|
27
|
+
sidebarOpen={sidebarOpen}
|
|
28
|
+
onToggleSidebar={() => {}}
|
|
29
|
+
onOpenMenu={() => {}}
|
|
30
|
+
threadsPanel={<text>threads</text>}
|
|
31
|
+
threadTitle="thread"
|
|
32
|
+
threadPanel={<text>body</text>}
|
|
33
|
+
changesOpen
|
|
34
|
+
projectOpen
|
|
35
|
+
onToggleChanges={() => {}}
|
|
36
|
+
onToggleProject={() => {}}
|
|
37
|
+
onToggleRight={() => {}}
|
|
38
|
+
projectMode="changes"
|
|
39
|
+
zoomHideThread={zoomed}
|
|
40
|
+
projectPanel={<text>project</text>}
|
|
41
|
+
changesPanel={
|
|
42
|
+
<EditorGrid
|
|
43
|
+
tree={grid}
|
|
44
|
+
focusedGroupId={grid.id}
|
|
45
|
+
onFocusGroup={() => {}}
|
|
46
|
+
onActivateTab={() => {}}
|
|
47
|
+
onCloseTab={() => {}}
|
|
48
|
+
onSplit={() => {}}
|
|
49
|
+
onZoom={() => {}}
|
|
50
|
+
zoomed={zoomed}
|
|
51
|
+
renderTab={() => <text>diff</text>}
|
|
52
|
+
/>
|
|
53
|
+
}
|
|
54
|
+
/>,
|
|
55
|
+
{ width, height: 8 },
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
allowEventLoopUpdates();
|
|
59
|
+
await setup.waitForVisualIdle();
|
|
60
|
+
const column = setup.captureCharFrame().split("\n")[0]!.indexOf(NERD.zoom);
|
|
61
|
+
|
|
62
|
+
setup.renderer.destroy();
|
|
63
|
+
|
|
64
|
+
return column;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
describe("zoom icon in the shell", () => {
|
|
68
|
+
// even widths are where the fractional split used to overflow; odd widths never did
|
|
69
|
+
for (const width of [160, 161]) {
|
|
70
|
+
for (const sidebarOpen of [false, true]) {
|
|
71
|
+
test(`stays in its column when zoom toggles (width ${width}, sidebar ${sidebarOpen ? "open" : "closed"})`, async () => {
|
|
72
|
+
// Act
|
|
73
|
+
const unzoomed = await zoomIconColumn(width, false, sidebarOpen);
|
|
74
|
+
const zoomed = await zoomIconColumn(width, true, sidebarOpen);
|
|
75
|
+
|
|
76
|
+
// Assert
|
|
77
|
+
expect(unzoomed).toBeGreaterThan(0);
|
|
78
|
+
expect(zoomed).toBe(unzoomed);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
test("the icon clears the Project border by its padding in both states", async () => {
|
|
84
|
+
// Act
|
|
85
|
+
const setupColumns = await Promise.all([
|
|
86
|
+
zoomIconColumn(160, false, false),
|
|
87
|
+
zoomIconColumn(160, true, false),
|
|
88
|
+
]);
|
|
89
|
+
|
|
90
|
+
// Assert - width 160, project pane 32: the border sits at 128 and the glyph one padding cell
|
|
91
|
+
// before it, the same inset the right-sidebar icon keeps from its own edge
|
|
92
|
+
for (const column of setupColumns) expect(128 - column).toBe(2);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -3,7 +3,7 @@ import { DARK } from "../theme";
|
|
|
3
3
|
import type { Story, StoryMeta } from "./story";
|
|
4
4
|
import { Breadcrumb } from "./Breadcrumb";
|
|
5
5
|
|
|
6
|
-
export const meta: StoryMeta = { title: "Breadcrumb" };
|
|
6
|
+
export const meta: StoryMeta = { title: "Chrome/Breadcrumb" };
|
|
7
7
|
|
|
8
8
|
export const SessionHeader: Story = {
|
|
9
9
|
render: () => (
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { DiffFileContents } from "@cueloop/schema";
|
|
3
|
+
import { DARK } from "../theme";
|
|
4
|
+
import type { Story, StoryMeta } from "./story";
|
|
5
|
+
import { ChangesColumn } from "./ChangesColumn";
|
|
6
|
+
|
|
7
|
+
export const meta: StoryMeta = { title: "Surfaces/ChangesColumn" };
|
|
8
|
+
|
|
9
|
+
function Frame({ children }: { children: React.ReactNode }): React.ReactNode {
|
|
10
|
+
return <box style={{ width: "100%", height: "100%", flexDirection: "row" }}>{children}</box>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const files: DiffFileContents[] = [
|
|
14
|
+
{ path: "packages/client/src/App.tsx", oldContents: "a", newContents: "b", status: "modified" },
|
|
15
|
+
{
|
|
16
|
+
path: "packages/client/src/components/ChangesColumn.tsx",
|
|
17
|
+
oldContents: "",
|
|
18
|
+
newContents: "c",
|
|
19
|
+
status: "added",
|
|
20
|
+
},
|
|
21
|
+
{ path: "packages/daemon/src/review.ts", oldContents: "d", newContents: "", status: "deleted" },
|
|
22
|
+
{ path: "README.md", oldContents: "e", newContents: "f", status: "modified" },
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
export const Changed: Story = {
|
|
26
|
+
render: () => (
|
|
27
|
+
<Frame>
|
|
28
|
+
<ChangesColumn
|
|
29
|
+
open
|
|
30
|
+
files={files}
|
|
31
|
+
selectedPath="packages/client/src/App.tsx"
|
|
32
|
+
onSelectFile={() => {}}
|
|
33
|
+
theme={DARK}
|
|
34
|
+
/>
|
|
35
|
+
</Frame>
|
|
36
|
+
),
|
|
37
|
+
expectedColors: [DARK.accent, DARK.border],
|
|
38
|
+
size: { width: 60, height: 12 },
|
|
39
|
+
};
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// The right-hand Changes column: the diff's changed files as a directory tree.
|
|
2
|
+
// A click on a file scrolls the diff to that file. Renders nothing when collapsed
|
|
3
|
+
// so the diff reclaims the width. Mirrors the left Threads column on the far side.
|
|
4
|
+
// Co-located here: the state hook that opens it by context and the header toggle.
|
|
5
|
+
|
|
6
|
+
import React, { useMemo, useState } from "react";
|
|
7
|
+
import type { DiffFileContents, ReviewSession } from "@cueloop/schema";
|
|
8
|
+
import type { Theme } from "../theme";
|
|
9
|
+
import type { DiffRow } from "../view-diff";
|
|
10
|
+
import { Tree } from "./primitives/Tree";
|
|
11
|
+
import { allFolderIds } from "./primitives/tree-model";
|
|
12
|
+
import { IconButton } from "./primitives/IconButton";
|
|
13
|
+
import { NERD } from "./primitives/icons";
|
|
14
|
+
import { buildFileTree } from "./file-tree";
|
|
15
|
+
|
|
16
|
+
export interface DiffColumnsState {
|
|
17
|
+
changesOpen: boolean;
|
|
18
|
+
toggleChanges: () => void;
|
|
19
|
+
/** The path under the diff cursor, highlighted in the Changes tree. */
|
|
20
|
+
currentFilePath?: string;
|
|
21
|
+
scrollToFile: (path: string) => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Owns the right Changes column for a diff: context opens it when you enter a
|
|
26
|
+
* diff thread, and a file click scrolls the sheet to that file's first row.
|
|
27
|
+
* Resets on session change via the guarded render-time pattern, no effect.
|
|
28
|
+
*/
|
|
29
|
+
export function useDiffColumns(params: {
|
|
30
|
+
session: ReviewSession | null;
|
|
31
|
+
rows: DiffRow[];
|
|
32
|
+
cursor: number;
|
|
33
|
+
setCursor: (index: number) => void;
|
|
34
|
+
}): DiffColumnsState {
|
|
35
|
+
const { session, rows, cursor, setCursor } = params;
|
|
36
|
+
const contextDefault = session?.artifact.type === "diff";
|
|
37
|
+
const [changesOpen, setChangesOpen] = useState(contextDefault);
|
|
38
|
+
const [seenSessionId, setSeenSessionId] = useState(session?.id);
|
|
39
|
+
|
|
40
|
+
// entering another thread lets context decide again; within a thread the manual toggle stays
|
|
41
|
+
if (session?.id !== seenSessionId) {
|
|
42
|
+
setSeenSessionId(session?.id);
|
|
43
|
+
setChangesOpen(contextDefault);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
changesOpen,
|
|
48
|
+
toggleChanges: () => setChangesOpen((open) => !open),
|
|
49
|
+
currentFilePath: rows[cursor]?.file,
|
|
50
|
+
scrollToFile: (path: string) => {
|
|
51
|
+
const fileRowIndex = rows.findIndex((row) => row.kind === "file" && row.file === path);
|
|
52
|
+
|
|
53
|
+
if (fileRowIndex >= 0) setCursor(fileRowIndex);
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The header toggle for the Changes column; shown only for a diff, where files exist to browse. */
|
|
59
|
+
export function DiffChangesToggle({
|
|
60
|
+
isDiff,
|
|
61
|
+
open,
|
|
62
|
+
onToggle,
|
|
63
|
+
theme,
|
|
64
|
+
}: {
|
|
65
|
+
isDiff: boolean;
|
|
66
|
+
open: boolean;
|
|
67
|
+
onToggle: () => void;
|
|
68
|
+
theme?: Theme;
|
|
69
|
+
}): React.ReactNode {
|
|
70
|
+
if (!isDiff) return null;
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<IconButton
|
|
74
|
+
glyph={open ? NERD.sidebarRight : NERD.sidebarRightOff}
|
|
75
|
+
onPress={onToggle}
|
|
76
|
+
marginLeft={2}
|
|
77
|
+
theme={theme}
|
|
78
|
+
/>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface ChangesColumnProps {
|
|
83
|
+
open: boolean;
|
|
84
|
+
files?: readonly DiffFileContents[];
|
|
85
|
+
/** The path whose diff the sheet is scrolled to, highlighted in the tree. */
|
|
86
|
+
selectedPath?: string;
|
|
87
|
+
onSelectFile: (path: string) => void;
|
|
88
|
+
/** Comments per file path, shown as a dot-and-count badge on each entry. */
|
|
89
|
+
commentCounts?: ReadonlyMap<string, number>;
|
|
90
|
+
width?: number;
|
|
91
|
+
theme?: Theme;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The changed-files tree on its own, with no surrounding column frame: the body
|
|
96
|
+
* a workbench Project pane renders. A file click scrolls the diff to that file.
|
|
97
|
+
*/
|
|
98
|
+
export function ChangesFileTree({
|
|
99
|
+
files = [],
|
|
100
|
+
selectedPath,
|
|
101
|
+
onSelectFile,
|
|
102
|
+
commentCounts,
|
|
103
|
+
theme,
|
|
104
|
+
}: Omit<ChangesColumnProps, "open" | "width">): React.ReactNode {
|
|
105
|
+
const nodes = useMemo(() => buildFileTree(files, commentCounts), [files, commentCounts]);
|
|
106
|
+
// every folder opens by default so each changed file is reachable; the user
|
|
107
|
+
// only ever names the folders they fold shut, so a new file set stays open
|
|
108
|
+
const [collapsedIds, setCollapsedIds] = useState<ReadonlySet<string>>(new Set());
|
|
109
|
+
const expandedIds = useMemo(() => {
|
|
110
|
+
const expanded = allFolderIds(nodes);
|
|
111
|
+
|
|
112
|
+
for (const id of collapsedIds) expanded.delete(id);
|
|
113
|
+
|
|
114
|
+
return expanded;
|
|
115
|
+
}, [nodes, collapsedIds]);
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<scrollbox style={{ flexGrow: 1 }} focused={false}>
|
|
119
|
+
<Tree
|
|
120
|
+
nodes={nodes}
|
|
121
|
+
expandedIds={expandedIds}
|
|
122
|
+
selectedId={selectedPath}
|
|
123
|
+
flattenEmptyDirectories
|
|
124
|
+
showStatus
|
|
125
|
+
onSelect={onSelectFile}
|
|
126
|
+
onToggle={(id) =>
|
|
127
|
+
setCollapsedIds((current) => {
|
|
128
|
+
const next = new Set(current);
|
|
129
|
+
|
|
130
|
+
if (next.has(id)) next.delete(id);
|
|
131
|
+
else next.add(id);
|
|
132
|
+
|
|
133
|
+
return next;
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
theme={theme}
|
|
137
|
+
/>
|
|
138
|
+
</scrollbox>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function ChangesColumn({
|
|
143
|
+
open,
|
|
144
|
+
files = [],
|
|
145
|
+
selectedPath,
|
|
146
|
+
onSelectFile,
|
|
147
|
+
width = 32,
|
|
148
|
+
theme,
|
|
149
|
+
}: ChangesColumnProps): React.ReactNode {
|
|
150
|
+
if (!open) return null;
|
|
151
|
+
|
|
152
|
+
return (
|
|
153
|
+
<box
|
|
154
|
+
style={{
|
|
155
|
+
width,
|
|
156
|
+
flexDirection: "column",
|
|
157
|
+
borderStyle: "single",
|
|
158
|
+
border: ["left"],
|
|
159
|
+
borderColor: theme?.border,
|
|
160
|
+
}}
|
|
161
|
+
>
|
|
162
|
+
<ChangesFileTree
|
|
163
|
+
files={files}
|
|
164
|
+
selectedPath={selectedPath}
|
|
165
|
+
onSelectFile={onSelectFile}
|
|
166
|
+
theme={theme}
|
|
167
|
+
/>
|
|
168
|
+
</box>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
@@ -3,7 +3,7 @@ import { DARK } from "../theme";
|
|
|
3
3
|
import type { Story, StoryMeta } from "./story";
|
|
4
4
|
import { CodeBlock } from "./CodeBlock";
|
|
5
5
|
|
|
6
|
-
export const meta: StoryMeta = { title: "CodeBlock" };
|
|
6
|
+
export const meta: StoryMeta = { title: "Primitives/CodeBlock" };
|
|
7
7
|
|
|
8
8
|
const SNIPPET = `export function gate(full: number) {
|
|
9
9
|
// threshold
|
|
@@ -3,7 +3,7 @@ import { DARK } from "../theme";
|
|
|
3
3
|
import type { Story, StoryMeta } from "./story";
|
|
4
4
|
import { CompletionOverlay } from "./CompletionOverlay";
|
|
5
5
|
|
|
6
|
-
export const meta: StoryMeta = { title: "CompletionOverlay" };
|
|
6
|
+
export const meta: StoryMeta = { title: "Overlays/CompletionOverlay" };
|
|
7
7
|
|
|
8
8
|
export const ApprovedPrompt: Story = {
|
|
9
9
|
render: () => <CompletionOverlay verdict="approve" completion={{ phase: "prompt" }} status="" />,
|
|
@@ -3,7 +3,7 @@ import { DARK } from "../theme";
|
|
|
3
3
|
import type { Story, StoryMeta } from "./story";
|
|
4
4
|
import { ConfirmCard } from "./ConfirmCard";
|
|
5
5
|
|
|
6
|
-
export const meta: StoryMeta = { title: "ConfirmCard" };
|
|
6
|
+
export const meta: StoryMeta = { title: "Cards/ConfirmCard" };
|
|
7
7
|
|
|
8
8
|
const callbacks = {
|
|
9
9
|
onInput: () => {},
|
|
@@ -98,11 +98,10 @@ export function ConfirmCard({
|
|
|
98
98
|
|
|
99
99
|
return (
|
|
100
100
|
<Card
|
|
101
|
-
title="
|
|
101
|
+
title=" send message "
|
|
102
102
|
contentRows={CONFIRM_CONTENT_ROWS + (viewedSummary !== undefined ? 2 : 0)}
|
|
103
103
|
borderColor={tokens.text}
|
|
104
|
-
backgroundColor=
|
|
105
|
-
marginRight={1}
|
|
104
|
+
backgroundColor={tokens.elevated}
|
|
106
105
|
theme={theme}
|
|
107
106
|
>
|
|
108
107
|
{viewedSummary !== undefined ? <text fg={tokens.textDim}>{viewedSummary}</text> : null}
|
|
@@ -118,7 +117,7 @@ export function ConfirmCard({
|
|
|
118
117
|
<box style={{ height: 1 }} />
|
|
119
118
|
<Toolbar>
|
|
120
119
|
<Button variant="solid" marginRight={2} onPress={onSubmit} theme={theme}>
|
|
121
|
-
{"
|
|
120
|
+
{" send message "}
|
|
122
121
|
</Button>
|
|
123
122
|
<Button onPress={onCancel} theme={theme}>
|
|
124
123
|
{" Cancel "}
|
|
@@ -3,7 +3,7 @@ import { DARK } from "../theme";
|
|
|
3
3
|
import type { Story, StoryMeta } from "./story";
|
|
4
4
|
import { ConfirmDialog } from "./ConfirmDialog";
|
|
5
5
|
|
|
6
|
-
export const meta: StoryMeta = { title: "ConfirmDialog" };
|
|
6
|
+
export const meta: StoryMeta = { title: "Overlays/ConfirmDialog" };
|
|
7
7
|
|
|
8
8
|
const callbacks = { onConfirm: () => {}, onCancel: () => {} };
|
|
9
9
|
|