@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.
- package/package.json +5 -5
- package/src/App.autoclose.test.tsx +2 -2
- package/src/App.diff.test.tsx +8 -73
- package/src/App.export.test.tsx +2 -2
- package/src/App.plan-review.test.tsx +60 -412
- package/src/App.prototype.test.tsx +1 -8
- package/src/App.readonly.test.tsx +85 -62
- package/src/App.rendering.test.tsx +5 -6
- package/src/App.submit-confirm.test.tsx +32 -70
- package/src/App.test.tsx +136 -66
- package/src/App.toast.test.tsx +17 -8
- package/src/App.tsx +243 -284
- package/src/App.walk.test.tsx +1 -1
- package/src/app-screens.tsx +67 -52
- package/src/app-view-model.ts +6 -100
- package/src/components/AnnotationCard.stories.tsx +1 -1
- package/src/components/AnnotationCard.tsx +2 -2
- package/src/components/AppShell.stories.tsx +84 -0
- package/src/components/AppShell.tsx +127 -0
- package/src/components/Breadcrumb.stories.tsx +1 -1
- package/src/components/ChangesColumn.stories.tsx +39 -0
- package/src/components/ChangesColumn.tsx +147 -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.stories.tsx +1 -1
- package/src/components/InboxList.stories.tsx +23 -8
- package/src/components/InboxList.tsx +45 -19
- package/src/components/KeybindsDialog.stories.tsx +1 -1
- package/src/components/KeybindsDialog.tsx +61 -17
- 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/PromptDialog.stories.tsx +1 -1
- package/src/components/PrototypeSheet.stories.tsx +1 -1
- package/src/components/ReviewPanel.stories.tsx +15 -1
- package/src/components/ReviewRail.stories.tsx +15 -1
- package/src/components/ReviewRail.tsx +13 -6
- package/src/components/SettingsDialog.stories.tsx +7 -1
- package/src/components/SettingsDialog.tsx +112 -66
- 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/ThreadFooter.stories.tsx +30 -0
- package/src/components/ThreadFooter.tsx +49 -0
- package/src/components/ThreadView.selection.test.tsx +812 -0
- package/src/components/ThreadView.slash.test.tsx +113 -0
- package/src/components/ThreadView.stories.tsx +26 -0
- package/src/components/ThreadView.tsx +1708 -0
- package/src/components/ThreadsSidebar.stories.tsx +36 -0
- package/src/components/ThreadsSidebar.tsx +57 -0
- package/src/components/Toast.stories.tsx +1 -1
- package/src/components/TreePane.stories.tsx +106 -0
- package/src/components/TreePane.tsx +156 -0
- package/src/components/WalkWizard.stories.tsx +1 -1
- package/src/components/__snapshots__/stories.test.tsx.snap +463 -305
- package/src/components/agent-launcher.stories.tsx +1 -1
- package/src/components/file-tree.test.ts +45 -0
- package/src/components/file-tree.ts +57 -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 +43 -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 +87 -0
- package/src/components/primitives/icons.ts +38 -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 +114 -0
- package/src/components/session-tree.ts +94 -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/diff-hunk-curate.ts +4 -104
- package/src/index.ts +1 -0
- package/src/intent-dispatch.test.ts +136 -3
- package/src/intent-dispatch.ts +92 -24
- package/src/key-bindings.test.ts +1 -1
- package/src/keymap.test.ts +4 -4
- package/src/keymap.ts +14 -7
- package/src/session-controller.curate.test.ts +89 -2
- package/src/session-controller.share.test.ts +195 -55
- package/src/session-controller.tree.test.ts +228 -0
- package/src/session-controller.ts +331 -72
- package/src/share.ts +114 -16
- package/src/test-support.test.ts +193 -1
- package/src/test-support.ts +64 -0
- package/src/theme.ts +4 -4
- package/src/thread-chords.test.ts +154 -0
- package/src/thread-chords.ts +144 -0
- package/src/thread-selection.test.ts +210 -0
- package/src/thread-selection.ts +243 -0
- package/src/tree-view.test.ts +128 -0
- package/src/tree-view.ts +148 -0
- package/src/use-settings-dialog.tsx +9 -0
- package/src/view-plan.ts +29 -12
- package/src/App.review-panel.test.tsx +0 -143
- package/src/components/PlanSheet.stories.tsx +0 -113
- package/src/components/PlanSheet.tsx +0 -419
- package/src/components/plan-sheet-run-style.ts +0 -88
package/src/App.walk.test.tsx
CHANGED
|
@@ -157,7 +157,7 @@ describe("the guided walk", () => {
|
|
|
157
157
|
await press(setup, "enter");
|
|
158
158
|
|
|
159
159
|
// Assert
|
|
160
|
-
await waitForText(setup, "
|
|
160
|
+
await waitForText(setup, "send message");
|
|
161
161
|
expect(setup.captureCharFrame()).toContain("3/3 files viewed");
|
|
162
162
|
|
|
163
163
|
// Act
|
package/src/app-screens.tsx
CHANGED
|
@@ -2,7 +2,7 @@ import React, { type Dispatch, type SetStateAction } from "react";
|
|
|
2
2
|
import type { ReviewSession, VerdictKind } from "@cueloop/schema";
|
|
3
3
|
import { returnPaneFor } from "@cueloop/schema";
|
|
4
4
|
import type { Theme } from "./theme";
|
|
5
|
-
import type { Mode } from "./intent-dispatch";
|
|
5
|
+
import type { Mode, TreeAsk } from "./intent-dispatch";
|
|
6
6
|
import type { Intent } from "./keymap";
|
|
7
7
|
import type { ReviewController, ToastState } from "./session-controller";
|
|
8
8
|
import { noteForFile } from "./walk";
|
|
@@ -12,11 +12,11 @@ import type { SettingsCategory, SettingsValues } from "./components/SettingsDial
|
|
|
12
12
|
import type { SettingsNav } from "./use-settings-dialog";
|
|
13
13
|
import { CLIENT_VERSION } from "./version";
|
|
14
14
|
import { ThemeProvider } from "./components/theme-context";
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
15
|
+
import { NERD } from "./components/primitives/icons";
|
|
16
|
+
import type { InboxRow } from "./components/session-tree";
|
|
17
17
|
import { SettingsDialog } from "./components/SettingsDialog";
|
|
18
18
|
import { CompletionOverlay } from "./components/CompletionOverlay";
|
|
19
|
-
import {
|
|
19
|
+
import { ThreadsSidebar } from "./components/ThreadsSidebar";
|
|
20
20
|
import { ConfirmDialog } from "./components/ConfirmDialog";
|
|
21
21
|
import { PromptDialog } from "./components/PromptDialog";
|
|
22
22
|
import { WalkWizard } from "./components/WalkWizard";
|
|
@@ -39,12 +39,8 @@ export function ConnectingScreen({ theme }: { theme: Theme }): React.ReactNode {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
export function MenuChrome(props: {
|
|
42
|
-
menuOpen: boolean;
|
|
43
42
|
menuDialog: "keybinds" | "settings" | null;
|
|
44
|
-
status: string;
|
|
45
43
|
theme: Theme;
|
|
46
|
-
setMenuOpen: Dispatch<SetStateAction<boolean>>;
|
|
47
|
-
setMenuDialog: Dispatch<SetStateAction<"keybinds" | "settings" | null>>;
|
|
48
44
|
keybindsSections: CheatsheetSection[];
|
|
49
45
|
settingsCategories: SettingsCategory[];
|
|
50
46
|
settingsValues: SettingsValues;
|
|
@@ -53,12 +49,8 @@ export function MenuChrome(props: {
|
|
|
53
49
|
cycleSetting: (rowKey: string) => void;
|
|
54
50
|
}): React.ReactNode {
|
|
55
51
|
const {
|
|
56
|
-
menuOpen,
|
|
57
52
|
menuDialog,
|
|
58
|
-
status,
|
|
59
53
|
theme,
|
|
60
|
-
setMenuOpen,
|
|
61
|
-
setMenuDialog,
|
|
62
54
|
keybindsSections,
|
|
63
55
|
settingsCategories,
|
|
64
56
|
settingsValues,
|
|
@@ -67,53 +59,37 @@ export function MenuChrome(props: {
|
|
|
67
59
|
cycleSetting,
|
|
68
60
|
} = props;
|
|
69
61
|
|
|
62
|
+
// the gear opens the settings dialog directly; Keybinds is a leaf in its tree nav
|
|
63
|
+
if (menuDialog !== "settings") return null;
|
|
64
|
+
|
|
70
65
|
return (
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}}
|
|
85
|
-
theme={theme}
|
|
86
|
-
/>
|
|
87
|
-
{menuDialog === "keybinds" ? (
|
|
88
|
-
<KeybindsDialog sections={keybindsSections} theme={theme} />
|
|
89
|
-
) : null}
|
|
90
|
-
{menuDialog === "settings" ? (
|
|
91
|
-
<SettingsDialog
|
|
92
|
-
isOpen
|
|
93
|
-
categories={settingsCategories}
|
|
94
|
-
values={settingsValues}
|
|
95
|
-
activeCategoryId={settingsNav.categoryId}
|
|
96
|
-
activeRowIndex={settingsNav.rowIndex}
|
|
97
|
-
activeZone={settingsNav.zone}
|
|
98
|
-
onCategorySelect={onCategorySelect}
|
|
99
|
-
onRowActivate={(row) => cycleSetting(row.key)}
|
|
100
|
-
theme={theme}
|
|
101
|
-
/>
|
|
102
|
-
) : null}
|
|
103
|
-
</>
|
|
66
|
+
<SettingsDialog
|
|
67
|
+
isOpen
|
|
68
|
+
version={CLIENT_VERSION}
|
|
69
|
+
keybindsSections={keybindsSections}
|
|
70
|
+
categories={settingsCategories}
|
|
71
|
+
values={settingsValues}
|
|
72
|
+
activeCategoryId={settingsNav.categoryId}
|
|
73
|
+
activeRowIndex={settingsNav.rowIndex}
|
|
74
|
+
activeZone={settingsNav.zone}
|
|
75
|
+
onCategorySelect={onCategorySelect}
|
|
76
|
+
onRowActivate={(row) => cycleSetting(row.key)}
|
|
77
|
+
theme={theme}
|
|
78
|
+
/>
|
|
104
79
|
);
|
|
105
80
|
}
|
|
106
81
|
|
|
107
82
|
export function InboxScreen(props: {
|
|
108
|
-
|
|
83
|
+
rows: InboxRow[];
|
|
109
84
|
inboxCursor: number;
|
|
110
85
|
mode: Mode;
|
|
111
86
|
theme: Theme;
|
|
112
87
|
controller: ReviewController;
|
|
113
88
|
setMode: Dispatch<SetStateAction<Mode>>;
|
|
114
89
|
menuChrome: React.ReactNode;
|
|
90
|
+
onOpenMenu: () => void;
|
|
115
91
|
}): React.ReactNode {
|
|
116
|
-
const {
|
|
92
|
+
const { rows, inboxCursor, mode, theme, controller, setMode, menuChrome, onOpenMenu } = props;
|
|
117
93
|
const confirming = mode.type === "confirmDelete" ? mode : null;
|
|
118
94
|
|
|
119
95
|
return (
|
|
@@ -139,6 +115,9 @@ export function InboxScreen(props: {
|
|
|
139
115
|
flexDirection: "row",
|
|
140
116
|
}}
|
|
141
117
|
>
|
|
118
|
+
<box onMouseUp={onOpenMenu} style={{ paddingRight: 2 }}>
|
|
119
|
+
<text fg={theme.textMuted}>{NERD.settings}</text>
|
|
120
|
+
</box>
|
|
142
121
|
<text>
|
|
143
122
|
<span fg={theme.accent}>cueloop</span>
|
|
144
123
|
<span fg={theme.textDim}> · resume</span>
|
|
@@ -146,11 +125,21 @@ export function InboxScreen(props: {
|
|
|
146
125
|
</box>
|
|
147
126
|
<box style={{ flexGrow: 1 }} />
|
|
148
127
|
</box>
|
|
149
|
-
<
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
128
|
+
<box style={{ flexGrow: 1, flexDirection: "row" }}>
|
|
129
|
+
<ThreadsSidebar
|
|
130
|
+
open
|
|
131
|
+
rows={rows}
|
|
132
|
+
cursor={inboxCursor}
|
|
133
|
+
onSelect={(id) => controller.open(id)}
|
|
134
|
+
onRequestDelete={(id, title) =>
|
|
135
|
+
setMode({ type: "confirmDelete", sessionId: id, title })
|
|
136
|
+
}
|
|
137
|
+
theme={theme}
|
|
138
|
+
/>
|
|
139
|
+
<box style={{ flexGrow: 1, paddingLeft: 2, paddingTop: 1 }}>
|
|
140
|
+
<text fg={theme.textDim}>Select a thread</text>
|
|
141
|
+
</box>
|
|
142
|
+
</box>
|
|
154
143
|
{menuChrome}
|
|
155
144
|
<ConfirmDialog
|
|
156
145
|
isOpen={confirming !== null}
|
|
@@ -195,6 +184,21 @@ export function CompletionScreen(props: {
|
|
|
195
184
|
);
|
|
196
185
|
}
|
|
197
186
|
|
|
187
|
+
/** The words each tree prompt uses; enter with an empty summary moves back without one. */
|
|
188
|
+
const TREE_PROMPTS: Record<TreeAsk, { title: string; label: string; placeholder: string }> = {
|
|
189
|
+
branch: { title: " Branch ", label: "Name for the new branch:", placeholder: "alt" },
|
|
190
|
+
label: {
|
|
191
|
+
title: " Checkpoint ",
|
|
192
|
+
label: "Name for this checkpoint:",
|
|
193
|
+
placeholder: "before the rewrite",
|
|
194
|
+
},
|
|
195
|
+
navigate: {
|
|
196
|
+
title: " Move back ",
|
|
197
|
+
label: "Summary of what you leave behind (optional):",
|
|
198
|
+
placeholder: "tried a shorter plan",
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
|
|
198
202
|
export function TrailingOverlays(props: {
|
|
199
203
|
walking: boolean;
|
|
200
204
|
walk: { index: number } | null;
|
|
@@ -264,6 +268,17 @@ export function TrailingOverlays(props: {
|
|
|
264
268
|
theme={theme}
|
|
265
269
|
/>
|
|
266
270
|
) : null}
|
|
271
|
+
{mode.type === "treePrompt" ? (
|
|
272
|
+
<PromptDialog
|
|
273
|
+
isOpen
|
|
274
|
+
title={TREE_PROMPTS[mode.ask].title}
|
|
275
|
+
label={TREE_PROMPTS[mode.ask].label}
|
|
276
|
+
value={mode.text}
|
|
277
|
+
placeholder={TREE_PROMPTS[mode.ask].placeholder}
|
|
278
|
+
onInput={(text) => setMode({ ...mode, text })}
|
|
279
|
+
theme={theme}
|
|
280
|
+
/>
|
|
281
|
+
) : null}
|
|
267
282
|
{toast ? <Toast title={toast.title} body={toast.body} theme={theme} /> : null}
|
|
268
283
|
</>
|
|
269
284
|
);
|
package/src/app-view-model.ts
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import type { Dispatch, MutableRefObject, SetStateAction } from "react";
|
|
2
2
|
import type { ReviewSession, VerdictKind } from "@cueloop/schema";
|
|
3
|
-
import {
|
|
4
|
-
import { displayText, type DisplayBlock } from "./view-plan";
|
|
3
|
+
import { reviewerAnnotations, type Mode } from "./intent-dispatch";
|
|
5
4
|
import type { DiffRow } from "./view-diff";
|
|
6
5
|
import type { Intent, KeyState } from "./keymap";
|
|
7
|
-
import type { Completion
|
|
8
|
-
import type { QuickAction } from "./config";
|
|
6
|
+
import type { Completion } from "./session-controller";
|
|
9
7
|
import type { WalkFile } from "./walk";
|
|
10
8
|
import { viewedCount } from "./walk";
|
|
11
|
-
import type { PlanComposeState, PlanPopoverState } from "./components/PlanSheet";
|
|
12
9
|
import type { DiffComposeState } from "./components/DiffSheet";
|
|
13
10
|
import type { ConfirmCardProps } from "./components/ConfirmCard";
|
|
14
11
|
import type { RailCardEdit } from "./components/ReviewRail";
|
|
@@ -44,7 +41,8 @@ export function resolveOverlay(
|
|
|
44
41
|
if (mode.type === "compose" || mode.type === "railEdit") return "compose";
|
|
45
42
|
if (mode.type === "submit") return "submit";
|
|
46
43
|
if (mode.type === "confirmDelete") return "confirm";
|
|
47
|
-
if (mode.type === "rename" || mode.type === "nameSelf"
|
|
44
|
+
if (mode.type === "rename" || mode.type === "nameSelf" || mode.type === "treePrompt")
|
|
45
|
+
return "prompt";
|
|
48
46
|
if (mode.type === "spanActions") return "spanActions";
|
|
49
47
|
if (completionPhase === "prompt") return "completion-prompt";
|
|
50
48
|
if (completionPhase === "counting") return "completion-counting";
|
|
@@ -114,65 +112,26 @@ export function buildRenderFlags(params: {
|
|
|
114
112
|
isDiff: boolean;
|
|
115
113
|
isPrototype: boolean;
|
|
116
114
|
resolved: boolean;
|
|
117
|
-
menuOpen: boolean;
|
|
118
115
|
menuDialog: "keybinds" | "settings" | null;
|
|
119
116
|
resolvedIds: Set<string>;
|
|
120
117
|
}) {
|
|
121
|
-
const { session, isOwner, isDiff, isPrototype, resolved,
|
|
122
|
-
params;
|
|
118
|
+
const { session, isOwner, isDiff, isPrototype, resolved, menuDialog, resolvedIds } = params;
|
|
123
119
|
|
|
124
120
|
return {
|
|
125
121
|
showOwnerActions: isOwner && !isDiff && !resolved,
|
|
126
122
|
prototypeCanComment: isOwner && !resolved,
|
|
127
|
-
chromeHidden:
|
|
123
|
+
chromeHidden: menuDialog !== null,
|
|
128
124
|
prototypePath: session.artifact.meta.prototypePath ?? "",
|
|
129
125
|
railResolvedIds: isDiff || isPrototype ? null : resolvedIds,
|
|
130
126
|
};
|
|
131
127
|
}
|
|
132
128
|
|
|
133
|
-
export function buildActiveSpan(
|
|
134
|
-
mode: Mode,
|
|
135
|
-
isDiff: boolean,
|
|
136
|
-
): { displayIndex: number; start: number; end: number } | null {
|
|
137
|
-
const markedSpan = activeSpanState(mode);
|
|
138
|
-
|
|
139
|
-
if (markedSpan)
|
|
140
|
-
return { displayIndex: markedSpan.displayIndex, start: markedSpan.start, end: markedSpan.end };
|
|
141
|
-
if (mode.type === "compose" && !isDiff)
|
|
142
|
-
return { displayIndex: mode.displayIndex, start: mode.start, end: mode.end };
|
|
143
|
-
|
|
144
|
-
return null;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
129
|
interface DraftHandlerDeps {
|
|
148
130
|
liveInput: MutableRefObject<string>;
|
|
149
131
|
setMode: Dispatch<SetStateAction<Mode>>;
|
|
150
132
|
dispatch: (intent: Intent) => void;
|
|
151
133
|
}
|
|
152
134
|
|
|
153
|
-
export function buildComposeState(
|
|
154
|
-
deps: DraftHandlerDeps & { mode: Mode; isDiff: boolean; display: DisplayBlock[] },
|
|
155
|
-
): PlanComposeState | null {
|
|
156
|
-
const { mode, isDiff, display, liveInput, setMode, dispatch } = deps;
|
|
157
|
-
|
|
158
|
-
if (mode.type !== "compose" || isDiff) return null;
|
|
159
|
-
|
|
160
|
-
return {
|
|
161
|
-
kind: mode.kind,
|
|
162
|
-
displayIndex: mode.displayIndex,
|
|
163
|
-
quote: displayText(display[mode.displayIndex]!).slice(mode.start, mode.end),
|
|
164
|
-
draft: {
|
|
165
|
-
text: mode.text,
|
|
166
|
-
onInput: (text: string) => {
|
|
167
|
-
liveInput.current = text;
|
|
168
|
-
setMode({ ...mode, text });
|
|
169
|
-
},
|
|
170
|
-
onSave: () => dispatch({ type: "saveCompose" }),
|
|
171
|
-
onCancel: () => dispatch({ type: "closeOverlay" }),
|
|
172
|
-
},
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
|
|
176
135
|
export function buildDiffComposeState(
|
|
177
136
|
deps: DraftHandlerDeps & { mode: Mode; isDiff: boolean; rows: DiffRow[] },
|
|
178
137
|
): DiffComposeState | null {
|
|
@@ -196,59 +155,6 @@ export function buildDiffComposeState(
|
|
|
196
155
|
};
|
|
197
156
|
}
|
|
198
157
|
|
|
199
|
-
export function buildPopoverState(deps: {
|
|
200
|
-
mode: Mode;
|
|
201
|
-
isDiff: boolean;
|
|
202
|
-
quickActions: QuickAction[];
|
|
203
|
-
isOwner: boolean;
|
|
204
|
-
observer: boolean;
|
|
205
|
-
resolved: boolean;
|
|
206
|
-
controller: ReviewController;
|
|
207
|
-
dispatch: (intent: Intent) => void;
|
|
208
|
-
}): PlanPopoverState | null {
|
|
209
|
-
const { mode, isDiff, quickActions, isOwner, observer, resolved, controller, dispatch } = deps;
|
|
210
|
-
const markedSpan = activeSpanState(mode);
|
|
211
|
-
|
|
212
|
-
if (!markedSpan || isDiff) return null;
|
|
213
|
-
const spanMutationBlock = (): string | null =>
|
|
214
|
-
observer ? "observer - read-only" : resolved ? "review submitted - read-only" : null;
|
|
215
|
-
|
|
216
|
-
return {
|
|
217
|
-
displayIndex: markedSpan.displayIndex,
|
|
218
|
-
view: mode.type === "spanActions" ? ("actions" as const) : ("toolbar" as const),
|
|
219
|
-
actions: quickActions,
|
|
220
|
-
actionIndex: mode.type === "spanActions" ? mode.index : 0,
|
|
221
|
-
canCut: isOwner,
|
|
222
|
-
onComment: () => {
|
|
223
|
-
const blocked = spanMutationBlock();
|
|
224
|
-
|
|
225
|
-
if (blocked) return controller.setStatus(blocked);
|
|
226
|
-
dispatch({ type: "openCompose", kind: "comment", from: "span" });
|
|
227
|
-
},
|
|
228
|
-
onCut: () => {
|
|
229
|
-
const blocked = spanMutationBlock();
|
|
230
|
-
|
|
231
|
-
if (blocked) return controller.setStatus(blocked);
|
|
232
|
-
if (!isOwner) return;
|
|
233
|
-
dispatch({ type: "spanCut" });
|
|
234
|
-
},
|
|
235
|
-
onOpenActions: () => {
|
|
236
|
-
const blocked = spanMutationBlock();
|
|
237
|
-
|
|
238
|
-
if (blocked) return controller.setStatus(blocked);
|
|
239
|
-
dispatch({ type: "openSpanActions" });
|
|
240
|
-
},
|
|
241
|
-
onClose: () => dispatch({ type: "closeOverlay" }),
|
|
242
|
-
onPickAction: (index: number) => {
|
|
243
|
-
const blocked = spanMutationBlock();
|
|
244
|
-
|
|
245
|
-
if (blocked) return controller.setStatus(blocked);
|
|
246
|
-
dispatch({ type: "pickSpanAction", index });
|
|
247
|
-
},
|
|
248
|
-
onBack: () => dispatch({ type: "closeSpanActions" }),
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
|
|
252
158
|
export function buildSubmitConfirmState(
|
|
253
159
|
deps: DraftHandlerDeps & {
|
|
254
160
|
mode: Mode;
|
|
@@ -3,7 +3,7 @@ import { DARK } from "../theme";
|
|
|
3
3
|
import type { Story, StoryMeta } from "./story";
|
|
4
4
|
import { AnnotationCard } from "./AnnotationCard";
|
|
5
5
|
|
|
6
|
-
export const meta: StoryMeta = { title: "AnnotationCard" };
|
|
6
|
+
export const meta: StoryMeta = { title: "Cards/AnnotationCard" };
|
|
7
7
|
|
|
8
8
|
const draftCallbacks = { onInput: () => {}, onSave: () => {}, onCancel: () => {} };
|
|
9
9
|
|
|
@@ -181,11 +181,11 @@ export function AnnotationCard({
|
|
|
181
181
|
);
|
|
182
182
|
|
|
183
183
|
if (draft) {
|
|
184
|
-
const
|
|
184
|
+
const primitive = "comment on";
|
|
185
185
|
|
|
186
186
|
return (
|
|
187
187
|
<Card
|
|
188
|
-
title={` ${
|
|
188
|
+
title={` ${primitive} "${truncateToSingleLine(quote, 40)}" `}
|
|
189
189
|
contentRows={editorRowCount + 1}
|
|
190
190
|
borderColor={kindColor}
|
|
191
191
|
backgroundColor="transparent"
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { DARK } from "../theme";
|
|
3
|
+
import type { Story, StoryMeta } from "./story";
|
|
4
|
+
import { AppShell } from "./AppShell";
|
|
5
|
+
|
|
6
|
+
export const meta: StoryMeta = { title: "Layout/AppShell" };
|
|
7
|
+
|
|
8
|
+
function Sidebar(): React.ReactNode {
|
|
9
|
+
return (
|
|
10
|
+
<box style={{ flexDirection: "column", paddingLeft: 1, paddingTop: 1 }}>
|
|
11
|
+
<text fg={DARK.textDim}>Projects</text>
|
|
12
|
+
<text fg={DARK.text}> cueloop</text>
|
|
13
|
+
<box style={{ backgroundColor: DARK.elevated }}>
|
|
14
|
+
<text fg={DARK.accent}> Read Cueloop Repository</text>
|
|
15
|
+
</box>
|
|
16
|
+
<text> </text>
|
|
17
|
+
<text fg={DARK.textDim}>Threads</text>
|
|
18
|
+
<text fg={DARK.textMuted}> Welcome to cueloop</text>
|
|
19
|
+
</box>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function Main(): React.ReactNode {
|
|
24
|
+
return (
|
|
25
|
+
<box style={{ flexDirection: "column", paddingLeft: 1, paddingTop: 1 }}>
|
|
26
|
+
<text fg={DARK.text}>read the repo</text>
|
|
27
|
+
<text> </text>
|
|
28
|
+
<text fg={DARK.textMuted}>Repository context loaded. main is clean.</text>
|
|
29
|
+
<text> </text>
|
|
30
|
+
<text fg={DARK.text}>Core architecture</text>
|
|
31
|
+
<text fg={DARK.textMuted}> - packages/schema is the dependency root</text>
|
|
32
|
+
<text fg={DARK.textMuted}> - packages/daemon owns session state</text>
|
|
33
|
+
</box>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function Inspector(): React.ReactNode {
|
|
38
|
+
return (
|
|
39
|
+
<box style={{ flexDirection: "column", paddingLeft: 1, paddingTop: 1 }}>
|
|
40
|
+
<text fg={DARK.text}>cueloop/main</text>
|
|
41
|
+
<text fg={DARK.textDim}> No changes</text>
|
|
42
|
+
</box>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function Header(): React.ReactNode {
|
|
47
|
+
return (
|
|
48
|
+
<box style={{ flexDirection: "row", paddingLeft: 1 }}>
|
|
49
|
+
<text fg={DARK.textMuted}>Read Cueloop Repository</text>
|
|
50
|
+
<box style={{ flexGrow: 1 }} />
|
|
51
|
+
<text fg={DARK.textDim}>markdown.test.ts</text>
|
|
52
|
+
</box>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function Footer(): React.ReactNode {
|
|
57
|
+
return (
|
|
58
|
+
<box style={{ flexDirection: "row", paddingLeft: 1 }}>
|
|
59
|
+
<text fg={DARK.textDim}>cueloop / main</text>
|
|
60
|
+
<box style={{ flexGrow: 1 }} />
|
|
61
|
+
<text fg={DARK.textDim}>Fable 5</text>
|
|
62
|
+
</box>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const ThreePane: Story = {
|
|
67
|
+
render: () => (
|
|
68
|
+
<AppShell
|
|
69
|
+
sidebar={<Sidebar />}
|
|
70
|
+
main={<Main />}
|
|
71
|
+
mainHeader={<Header />}
|
|
72
|
+
inspector={<Inspector />}
|
|
73
|
+
footer={<Footer />}
|
|
74
|
+
/>
|
|
75
|
+
),
|
|
76
|
+
expectedColors: [DARK.border, DARK.accent],
|
|
77
|
+
size: { width: 120, height: 28 },
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const SidebarAndMain: Story = {
|
|
81
|
+
render: () => <AppShell sidebar={<Sidebar />} main={<Main />} />,
|
|
82
|
+
expectedColors: [DARK.border],
|
|
83
|
+
size: { width: 90, height: 20 },
|
|
84
|
+
};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// App shell: a grid of pane columns. Each column stacks a header cell over its body; the vertical
|
|
2
|
+
// rules run full height (through the header), and each header cell's bottom rule joins into one
|
|
3
|
+
// horizontal line across the columns. Rules only - no enclosing frames.
|
|
4
|
+
|
|
5
|
+
import React from "react";
|
|
6
|
+
import { DARK, type Theme } from "../theme";
|
|
7
|
+
import type { BorderSides } from "@opentui/core";
|
|
8
|
+
|
|
9
|
+
export interface AppShellProps {
|
|
10
|
+
headerHeight?: number;
|
|
11
|
+
sidebar?: React.ReactNode;
|
|
12
|
+
sidebarHeader?: React.ReactNode;
|
|
13
|
+
sidebarWidth?: number;
|
|
14
|
+
main: React.ReactNode;
|
|
15
|
+
mainHeader?: React.ReactNode;
|
|
16
|
+
inspector?: React.ReactNode;
|
|
17
|
+
inspectorHeader?: React.ReactNode;
|
|
18
|
+
inspectorWidth?: number;
|
|
19
|
+
footer?: React.ReactNode;
|
|
20
|
+
theme?: Theme;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface PaneColumnProps {
|
|
24
|
+
width?: number;
|
|
25
|
+
grow?: boolean;
|
|
26
|
+
side?: BorderSides;
|
|
27
|
+
header: React.ReactNode;
|
|
28
|
+
body: React.ReactNode;
|
|
29
|
+
headerHeight: number;
|
|
30
|
+
theme: Theme;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function PaneColumn({
|
|
34
|
+
width,
|
|
35
|
+
grow,
|
|
36
|
+
side,
|
|
37
|
+
header,
|
|
38
|
+
body,
|
|
39
|
+
headerHeight,
|
|
40
|
+
theme,
|
|
41
|
+
}: PaneColumnProps): React.ReactNode {
|
|
42
|
+
return (
|
|
43
|
+
<box
|
|
44
|
+
style={{
|
|
45
|
+
flexDirection: "column",
|
|
46
|
+
...(grow ? { flexGrow: 1 } : { width }),
|
|
47
|
+
borderStyle: "single",
|
|
48
|
+
border: side ? [side] : [],
|
|
49
|
+
borderColor: theme.border,
|
|
50
|
+
}}
|
|
51
|
+
>
|
|
52
|
+
<box
|
|
53
|
+
style={{
|
|
54
|
+
height: headerHeight,
|
|
55
|
+
borderStyle: "single",
|
|
56
|
+
border: ["bottom"],
|
|
57
|
+
borderColor: theme.border,
|
|
58
|
+
}}
|
|
59
|
+
>
|
|
60
|
+
{header}
|
|
61
|
+
</box>
|
|
62
|
+
<box style={{ flexGrow: 1 }}>{body}</box>
|
|
63
|
+
</box>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function AppShell({
|
|
68
|
+
headerHeight = 3,
|
|
69
|
+
sidebar,
|
|
70
|
+
sidebarHeader,
|
|
71
|
+
sidebarWidth = 28,
|
|
72
|
+
main,
|
|
73
|
+
mainHeader,
|
|
74
|
+
inspector,
|
|
75
|
+
inspectorHeader,
|
|
76
|
+
inspectorWidth = 40,
|
|
77
|
+
footer,
|
|
78
|
+
theme,
|
|
79
|
+
}: AppShellProps): React.ReactNode {
|
|
80
|
+
const tokens = theme ?? DARK;
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<box
|
|
84
|
+
style={{
|
|
85
|
+
flexDirection: "column",
|
|
86
|
+
width: "100%",
|
|
87
|
+
height: "100%",
|
|
88
|
+
backgroundColor: tokens.background,
|
|
89
|
+
}}
|
|
90
|
+
>
|
|
91
|
+
<box style={{ flexDirection: "row", flexGrow: 1 }}>
|
|
92
|
+
{sidebar !== undefined ? (
|
|
93
|
+
<PaneColumn
|
|
94
|
+
width={sidebarWidth}
|
|
95
|
+
side="right"
|
|
96
|
+
header={sidebarHeader}
|
|
97
|
+
body={sidebar}
|
|
98
|
+
headerHeight={headerHeight}
|
|
99
|
+
theme={tokens}
|
|
100
|
+
/>
|
|
101
|
+
) : null}
|
|
102
|
+
<PaneColumn
|
|
103
|
+
grow
|
|
104
|
+
header={mainHeader}
|
|
105
|
+
body={main}
|
|
106
|
+
headerHeight={headerHeight}
|
|
107
|
+
theme={tokens}
|
|
108
|
+
/>
|
|
109
|
+
{inspector !== undefined ? (
|
|
110
|
+
<PaneColumn
|
|
111
|
+
width={inspectorWidth}
|
|
112
|
+
side="left"
|
|
113
|
+
header={inspectorHeader}
|
|
114
|
+
body={inspector}
|
|
115
|
+
headerHeight={headerHeight}
|
|
116
|
+
theme={tokens}
|
|
117
|
+
/>
|
|
118
|
+
) : null}
|
|
119
|
+
</box>
|
|
120
|
+
{footer !== undefined ? (
|
|
121
|
+
<box style={{ borderStyle: "single", border: ["top"], borderColor: tokens.border }}>
|
|
122
|
+
{footer}
|
|
123
|
+
</box>
|
|
124
|
+
) : null}
|
|
125
|
+
</box>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
@@ -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
|
+
};
|