@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.
Files changed (160) hide show
  1. package/package.json +6 -5
  2. package/src/App.autoclose.test.tsx +2 -2
  3. package/src/App.diff.test.tsx +79 -112
  4. package/src/App.plan-review.test.tsx +23 -205
  5. package/src/App.prototype.test.tsx +1 -8
  6. package/src/App.readonly.test.tsx +9 -27
  7. package/src/App.submit-confirm.test.tsx +19 -72
  8. package/src/App.test.tsx +136 -31
  9. package/src/App.tsx +491 -322
  10. package/src/App.walk.test.tsx +15 -16
  11. package/src/App.workbench.test.tsx +390 -0
  12. package/src/__snapshots__/view-plan.regression.test.ts.snap +1 -1
  13. package/src/annotation-palette.ts +61 -0
  14. package/src/app-screens.tsx +197 -77
  15. package/src/app-view-model.ts +9 -69
  16. package/src/components/AnnotationCard.stories.tsx +1 -1
  17. package/src/components/AnnotationCard.tsx +1 -1
  18. package/src/components/AnnotationCards.stories.tsx +87 -0
  19. package/src/components/AnnotationCards.tsx +303 -0
  20. package/src/components/AppHeader.stories.tsx +50 -0
  21. package/src/components/AppHeader.tsx +83 -0
  22. package/src/components/AppShell.stories.tsx +89 -0
  23. package/src/components/AppShell.tsx +239 -0
  24. package/src/components/AppShell.zoom.test.tsx +94 -0
  25. package/src/components/Breadcrumb.stories.tsx +1 -1
  26. package/src/components/ChangesColumn.stories.tsx +39 -0
  27. package/src/components/ChangesColumn.tsx +170 -0
  28. package/src/components/CodeBlock.stories.tsx +1 -1
  29. package/src/components/CompletionOverlay.stories.tsx +1 -1
  30. package/src/components/ConfirmCard.stories.tsx +1 -1
  31. package/src/components/ConfirmCard.tsx +3 -4
  32. package/src/components/ConfirmDialog.stories.tsx +1 -1
  33. package/src/components/DiffSheet.scroll.test.tsx +38 -55
  34. package/src/components/DiffSheet.split.test.tsx +140 -0
  35. package/src/components/DiffSheet.stories.tsx +56 -54
  36. package/src/components/DiffSheet.syntax.test.tsx +24 -7
  37. package/src/components/DiffSheet.tsx +607 -276
  38. package/src/components/DiscussionMarkerRail.stories.tsx +54 -0
  39. package/src/components/DiscussionMarkerRail.tsx +181 -0
  40. package/src/components/EditorGrid.stories.tsx +59 -0
  41. package/src/components/EditorGrid.tabs.test.tsx +105 -0
  42. package/src/components/EditorGrid.tsx +338 -0
  43. package/src/components/EditorGrid.zoom.test.tsx +68 -0
  44. package/src/components/FileContentsView.stories.tsx +16 -0
  45. package/src/components/FileContentsView.test.tsx +29 -0
  46. package/src/components/FileContentsView.tsx +78 -0
  47. package/src/components/InboxList.stories.tsx +23 -8
  48. package/src/components/InboxList.tsx +188 -33
  49. package/src/components/KeybindsDialog.stories.tsx +1 -1
  50. package/src/components/MarkerPopover.stories.tsx +1 -1
  51. package/src/components/MenuBar.stories.tsx +3 -12
  52. package/src/components/MenuBar.tsx +25 -56
  53. package/src/components/PaneGrid.stories.tsx +84 -0
  54. package/src/components/PaneGrid.tsx +128 -0
  55. package/src/components/PanelColumn.stories.tsx +25 -0
  56. package/src/components/PanelColumn.tsx +95 -0
  57. package/src/components/ProjectTreeView.stories.tsx +25 -0
  58. package/src/components/ProjectTreeView.test.tsx +32 -0
  59. package/src/components/ProjectTreeView.tsx +82 -0
  60. package/src/components/PromptDialog.stories.tsx +1 -1
  61. package/src/components/PromptDialog.tsx +8 -1
  62. package/src/components/PrototypeSheet.stories.tsx +1 -1
  63. package/src/components/RootOverlay.stories.tsx +44 -0
  64. package/src/components/RootOverlay.tsx +35 -0
  65. package/src/components/SettingsDialog.stories.tsx +7 -1
  66. package/src/components/SettingsDialog.tsx +114 -67
  67. package/src/components/SettingsRows.stories.tsx +1 -1
  68. package/src/components/ShellHeader.stories.tsx +50 -0
  69. package/src/components/ShellHeader.tsx +137 -0
  70. package/src/components/SurfaceScrollbar.stories.tsx +34 -0
  71. package/src/components/SurfaceScrollbar.test.tsx +111 -0
  72. package/src/components/SurfaceScrollbar.tsx +93 -0
  73. package/src/components/ThreadFooter.stories.tsx +30 -0
  74. package/src/components/ThreadFooter.test.tsx +31 -0
  75. package/src/components/ThreadFooter.tsx +54 -0
  76. package/src/components/ThreadView.slash.test.tsx +113 -0
  77. package/src/components/ThreadView.stories.tsx +1 -1
  78. package/src/components/ThreadView.tsx +62 -1352
  79. package/src/components/ThreadsSidebar.stories.tsx +36 -0
  80. package/src/components/ThreadsSidebar.tsx +70 -0
  81. package/src/components/Toast.stories.tsx +1 -1
  82. package/src/components/Tooltip.stories.tsx +26 -0
  83. package/src/components/Tooltip.tsx +72 -0
  84. package/src/components/TreePane.stories.tsx +1 -1
  85. package/src/components/WalkWizard.stories.tsx +1 -1
  86. package/src/components/WelcomeSurface.stories.tsx +12 -0
  87. package/src/components/WelcomeSurface.tsx +52 -0
  88. package/src/components/__snapshots__/stories.test.tsx.snap +732 -572
  89. package/src/components/agent-launcher.stories.tsx +1 -1
  90. package/src/components/diff-sheet-layout.test.ts +125 -55
  91. package/src/components/diff-sheet-layout.ts +3 -103
  92. package/src/components/editor-grid.test.ts +86 -0
  93. package/src/components/editor-grid.ts +138 -0
  94. package/src/components/fade-title.test.ts +49 -0
  95. package/src/components/fade-title.ts +61 -0
  96. package/src/components/file-tree.test.ts +60 -0
  97. package/src/components/file-tree.ts +112 -0
  98. package/src/components/pages/Workspace.stories.tsx +181 -0
  99. package/src/components/primitives/Button.stories.tsx +1 -1
  100. package/src/components/primitives/Card.stories.tsx +1 -1
  101. package/src/components/primitives/Dialog.stories.tsx +1 -1
  102. package/src/components/primitives/IconButton.stories.tsx +36 -0
  103. package/src/components/primitives/IconButton.tsx +62 -0
  104. package/src/components/primitives/StatusBar.stories.tsx +1 -1
  105. package/src/components/primitives/Tabs.stories.tsx +1 -1
  106. package/src/components/primitives/Toolbar.stories.tsx +1 -1
  107. package/src/components/primitives/Tree.stories.tsx +124 -0
  108. package/src/components/primitives/Tree.tsx +96 -0
  109. package/src/components/primitives/icons.ts +61 -0
  110. package/src/components/primitives/tree-model.test.ts +99 -0
  111. package/src/components/primitives/tree-model.ts +137 -0
  112. package/src/components/quick-actions-editor.stories.tsx +1 -1
  113. package/src/components/session-tree.test.ts +134 -0
  114. package/src/components/session-tree.ts +108 -0
  115. package/src/components/stories-app.tsx +142 -82
  116. package/src/components/story-tree.test.ts +100 -0
  117. package/src/components/story.ts +55 -0
  118. package/src/components/theme-context.stories.tsx +1 -1
  119. package/src/config.test.ts +20 -49
  120. package/src/config.ts +26 -28
  121. package/src/diff-fold.test.ts +78 -0
  122. package/src/diff-fold.ts +126 -0
  123. package/src/diff-syntax.test.ts +16 -0
  124. package/src/diff-syntax.ts +6 -3
  125. package/src/discussions.ts +79 -0
  126. package/src/intent-dispatch.test.ts +31 -11
  127. package/src/intent-dispatch.ts +93 -48
  128. package/src/key-bindings.test.ts +1 -1
  129. package/src/keymap.test.ts +3 -25
  130. package/src/keymap.ts +17 -18
  131. package/src/mark-runs.ts +120 -0
  132. package/src/pty.ts +10 -2
  133. package/src/run.ts +17 -7
  134. package/src/serve.test.ts +4 -8
  135. package/src/session-controller.curate.test.ts +75 -0
  136. package/src/session-controller.share.test.ts +1 -0
  137. package/src/session-controller.tree.test.ts +26 -0
  138. package/src/session-controller.ts +160 -31
  139. package/src/slash-palette.ts +83 -0
  140. package/src/split-diff.test.ts +110 -0
  141. package/src/split-diff.ts +149 -0
  142. package/src/test-support.test.ts +3 -3
  143. package/src/test-support.ts +39 -8
  144. package/src/theme.ts +4 -4
  145. package/src/thread-chords.test.ts +25 -9
  146. package/src/thread-chords.ts +33 -8
  147. package/src/use-annotation-surface.tsx +837 -0
  148. package/src/use-changes-workbench.ts +151 -0
  149. package/src/use-frame-measure.ts +38 -0
  150. package/src/use-settings-dialog.tsx +23 -24
  151. package/src/use-terminal-virtualizer.ts +233 -0
  152. package/src/view-diff.ts +114 -0
  153. package/src/App.review-panel.test.tsx +0 -140
  154. package/src/App.tree.test.tsx +0 -224
  155. package/src/components/ReviewPanel.stories.tsx +0 -100
  156. package/src/components/ReviewPanel.tsx +0 -103
  157. package/src/components/ReviewRail.stories.tsx +0 -232
  158. package/src/components/ReviewRail.tsx +0 -327
  159. package/src/review-panel.test.ts +0 -113
  160. package/src/review-panel.ts +0 -83
@@ -1,5 +1,5 @@
1
- import React, { type Dispatch, type SetStateAction } from "react";
2
- import type { ReviewSession, VerdictKind } from "@cueloop/schema";
1
+ import React, { useEffect, useState, type Dispatch, type SetStateAction } from "react";
2
+ import type { DiffFileContents, ReviewSession, VerdictKind } from "@cueloop/schema";
3
3
  import { returnPaneFor } from "@cueloop/schema";
4
4
  import type { Theme } from "./theme";
5
5
  import type { Mode, TreeAsk } from "./intent-dispatch";
@@ -12,11 +12,17 @@ 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 { MenuBar } from "./components/MenuBar";
16
- import { KeybindsDialog } from "./components/KeybindsDialog";
15
+ import type { InboxRow } from "./components/session-tree";
17
16
  import { SettingsDialog } from "./components/SettingsDialog";
18
17
  import { CompletionOverlay } from "./components/CompletionOverlay";
19
18
  import { InboxList } from "./components/InboxList";
19
+ import { WelcomeSurface } from "./components/WelcomeSurface";
20
+ import { AppShell, type ProjectPanelMode } from "./components/AppShell";
21
+ import { EditorGrid } from "./components/EditorGrid";
22
+ import { ProjectTreeView } from "./components/ProjectTreeView";
23
+ import { ChangesFileTree } from "./components/ChangesColumn";
24
+ import { FileContentsView } from "./components/FileContentsView";
25
+ import { useChangesWorkbench } from "./use-changes-workbench";
20
26
  import { ConfirmDialog } from "./components/ConfirmDialog";
21
27
  import { PromptDialog } from "./components/PromptDialog";
22
28
  import { WalkWizard } from "./components/WalkWizard";
@@ -39,12 +45,8 @@ export function ConnectingScreen({ theme }: { theme: Theme }): React.ReactNode {
39
45
  }
40
46
 
41
47
  export function MenuChrome(props: {
42
- menuOpen: boolean;
43
48
  menuDialog: "keybinds" | "settings" | null;
44
- status: string;
45
49
  theme: Theme;
46
- setMenuOpen: Dispatch<SetStateAction<boolean>>;
47
- setMenuDialog: Dispatch<SetStateAction<"keybinds" | "settings" | null>>;
48
50
  keybindsSections: CheatsheetSection[];
49
51
  settingsCategories: SettingsCategory[];
50
52
  settingsValues: SettingsValues;
@@ -53,12 +55,8 @@ export function MenuChrome(props: {
53
55
  cycleSetting: (rowKey: string) => void;
54
56
  }): React.ReactNode {
55
57
  const {
56
- menuOpen,
57
58
  menuDialog,
58
- status,
59
59
  theme,
60
- setMenuOpen,
61
- setMenuDialog,
62
60
  keybindsSections,
63
61
  settingsCategories,
64
62
  settingsValues,
@@ -67,90 +65,190 @@ export function MenuChrome(props: {
67
65
  cycleSetting,
68
66
  } = props;
69
67
 
68
+ // the gear opens the settings dialog directly; Keybinds is a leaf in its tree nav
69
+ if (menuDialog !== "settings") return null;
70
+
70
71
  return (
71
- <>
72
- <MenuBar
73
- open={menuOpen}
74
- version={CLIENT_VERSION}
75
- status={status}
76
- onToggle={() => setMenuOpen((isOpen) => !isOpen)}
77
- onSettings={() => {
78
- setMenuOpen(false);
79
- setMenuDialog("settings");
80
- }}
81
- onKeybinds={() => {
82
- setMenuOpen(false);
83
- setMenuDialog("keybinds");
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
- </>
72
+ <SettingsDialog
73
+ isOpen
74
+ version={CLIENT_VERSION}
75
+ keybindsSections={keybindsSections}
76
+ categories={settingsCategories}
77
+ values={settingsValues}
78
+ activeCategoryId={settingsNav.categoryId}
79
+ activeRowIndex={settingsNav.rowIndex}
80
+ activeZone={settingsNav.zone}
81
+ onCategorySelect={onCategorySelect}
82
+ onRowActivate={(row) => cycleSetting(row.key)}
83
+ theme={theme}
84
+ />
85
+ );
86
+ }
87
+
88
+ /** The welcome shell's Project pane: the launch repo's changed files in changes mode, its full tree otherwise. */
89
+ function WelcomeProjectPanel({
90
+ mode,
91
+ controller,
92
+ onOpenFile,
93
+ theme,
94
+ }: {
95
+ mode: ProjectPanelMode;
96
+ controller: ReviewController;
97
+ onOpenFile: (path: string) => void;
98
+ theme: Theme;
99
+ }): React.ReactNode {
100
+ const [changes, setChanges] = useState<readonly DiffFileContents[]>([]);
101
+
102
+ useEffect(() => {
103
+ let alive = true;
104
+
105
+ void controller.repoChanges().then(
106
+ (files) => {
107
+ if (alive) setChanges(files);
108
+ },
109
+ () => {
110
+ if (alive) setChanges([]);
111
+ },
112
+ );
113
+
114
+ return () => {
115
+ alive = false;
116
+ };
117
+ }, [controller]);
118
+
119
+ if (mode === "changes") {
120
+ return <ChangesFileTree files={changes} onSelectFile={onOpenFile} theme={theme} />;
121
+ }
122
+
123
+ return (
124
+ <ProjectTreeView
125
+ loadFiles={() => controller.repoFiles()}
126
+ onSelectFile={onOpenFile}
127
+ theme={theme}
128
+ />
104
129
  );
105
130
  }
106
131
 
107
- export function InboxScreen(props: {
108
- inbox: ReviewSession[];
132
+ /**
133
+ * The shell with no thread open: the same header and Projects/Threads sidebar as
134
+ * the thread view, and a disposable Welcome tab in the center. There is no
135
+ * separate inbox screen - opening the app lands here, and picking a thread swaps
136
+ * the center for it. Closing the Welcome tab leaves a bare "select a thread" hint.
137
+ */
138
+ export function NoThreadShell(props: {
139
+ rows: InboxRow[];
109
140
  inboxCursor: number;
110
141
  mode: Mode;
111
142
  theme: Theme;
112
143
  controller: ReviewController;
113
144
  setMode: Dispatch<SetStateAction<Mode>>;
114
145
  menuChrome: React.ReactNode;
146
+ onOpenMenu: () => void;
147
+ /** Shared with the thread view, so picking a thread preserves the sidebar. */
148
+ sidebarOpen: boolean;
149
+ onToggleSidebar: () => void;
150
+ pinnedIds: ReadonlySet<string>;
151
+ onPin: (id: string) => void;
152
+ onRename: (id: string, title: string) => void;
115
153
  }): React.ReactNode {
116
- const { inbox, inboxCursor, mode, theme, controller, setMode, menuChrome } = props;
154
+ const {
155
+ rows,
156
+ inboxCursor,
157
+ mode,
158
+ theme,
159
+ controller,
160
+ setMode,
161
+ menuChrome,
162
+ onOpenMenu,
163
+ sidebarOpen,
164
+ onToggleSidebar,
165
+ pinnedIds,
166
+ onPin,
167
+ onRename,
168
+ } = props;
117
169
  const confirming = mode.type === "confirmDelete" ? mode : null;
170
+ // The bare-launch shell is the same four panes as a thread: the Thread pane waits in its empty state
171
+ // and a disposable Welcome tab rides in the Changes editor until a thread or diff is opened.
172
+ const workbench = useChangesWorkbench({ seed: "welcome" });
173
+ const openRepoFile = (path: string): void => workbench.openFile(path, "contents");
118
174
 
119
175
  return (
120
176
  <ThemeProvider theme={theme}>
121
- <box
122
- style={{
123
- flexDirection: "column",
124
- width: "100%",
125
- height: "100%",
126
- backgroundColor: theme.background,
127
- }}
128
- >
129
- {/* mirrors the review header row: same box, position, and accent product
130
- word, with a " · resume" separator and no Edit/Share toolbar */}
131
- <box
132
- style={{ flexDirection: "row", height: 2, paddingTop: 1, backgroundColor: theme.panel }}
133
- >
177
+ <AppShell
178
+ sidebarOpen={sidebarOpen}
179
+ onToggleSidebar={onToggleSidebar}
180
+ onOpenMenu={onOpenMenu}
181
+ threadsPanel={
182
+ <scrollbox style={{ flexGrow: 1 }} focused={false}>
183
+ <InboxList
184
+ rows={rows}
185
+ cursor={inboxCursor}
186
+ pinnedIds={pinnedIds}
187
+ width={30}
188
+ onSelect={(id) => controller.open(id)}
189
+ onRequestDelete={(id, title) =>
190
+ setMode({ type: "confirmDelete", sessionId: id, title })
191
+ }
192
+ onPin={onPin}
193
+ onRename={onRename}
194
+ theme={theme}
195
+ />
196
+ </scrollbox>
197
+ }
198
+ threadTitle=""
199
+ threadPanel={
134
200
  <box
135
201
  style={{
136
- height: 1,
137
- backgroundColor: theme.panel,
138
- paddingLeft: 1,
139
- flexDirection: "row",
202
+ flexGrow: 1,
203
+ flexDirection: "column",
204
+ alignItems: "center",
205
+ justifyContent: "center",
140
206
  }}
141
207
  >
142
- <text>
143
- <span fg={theme.accent}>cueloop</span>
144
- <span fg={theme.textDim}> · resume</span>
145
- </text>
208
+ <text fg={theme.textDim}>Select a thread</text>
146
209
  </box>
147
- <box style={{ flexGrow: 1 }} />
148
- </box>
149
- <InboxList
150
- inbox={inbox}
151
- cursor={inboxCursor}
152
- onRequestDelete={(id, title) => setMode({ type: "confirmDelete", sessionId: id, title })}
153
- />
210
+ }
211
+ changesOpen={workbench.changesOpen}
212
+ projectOpen={workbench.projectOpen}
213
+ onToggleChanges={workbench.toggleChanges}
214
+ onToggleProject={workbench.toggleProject}
215
+ onToggleRight={workbench.toggleRight}
216
+ projectMode={workbench.projectMode}
217
+ zoomHideThread={workbench.zoomed}
218
+ changesPanel={
219
+ <EditorGrid
220
+ tree={workbench.grid}
221
+ focusedGroupId={workbench.activeGroup}
222
+ onFocusGroup={workbench.focusGroup}
223
+ onActivateTab={workbench.activate}
224
+ onCloseTab={workbench.close}
225
+ onSplit={workbench.split}
226
+ onZoom={workbench.toggleZoom}
227
+ zoomed={workbench.zoomed}
228
+ renderTab={(tab) =>
229
+ tab.kind === "welcome" ? (
230
+ <WelcomeSurface version={CLIENT_VERSION} theme={theme} />
231
+ ) : (
232
+ <FileContentsView
233
+ path={tab.path ?? ""}
234
+ loadContents={(path) => controller.repoReadFile(path)}
235
+ theme={theme}
236
+ />
237
+ )
238
+ }
239
+ theme={theme}
240
+ />
241
+ }
242
+ projectPanel={
243
+ <WelcomeProjectPanel
244
+ mode={workbench.projectMode}
245
+ controller={controller}
246
+ onOpenFile={openRepoFile}
247
+ theme={theme}
248
+ />
249
+ }
250
+ theme={theme}
251
+ >
154
252
  {menuChrome}
155
253
  <ConfirmDialog
156
254
  isOpen={confirming !== null}
@@ -165,7 +263,18 @@ export function InboxScreen(props: {
165
263
  onCancel={() => setMode({ type: "normal" })}
166
264
  theme={theme}
167
265
  />
168
- </box>
266
+ {mode.type === "renameThread" ? (
267
+ <PromptDialog
268
+ isOpen
269
+ title=" rename thread "
270
+ label="new title for this thread:"
271
+ value={mode.text}
272
+ placeholder="a short title"
273
+ onInput={(text) => setMode({ ...mode, text })}
274
+ theme={theme}
275
+ />
276
+ ) : null}
277
+ </AppShell>
169
278
  </ThemeProvider>
170
279
  );
171
280
  }
@@ -268,6 +377,17 @@ export function TrailingOverlays(props: {
268
377
  theme={theme}
269
378
  />
270
379
  ) : null}
380
+ {mode.type === "renameThread" ? (
381
+ <PromptDialog
382
+ isOpen
383
+ title=" rename thread "
384
+ label="new title for this thread:"
385
+ value={mode.text}
386
+ placeholder="a short title"
387
+ onInput={(text) => setMode({ ...mode, text })}
388
+ theme={theme}
389
+ />
390
+ ) : null}
271
391
  {mode.type === "nameSelf" ? (
272
392
  <PromptDialog
273
393
  isOpen
@@ -1,16 +1,12 @@
1
1
  import type { Dispatch, MutableRefObject, SetStateAction } from "react";
2
2
  import type { ReviewSession, VerdictKind } from "@cueloop/schema";
3
- import { reviewerAnnotations, type Mode } from "./intent-dispatch";
4
- import type { DiffRow } from "./view-diff";
3
+ import type { Mode } from "./intent-dispatch";
5
4
  import type { Intent, KeyState } from "./keymap";
6
5
  import type { Completion } from "./session-controller";
7
6
  import type { WalkFile } from "./walk";
8
7
  import { viewedCount } from "./walk";
9
- import type { DiffComposeState } from "./components/DiffSheet";
10
8
  import type { ConfirmCardProps } from "./components/ConfirmCard";
11
- import type { RailCardEdit } from "./components/ReviewRail";
12
9
  import type { BreadcrumbItem } from "./components/Breadcrumb";
13
- import { REVIEW_COMPACT_WIDTH, resolveReviewWidth, type ReviewPanelMode } from "./review-panel";
14
10
 
15
11
  export function computeRoleCapabilities(
16
12
  readOnly: boolean,
@@ -41,7 +37,12 @@ export function resolveOverlay(
41
37
  if (mode.type === "compose" || mode.type === "railEdit") return "compose";
42
38
  if (mode.type === "submit") return "submit";
43
39
  if (mode.type === "confirmDelete") return "confirm";
44
- if (mode.type === "rename" || mode.type === "nameSelf" || mode.type === "treePrompt")
40
+ if (
41
+ mode.type === "rename" ||
42
+ mode.type === "renameThread" ||
43
+ mode.type === "nameSelf" ||
44
+ mode.type === "treePrompt"
45
+ )
45
46
  return "prompt";
46
47
  if (mode.type === "spanActions") return "spanActions";
47
48
  if (completionPhase === "prompt") return "completion-prompt";
@@ -57,25 +58,6 @@ export function isCompletionOverlayPhase(
57
58
  return completion.phase === "prompt" || completion.phase === "counting";
58
59
  }
59
60
 
60
- export function computePendingCount(session: ReviewSession): number {
61
- return reviewerAnnotations(session).length + (session.workingCopy !== undefined ? 1 : 0);
62
- }
63
-
64
- export function computeRailFootprint(
65
- reviewMode: ReviewPanelMode,
66
- reviewWidth: number,
67
- terminalWidth: number,
68
- ): number {
69
- if (reviewMode === "hidden") return 0;
70
-
71
- return (
72
- 1 +
73
- (reviewMode === "compact"
74
- ? REVIEW_COMPACT_WIDTH
75
- : resolveReviewWidth(reviewWidth, terminalWidth))
76
- );
77
- }
78
-
79
61
  export function buildHeaderItems(params: {
80
62
  session: ReviewSession;
81
63
  resolved: boolean;
@@ -112,17 +94,15 @@ export function buildRenderFlags(params: {
112
94
  isDiff: boolean;
113
95
  isPrototype: boolean;
114
96
  resolved: boolean;
115
- menuOpen: boolean;
116
97
  menuDialog: "keybinds" | "settings" | null;
117
98
  resolvedIds: Set<string>;
118
99
  }) {
119
- const { session, isOwner, isDiff, isPrototype, resolved, menuOpen, menuDialog, resolvedIds } =
120
- params;
100
+ const { session, isOwner, isDiff, isPrototype, resolved, menuDialog, resolvedIds } = params;
121
101
 
122
102
  return {
123
103
  showOwnerActions: isOwner && !isDiff && !resolved,
124
104
  prototypeCanComment: isOwner && !resolved,
125
- chromeHidden: menuOpen || menuDialog !== null,
105
+ chromeHidden: menuDialog !== null,
126
106
  prototypePath: session.artifact.meta.prototypePath ?? "",
127
107
  railResolvedIds: isDiff || isPrototype ? null : resolvedIds,
128
108
  };
@@ -134,29 +114,6 @@ interface DraftHandlerDeps {
134
114
  dispatch: (intent: Intent) => void;
135
115
  }
136
116
 
137
- export function buildDiffComposeState(
138
- deps: DraftHandlerDeps & { mode: Mode; isDiff: boolean; rows: DiffRow[] },
139
- ): DiffComposeState | null {
140
- const { mode, isDiff, rows, liveInput, setMode, dispatch } = deps;
141
-
142
- if (mode.type !== "compose" || !isDiff) return null;
143
-
144
- return {
145
- kind: mode.kind,
146
- rowIndex: mode.displayIndex,
147
- quote: rows[mode.displayIndex]?.text.replace(/\n$/, "") ?? "",
148
- draft: {
149
- text: mode.text,
150
- onInput: (text: string) => {
151
- liveInput.current = text;
152
- setMode({ ...mode, text });
153
- },
154
- onSave: () => dispatch({ type: "saveCompose" }),
155
- onCancel: () => dispatch({ type: "closeOverlay" }),
156
- },
157
- };
158
- }
159
-
160
117
  export function buildSubmitConfirmState(
161
118
  deps: DraftHandlerDeps & {
162
119
  mode: Mode;
@@ -186,20 +143,3 @@ export function buildSubmitConfirmState(
186
143
  onCancel: () => dispatch({ type: "closeOverlay" }),
187
144
  };
188
145
  }
189
-
190
- export function buildCardEditState(deps: DraftHandlerDeps & { mode: Mode }): RailCardEdit | null {
191
- const { mode, liveInput, setMode, dispatch } = deps;
192
-
193
- if (mode.type !== "railEdit") return null;
194
-
195
- return {
196
- id: mode.id,
197
- text: mode.text,
198
- onInput: (text: string) => {
199
- liveInput.current = text;
200
- setMode({ type: "railEdit", id: mode.id, text });
201
- },
202
- onSave: () => dispatch({ type: "saveCompose" }),
203
- onCancel: () => dispatch({ type: "closeOverlay" }),
204
- };
205
- }
@@ -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
 
@@ -185,7 +185,7 @@ export function AnnotationCard({
185
185
 
186
186
  return (
187
187
  <Card
188
- title={` ${primitive} "${truncateToSingleLine(quote, 40)}" `}
188
+ title={` ${primitive} "${truncateToSingleLine(quote, 20)}" `}
189
189
  contentRows={editorRowCount + 1}
190
190
  borderColor={kindColor}
191
191
  backgroundColor="transparent"
@@ -0,0 +1,87 @@
1
+ import React from "react";
2
+ import type { Annotation } from "@cueloop/schema";
3
+ import { DARK } from "../theme";
4
+ import { annotationPaletteFor } from "../annotation-palette";
5
+ import { slashItemsFrom } from "../slash-palette";
6
+ import { DEFAULT_QUICK_ACTIONS } from "../config";
7
+ import type { Story, StoryMeta } from "./story";
8
+ import { CommentRow, ComposerPalette, DiscussionCard } from "./AnnotationCards";
9
+
10
+ export const meta: StoryMeta = { title: "Chrome/AnnotationCards" };
11
+
12
+ const palette = annotationPaletteFor(DARK);
13
+
14
+ function annotation(id: string, body: string, author?: string): Annotation {
15
+ const comment: Annotation = {
16
+ id,
17
+ kind: "comment",
18
+ anchor: { quote: "the daemon", prefix: "", suffix: "" },
19
+ body,
20
+ createdAt: "2026-09-07T10:00:00.000Z",
21
+ };
22
+
23
+ if (author !== undefined) comment.author = author;
24
+
25
+ return comment;
26
+ }
27
+
28
+ // A two-voice discussion: the owner's comment then a collaborator's reply, each
29
+ // segment wearing its own accent edge.
30
+ export const TwoVoices: Story = {
31
+ render: () => (
32
+ <DiscussionCard
33
+ tokens={DARK}
34
+ segments={[
35
+ {
36
+ color: palette.cardEdge,
37
+ node: <CommentRow annotation={annotation("a", "Which daemon?")} tokens={DARK} />,
38
+ },
39
+ {
40
+ color: DARK.text,
41
+ node: (
42
+ <CommentRow
43
+ annotation={annotation("b", "The one the CLI spawns.", "fp:collab")}
44
+ tokens={DARK}
45
+ />
46
+ ),
47
+ },
48
+ ]}
49
+ />
50
+ ),
51
+ expectedColors: [palette.cardEdge, DARK.text],
52
+ size: { width: 60, height: 8 },
53
+ };
54
+
55
+ export const FocusedCard: Story = {
56
+ render: () => (
57
+ <DiscussionCard
58
+ focused
59
+ tokens={DARK}
60
+ segments={[
61
+ {
62
+ color: palette.cardEdge,
63
+ node: (
64
+ <CommentRow annotation={annotation("a", "Needs an eviction story.")} tokens={DARK} />
65
+ ),
66
+ },
67
+ ]}
68
+ />
69
+ ),
70
+ expectedColors: [DARK.text],
71
+ size: { width: 60, height: 6 },
72
+ };
73
+
74
+ // The palette a leading "/" opens under the composer, with the second item selected.
75
+ export const SlashPaletteOpen: Story = {
76
+ render: () => (
77
+ <ComposerPalette
78
+ slashActive
79
+ slashItems={slashItemsFrom(DEFAULT_QUICK_ACTIONS)}
80
+ slashIndex={1}
81
+ inline={null}
82
+ tokens={DARK}
83
+ />
84
+ ),
85
+ expectedColors: [DARK.accent, DARK.textDim],
86
+ size: { width: 90, height: 8 },
87
+ };