@cueloop/client 0.1.0-alpha.32 → 0.1.0-alpha.34
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 +27 -26
- package/src/App.autoclose.test.tsx +7 -4
- package/src/App.diff.test.tsx +131 -5
- package/src/App.export.test.tsx +23 -5
- package/src/App.plan-review.test.tsx +133 -32
- package/src/App.readonly.test.tsx +35 -13
- package/src/App.rendering.test.tsx +14 -4
- package/src/App.review-panel.test.tsx +27 -15
- package/src/App.submit-confirm.test.tsx +14 -13
- package/src/App.test.tsx +50 -13
- package/src/App.toast.test.tsx +75 -0
- package/src/App.tsx +505 -101
- package/src/App.walk.test.tsx +31 -11
- package/src/attribution.ts +8 -2
- package/src/clipboard.ts +5 -1
- package/src/components/AnnotationCard.stories.tsx +29 -10
- package/src/components/AnnotationCard.tsx +57 -37
- package/src/components/Breadcrumb.tsx +7 -1
- package/src/components/CodeBlock.stories.tsx +19 -2
- package/src/components/CodeBlock.tsx +24 -9
- package/src/components/CompletionOverlay.tsx +13 -3
- package/src/components/ConfirmCard.stories.tsx +22 -3
- package/src/components/ConfirmCard.tsx +23 -5
- package/src/components/ConfirmDialog.tsx +10 -2
- package/src/components/DiffSheet.scroll.test.tsx +123 -0
- package/src/components/DiffSheet.stories.tsx +35 -1
- package/src/components/DiffSheet.syntax.test.tsx +62 -0
- package/src/components/DiffSheet.tsx +213 -101
- package/src/components/InboxList.stories.tsx +16 -3
- package/src/components/InboxList.tsx +17 -10
- package/src/components/KeybindsDialog.stories.tsx +31 -0
- package/src/components/KeybindsDialog.tsx +58 -0
- package/src/components/MarkerPopover.stories.tsx +58 -0
- package/src/components/MarkerPopover.tsx +102 -0
- package/src/components/MenuBar.stories.tsx +37 -0
- package/src/components/MenuBar.tsx +74 -0
- package/src/components/PlanSheet.stories.tsx +35 -3
- package/src/components/PlanSheet.tsx +160 -100
- package/src/components/PromptDialog.tsx +19 -3
- package/src/components/ReviewPanel.stories.tsx +17 -19
- package/src/components/ReviewPanel.tsx +17 -32
- package/src/components/ReviewRail.stories.tsx +159 -47
- package/src/components/ReviewRail.tsx +206 -62
- package/src/components/SettingsDialog.stories.tsx +51 -3
- package/src/components/SettingsDialog.tsx +11 -5
- package/src/components/SettingsRows.stories.tsx +1 -1
- package/src/components/SettingsRows.tsx +19 -3
- package/src/components/Toast.stories.tsx +12 -0
- package/src/components/Toast.tsx +44 -0
- package/src/components/WalkWizard.stories.tsx +13 -2
- package/src/components/WalkWizard.tsx +9 -3
- package/src/components/__snapshots__/stories.test.tsx.snap +853 -623
- package/src/components/diff-sheet-layout.test.ts +127 -0
- package/src/components/diff-sheet-layout.ts +167 -0
- package/src/components/primitives/Button.stories.tsx +2 -2
- package/src/components/primitives/Button.tsx +5 -1
- package/src/components/primitives/Dialog.tsx +12 -1
- package/src/components/primitives/Tabs.tsx +40 -39
- package/src/components/stories-app.tsx +34 -6
- package/src/components/story-fixtures.ts +11 -3
- package/src/components/story.ts +3 -1
- package/src/components/syntax-highlight.ts +47 -12
- package/src/components/theme-context.stories.tsx +6 -1
- package/src/components/theme-context.tsx +7 -1
- package/src/config.test.ts +178 -2
- package/src/config.ts +121 -23
- package/src/diff-hunk-curate.test.ts +315 -0
- package/src/diff-hunk-curate.ts +233 -0
- package/src/diff-intraline.test.ts +164 -0
- package/src/diff-intraline.ts +132 -0
- package/src/diff-syntax.test.ts +96 -0
- package/src/diff-syntax.ts +154 -0
- package/src/editor.test.ts +13 -4
- package/src/editor.ts +31 -5
- package/src/herdr.ts +8 -2
- package/src/index.ts +9 -1
- package/src/integrations.ts +3 -1
- package/src/intent-dispatch.test.ts +193 -9
- package/src/intent-dispatch.ts +151 -18
- package/src/key-bindings.test.ts +19 -9
- package/src/key-bindings.ts +77 -16
- package/src/keymap.test.ts +171 -26
- package/src/keymap.ts +83 -16
- package/src/review-panel.test.ts +3 -1
- package/src/review-panel.ts +9 -2
- package/src/serve.test.ts +13 -5
- package/src/session-controller.curate.test.ts +253 -0
- package/src/session-controller.share.test.ts +77 -16
- package/src/session-controller.ts +319 -26
- package/src/share.ts +41 -8
- package/src/test-support.ts +97 -23
- package/src/theme-presets.ts +168 -0
- package/src/theme.ts +1 -3
- package/src/version.ts +5 -0
- package/src/view-diff.ts +15 -4
- package/src/view-plan.test.ts +40 -6
- package/src/view-plan.ts +71 -28
- package/src/walk.test.ts +7 -2
- package/src/walk.ts +7 -2
- package/src/components/ComposeBar.stories.tsx +0 -18
- package/src/components/ComposeBar.tsx +0 -54
|
@@ -2,14 +2,17 @@ import React from "react";
|
|
|
2
2
|
import { DARK } from "../theme";
|
|
3
3
|
import type { Story, StoryMeta } from "./story";
|
|
4
4
|
import { ReviewRail } from "./ReviewRail";
|
|
5
|
-
import { FIXTURE_ANNOTATIONS, fixturePlanSession } from "./story-fixtures";
|
|
5
|
+
import { FIXTURE_ANNOTATIONS, fixtureDiffSession, fixturePlanSession } from "./story-fixtures";
|
|
6
6
|
|
|
7
7
|
export const meta: StoryMeta = { title: "ReviewRail" };
|
|
8
8
|
|
|
9
9
|
const callbacks = {
|
|
10
|
+
curationItems: [],
|
|
10
11
|
onTabChange: () => {},
|
|
11
12
|
onSelectCard: () => {},
|
|
12
13
|
onActivateCard: () => {},
|
|
14
|
+
onSelectCuration: () => {},
|
|
15
|
+
onUndoCuration: () => {},
|
|
13
16
|
onSubmitRequest: () => {},
|
|
14
17
|
};
|
|
15
18
|
|
|
@@ -22,19 +25,123 @@ export const AnnotationStack: Story = {
|
|
|
22
25
|
render: () => (
|
|
23
26
|
<RailFrame>
|
|
24
27
|
<ReviewRail
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
session={fixturePlanSession()}
|
|
29
|
+
authorNames={{}}
|
|
30
|
+
selectedId={FIXTURE_ANNOTATIONS[0]!.id}
|
|
31
|
+
resolvedIds={new Set(FIXTURE_ANNOTATIONS.map((annotation) => annotation.id))}
|
|
32
|
+
railTab="review"
|
|
33
|
+
pendingCount={2}
|
|
34
|
+
cardEdit={null}
|
|
35
|
+
submitConfirm={null}
|
|
36
|
+
{...callbacks}
|
|
34
37
|
/>
|
|
35
38
|
</RailFrame>
|
|
36
39
|
),
|
|
37
|
-
expectedColors: [DARK.
|
|
40
|
+
expectedColors: [DARK.accent],
|
|
41
|
+
size: { width: 40, height: 24 },
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const MixedOwnershipStack: Story = {
|
|
45
|
+
render: () => (
|
|
46
|
+
<RailFrame>
|
|
47
|
+
<ReviewRail
|
|
48
|
+
session={fixturePlanSession({
|
|
49
|
+
participants: [{ id: "SHA256:collab1", provider: "ssh", name: "Priya" }],
|
|
50
|
+
annotations: [
|
|
51
|
+
{
|
|
52
|
+
id: "own_note",
|
|
53
|
+
kind: "comment",
|
|
54
|
+
anchor: { quote: "persists sessions", prefix: "The daemon ", suffix: " to disk" },
|
|
55
|
+
body: "Which daemon owns this?",
|
|
56
|
+
createdAt: "2026-01-01T00:00:00Z",
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: "collab_note",
|
|
60
|
+
kind: "comment",
|
|
61
|
+
anchor: { quote: "move the store", prefix: "", suffix: "" },
|
|
62
|
+
body: "move it behind one interface",
|
|
63
|
+
author: "SHA256:collab1",
|
|
64
|
+
createdAt: "2026-01-01T00:00:01Z",
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
})}
|
|
68
|
+
authorNames={{}}
|
|
69
|
+
selectedId="own_note"
|
|
70
|
+
resolvedIds={new Set(["own_note", "collab_note"])}
|
|
71
|
+
railTab="review"
|
|
72
|
+
pendingCount={2}
|
|
73
|
+
cardEdit={null}
|
|
74
|
+
submitConfirm={null}
|
|
75
|
+
{...callbacks}
|
|
76
|
+
/>
|
|
77
|
+
</RailFrame>
|
|
78
|
+
),
|
|
79
|
+
// every annotation border is accent now
|
|
80
|
+
expectedColors: [DARK.accent],
|
|
81
|
+
size: { width: 40, height: 24 },
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export const Removals: Story = {
|
|
85
|
+
render: () => (
|
|
86
|
+
<RailFrame>
|
|
87
|
+
<ReviewRail
|
|
88
|
+
session={fixtureDiffSession()}
|
|
89
|
+
authorNames={{}}
|
|
90
|
+
resolvedIds={null}
|
|
91
|
+
railTab="review"
|
|
92
|
+
pendingCount={0}
|
|
93
|
+
cardEdit={null}
|
|
94
|
+
submitConfirm={null}
|
|
95
|
+
{...callbacks}
|
|
96
|
+
curationItems={[
|
|
97
|
+
{
|
|
98
|
+
id: "diff:src/store.ts#0#1",
|
|
99
|
+
source: "diff",
|
|
100
|
+
preview: ["- private items = [];", "+ private items = new Map();"],
|
|
101
|
+
revealIndex: 3,
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
id: "diff:src/store.ts#1#hunk",
|
|
105
|
+
source: "diff",
|
|
106
|
+
preview: ["- return null;", "+ return fallback();", "+ log(fallback);"],
|
|
107
|
+
revealIndex: 9,
|
|
108
|
+
},
|
|
109
|
+
]}
|
|
110
|
+
selectedCurationId="diff:src/store.ts#0#1"
|
|
111
|
+
/>
|
|
112
|
+
</RailFrame>
|
|
113
|
+
),
|
|
114
|
+
expectedColors: [DARK.red],
|
|
115
|
+
size: { width: 40, height: 24 },
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/** A plan cut and an annotation interleave in one line-ordered stack. */
|
|
119
|
+
export const InterleavedStack: Story = {
|
|
120
|
+
render: () => (
|
|
121
|
+
<RailFrame>
|
|
122
|
+
<ReviewRail
|
|
123
|
+
session={fixturePlanSession()}
|
|
124
|
+
authorNames={{}}
|
|
125
|
+
selectedId={FIXTURE_ANNOTATIONS[0]!.id}
|
|
126
|
+
resolvedIds={new Set(FIXTURE_ANNOTATIONS.map((annotation) => annotation.id))}
|
|
127
|
+
railTab="review"
|
|
128
|
+
pendingCount={2}
|
|
129
|
+
cardEdit={null}
|
|
130
|
+
submitConfirm={null}
|
|
131
|
+
{...callbacks}
|
|
132
|
+
curationItems={[
|
|
133
|
+
{
|
|
134
|
+
id: "plan:6-6",
|
|
135
|
+
source: "plan",
|
|
136
|
+
preview: ["- add recovery"],
|
|
137
|
+
revealIndex: 1,
|
|
138
|
+
},
|
|
139
|
+
]}
|
|
140
|
+
annotationPositions={new Map([[FIXTURE_ANNOTATIONS[0]!.id, 5]])}
|
|
141
|
+
/>
|
|
142
|
+
</RailFrame>
|
|
143
|
+
),
|
|
144
|
+
expectedColors: [DARK.accent],
|
|
38
145
|
size: { width: 40, height: 24 },
|
|
39
146
|
};
|
|
40
147
|
|
|
@@ -42,14 +149,14 @@ export const AgentTab: Story = {
|
|
|
42
149
|
render: () => (
|
|
43
150
|
<RailFrame>
|
|
44
151
|
<ReviewRail
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
152
|
+
session={fixturePlanSession()}
|
|
153
|
+
authorNames={{}}
|
|
154
|
+
resolvedIds={new Set()}
|
|
155
|
+
railTab="agent"
|
|
156
|
+
pendingCount={2}
|
|
157
|
+
cardEdit={null}
|
|
158
|
+
submitConfirm={null}
|
|
159
|
+
{...callbacks}
|
|
53
160
|
/>
|
|
54
161
|
</RailFrame>
|
|
55
162
|
),
|
|
@@ -60,23 +167,23 @@ export const SubmitConfirmOpen: Story = {
|
|
|
60
167
|
render: () => (
|
|
61
168
|
<RailFrame>
|
|
62
169
|
<ReviewRail
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
170
|
+
session={fixturePlanSession()}
|
|
171
|
+
authorNames={{}}
|
|
172
|
+
resolvedIds={new Set()}
|
|
173
|
+
railTab="review"
|
|
174
|
+
pendingCount={2}
|
|
175
|
+
cardEdit={null}
|
|
176
|
+
submitConfirm={{
|
|
177
|
+
verdict: "request_changes",
|
|
178
|
+
summary: "",
|
|
179
|
+
annotationCount: 2,
|
|
180
|
+
blockingCount: 0,
|
|
181
|
+
onInput: () => {},
|
|
182
|
+
onSelectVerdict: () => {},
|
|
183
|
+
onSubmit: () => {},
|
|
184
|
+
onCancel: () => {},
|
|
185
|
+
}}
|
|
186
|
+
{...callbacks}
|
|
80
187
|
/>
|
|
81
188
|
</RailFrame>
|
|
82
189
|
),
|
|
@@ -88,17 +195,22 @@ export const Resolved: Story = {
|
|
|
88
195
|
render: () => (
|
|
89
196
|
<RailFrame>
|
|
90
197
|
<ReviewRail
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
198
|
+
session={fixturePlanSession({
|
|
199
|
+
status: "resolved",
|
|
200
|
+
verdict: {
|
|
201
|
+
kind: "approve",
|
|
202
|
+
summary: "",
|
|
203
|
+
feedback: "",
|
|
204
|
+
resolvedAt: "2026-01-01T00:00:00Z",
|
|
205
|
+
},
|
|
206
|
+
})}
|
|
207
|
+
authorNames={{}}
|
|
208
|
+
resolvedIds={new Set()}
|
|
209
|
+
railTab="review"
|
|
210
|
+
pendingCount={0}
|
|
211
|
+
cardEdit={null}
|
|
212
|
+
submitConfirm={null}
|
|
213
|
+
{...callbacks}
|
|
102
214
|
/>
|
|
103
215
|
</RailFrame>
|
|
104
216
|
),
|
|
@@ -7,14 +7,18 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import React, { forwardRef, useImperativeHandle, useRef } from "react";
|
|
10
|
-
import type
|
|
11
|
-
import { isAddressed, type Annotation, type ReviewSession } from "@cueloop/schema";
|
|
10
|
+
import { createTextAttributes, type ScrollBoxRenderable } from "@opentui/core";
|
|
11
|
+
import { isAddressed, isAgentNote, type Annotation, type ReviewSession } from "@cueloop/schema";
|
|
12
|
+
import type { CurationItem } from "../session-controller";
|
|
12
13
|
import type { Theme } from "../theme";
|
|
13
14
|
import { useComponentTheme } from "./theme-context";
|
|
14
15
|
import { Tabs, Tab, TabList } from "./primitives/Tabs";
|
|
15
16
|
import { Button } from "./primitives/Button";
|
|
17
|
+
import { Toolbar } from "./primitives/Toolbar";
|
|
16
18
|
import { AnnotationCard, type AnnotationDraft } from "./AnnotationCard";
|
|
17
19
|
import { ConfirmCard, type ConfirmCardProps } from "./ConfirmCard";
|
|
20
|
+
import { Card } from "./primitives/Card";
|
|
21
|
+
import { truncateToSingleLine } from "./truncate-text";
|
|
18
22
|
import { resolveDisplayName } from "../attribution";
|
|
19
23
|
|
|
20
24
|
export type RailTab = "review" | "agent";
|
|
@@ -35,6 +39,15 @@ export interface ReviewRailProps {
|
|
|
35
39
|
selectedId?: string;
|
|
36
40
|
/** Ids whose anchor resolved; null = orphan display off (diff view). */
|
|
37
41
|
resolvedIds: Set<string> | null;
|
|
42
|
+
/** Removed pieces (diff rejections / plan cuts) interleaved into the card stack. */
|
|
43
|
+
curationItems: CurationItem[];
|
|
44
|
+
/** The selected curation item, highlighted and the undo target. */
|
|
45
|
+
selectedCurationId?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Sort position per annotation id (diff row index / plan display index), so
|
|
48
|
+
* annotation and removal cards interleave in one position-ordered stack.
|
|
49
|
+
*/
|
|
50
|
+
annotationPositions?: Map<string, number>;
|
|
38
51
|
railTab: RailTab;
|
|
39
52
|
pendingCount: number;
|
|
40
53
|
cardEdit: RailCardEdit | null;
|
|
@@ -42,11 +55,15 @@ export interface ReviewRailProps {
|
|
|
42
55
|
onTabChange: (tab: RailTab) => void;
|
|
43
56
|
onSelectCard: (id: string) => void;
|
|
44
57
|
onActivateCard: (id: string) => void;
|
|
58
|
+
/** Select a curation item (reveals its diff row); pressing undo restores it. */
|
|
59
|
+
onSelectCuration: (id: string) => void;
|
|
60
|
+
/** Restore a curation item (the selected card's undo button, same as the u key). */
|
|
61
|
+
onUndoCuration: (id: string) => void;
|
|
45
62
|
onSubmitRequest: () => void;
|
|
46
63
|
/** Rail column width; the app derives it from the persisted review layout. */
|
|
47
64
|
width?: number;
|
|
48
65
|
/**
|
|
49
|
-
* When set, renders the muted
|
|
66
|
+
* When set, renders the muted `>` chevron pinned at the rail's bottom-left,
|
|
50
67
|
* one column from the divider. Clicking it collapses the panel to compact.
|
|
51
68
|
*/
|
|
52
69
|
onCollapse?: () => void;
|
|
@@ -64,13 +81,17 @@ export const ReviewRail = forwardRef<ReviewRailHandle, ReviewRailProps>(function
|
|
|
64
81
|
authorNames,
|
|
65
82
|
selectedId,
|
|
66
83
|
resolvedIds,
|
|
84
|
+
curationItems,
|
|
85
|
+
selectedCurationId,
|
|
86
|
+
annotationPositions,
|
|
67
87
|
railTab,
|
|
68
|
-
pendingCount,
|
|
69
88
|
cardEdit,
|
|
70
89
|
submitConfirm,
|
|
71
90
|
onTabChange,
|
|
72
91
|
onSelectCard,
|
|
73
92
|
onActivateCard,
|
|
93
|
+
onSelectCuration,
|
|
94
|
+
onUndoCuration,
|
|
74
95
|
onSubmitRequest,
|
|
75
96
|
width = 34,
|
|
76
97
|
onCollapse,
|
|
@@ -94,79 +115,202 @@ export const ReviewRail = forwardRef<ReviewRailHandle, ReviewRailProps>(function
|
|
|
94
115
|
},
|
|
95
116
|
}));
|
|
96
117
|
|
|
118
|
+
const renderAnnotationCard = (annotation: Annotation): React.ReactNode => (
|
|
119
|
+
<AnnotationCard
|
|
120
|
+
key={annotation.id}
|
|
121
|
+
id={`annotation-card-${annotation.id}`}
|
|
122
|
+
kind={annotation.kind}
|
|
123
|
+
quote={annotation.anchor.quote}
|
|
124
|
+
theme={theme}
|
|
125
|
+
saved={{
|
|
126
|
+
body: annotation.body,
|
|
127
|
+
isSelected: annotation.id === selectedId,
|
|
128
|
+
isOrphan: resolvedIds !== null && !resolvedIds.has(annotation.id),
|
|
129
|
+
isBlocking: annotationBlocking(annotation),
|
|
130
|
+
// border-title author: a collaborator's name, "agent" for agent notes, else "me"
|
|
131
|
+
author: annotation.author
|
|
132
|
+
? resolveDisplayName(annotation.author, session.participants, authorNames)
|
|
133
|
+
: isAgentNote(annotation)
|
|
134
|
+
? "agent"
|
|
135
|
+
: "me",
|
|
136
|
+
editing:
|
|
137
|
+
cardEdit && cardEdit.id === annotation.id
|
|
138
|
+
? {
|
|
139
|
+
text: cardEdit.text,
|
|
140
|
+
onInput: cardEdit.onInput,
|
|
141
|
+
onSave: cardEdit.onSave,
|
|
142
|
+
onCancel: cardEdit.onCancel,
|
|
143
|
+
}
|
|
144
|
+
: null,
|
|
145
|
+
onPress: () =>
|
|
146
|
+
annotation.id === selectedId
|
|
147
|
+
? onActivateCard(annotation.id)
|
|
148
|
+
: onSelectCard(annotation.id),
|
|
149
|
+
}}
|
|
150
|
+
/>
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
// one position-ordered stack: annotation and removal cards interleaved by their
|
|
154
|
+
// source line (diff row / plan block), original order breaking ties
|
|
155
|
+
const railEntries: { sort: number; order: number; node: React.ReactNode }[] = [
|
|
156
|
+
...openAnnotations.map((annotation, order) => ({
|
|
157
|
+
sort: annotationPositions?.get(annotation.id) ?? Number.MAX_SAFE_INTEGER,
|
|
158
|
+
order,
|
|
159
|
+
node: renderAnnotationCard(annotation),
|
|
160
|
+
})),
|
|
161
|
+
...curationItems.map((item, index) => ({
|
|
162
|
+
sort: item.revealIndex,
|
|
163
|
+
order: openAnnotations.length + index,
|
|
164
|
+
node: (
|
|
165
|
+
<RemovalCard
|
|
166
|
+
key={item.id}
|
|
167
|
+
item={item}
|
|
168
|
+
isSelected={item.id === selectedCurationId}
|
|
169
|
+
onSelect={onSelectCuration}
|
|
170
|
+
onUndo={onUndoCuration}
|
|
171
|
+
theme={theme}
|
|
172
|
+
/>
|
|
173
|
+
),
|
|
174
|
+
})),
|
|
175
|
+
].sort((left, right) => left.sort - right.sort || left.order - right.order);
|
|
176
|
+
|
|
97
177
|
return (
|
|
98
|
-
<box
|
|
99
|
-
|
|
178
|
+
<box
|
|
179
|
+
style={{
|
|
180
|
+
width,
|
|
181
|
+
backgroundColor: tokens.panel,
|
|
182
|
+
flexDirection: "column",
|
|
183
|
+
paddingLeft: 1,
|
|
184
|
+
paddingBottom: 1,
|
|
185
|
+
}}
|
|
186
|
+
>
|
|
187
|
+
<Tabs
|
|
188
|
+
selectedKey={railTab}
|
|
189
|
+
onSelectionChange={(key) => onTabChange(key as RailTab)}
|
|
190
|
+
theme={theme}
|
|
191
|
+
>
|
|
100
192
|
<TabList>
|
|
101
|
-
<Tab id="review">
|
|
193
|
+
<Tab id="review">Review</Tab>
|
|
102
194
|
<Tab id="agent">Agent</Tab>
|
|
103
195
|
</TabList>
|
|
104
196
|
</Tabs>
|
|
105
|
-
<text> </text>
|
|
106
197
|
{railTab === "agent" ? (
|
|
107
|
-
<
|
|
198
|
+
<box style={{ flexGrow: 1, flexDirection: "column", paddingLeft: 2 }}>
|
|
199
|
+
<text> </text>
|
|
200
|
+
<AgentTab session={session} theme={theme} />
|
|
201
|
+
</box>
|
|
202
|
+
) : openAnnotations.length === 0 && curationItems.length === 0 ? (
|
|
203
|
+
<box style={{ flexGrow: 1, alignItems: "center", justifyContent: "center" }}>
|
|
204
|
+
<text fg={tokens.textDim}>
|
|
205
|
+
{session.annotations.length === 0 ? "no annotations yet" : "all annotations addressed"}
|
|
206
|
+
</text>
|
|
207
|
+
</box>
|
|
108
208
|
) : (
|
|
109
|
-
|
|
110
|
-
{
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
text: cardEdit.text,
|
|
133
|
-
onInput: cardEdit.onInput,
|
|
134
|
-
onSave: cardEdit.onSave,
|
|
135
|
-
onCancel: cardEdit.onCancel,
|
|
136
|
-
}
|
|
137
|
-
: null,
|
|
138
|
-
onPress: () =>
|
|
139
|
-
annotation.id === selectedId ? onActivateCard(annotation.id) : onSelectCard(annotation.id),
|
|
140
|
-
}}
|
|
141
|
-
/>
|
|
142
|
-
))}
|
|
143
|
-
</scrollbox>
|
|
144
|
-
)}
|
|
145
|
-
<box style={{ flexGrow: 1 }} />
|
|
146
|
-
{/* the confirm card sits OUTSIDE the scrollbox: the annotation stack
|
|
147
|
-
above scrolls while the card stays pinned to the rail bottom */}
|
|
148
|
-
{session.status === "resolved" ? (
|
|
209
|
+
<box style={{ flexGrow: 1, flexDirection: "column" }}>
|
|
210
|
+
{addressedCount > 0 ? (
|
|
211
|
+
<text fg={tokens.textDim}>✓ {addressedCount} addressed by revision</text>
|
|
212
|
+
) : null}
|
|
213
|
+
<scrollbox ref={scrollRef} style={{ flexGrow: 1 }} focused={false}>
|
|
214
|
+
{railEntries.map((entry) => entry.node)}
|
|
215
|
+
</scrollbox>
|
|
216
|
+
</box>
|
|
217
|
+
)}
|
|
218
|
+
{/* the confirm card keeps full width, above the footer row */}
|
|
219
|
+
{railTab !== "agent" && submitConfirm ? (
|
|
220
|
+
<ConfirmCard {...submitConfirm} theme={theme} />
|
|
221
|
+
) : null}
|
|
222
|
+
{/* footer row: collapse chevron left-bound, the submit affordance inline
|
|
223
|
+
beside it, both on the rail's last row (the plan's bottom-border height) */}
|
|
224
|
+
<box style={{ flexDirection: "row", alignItems: "center", justifyContent: "space-between" }}>
|
|
225
|
+
{onCollapse ? (
|
|
226
|
+
<box onMouseUp={onCollapse}>
|
|
227
|
+
<text fg={tokens.textDim}>{">"}</text>
|
|
228
|
+
</box>
|
|
229
|
+
) : null}
|
|
230
|
+
{railTab !== "agent" && !submitConfirm ? (
|
|
231
|
+
session.status === "resolved" ? (
|
|
149
232
|
<text fg={tokens.green}>resolved: {session.verdict!.kind.replace("_", " ")}</text>
|
|
150
|
-
) : submitConfirm ? (
|
|
151
|
-
<ConfirmCard {...submitConfirm} theme={theme} />
|
|
152
233
|
) : (
|
|
153
234
|
<Button variant="accent-text" onPress={onSubmitRequest} theme={theme}>
|
|
154
|
-
|
|
235
|
+
Submit review
|
|
155
236
|
</Button>
|
|
156
|
-
)
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
{/* the collapse chevron: muted, left-bound, one column from the divider.
|
|
160
|
-
» points right - it hands the width back toward the plan */}
|
|
161
|
-
{onCollapse ? (
|
|
162
|
-
<box style={{ flexDirection: "row" }} onMouseUp={onCollapse}>
|
|
163
|
-
<text fg={tokens.textDim}>»</text>
|
|
164
|
-
</box>
|
|
165
|
-
) : null}
|
|
237
|
+
)
|
|
238
|
+
) : null}
|
|
239
|
+
</box>
|
|
166
240
|
</box>
|
|
167
241
|
);
|
|
168
242
|
});
|
|
169
243
|
|
|
244
|
+
/** A removal preview reads as removed content: struck through and dimmed. */
|
|
245
|
+
const REMOVAL_ATTRIBUTES = createTextAttributes({ strikethrough: true, dim: true });
|
|
246
|
+
|
|
247
|
+
/** Preview lines a removal card shows before it collapses to a "+N more" row. */
|
|
248
|
+
const REMOVAL_PREVIEW_LINES = 4;
|
|
249
|
+
|
|
250
|
+
/** Rough content width inside a removal card's border + padding, for truncation. */
|
|
251
|
+
const REMOVAL_PREVIEW_WIDTH = 24;
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* A removed piece as a full display card - the same border/padding/selection
|
|
255
|
+
* chrome as a saved annotation card - previewing the removed content struck
|
|
256
|
+
* through. Unified across diff (a curated-out hunk/change) and plan (a cut
|
|
257
|
+
* block). Clicking selects (App reveals its source line); the undo key restores.
|
|
258
|
+
*/
|
|
259
|
+
function RemovalCard({
|
|
260
|
+
item,
|
|
261
|
+
isSelected,
|
|
262
|
+
onSelect,
|
|
263
|
+
onUndo,
|
|
264
|
+
theme,
|
|
265
|
+
}: {
|
|
266
|
+
item: CurationItem;
|
|
267
|
+
isSelected: boolean;
|
|
268
|
+
onSelect: (id: string) => void;
|
|
269
|
+
onUndo: (id: string) => void;
|
|
270
|
+
theme?: Theme;
|
|
271
|
+
}): React.ReactNode {
|
|
272
|
+
const tokens = useComponentTheme(theme);
|
|
273
|
+
const previewLines = item.preview.slice(0, REMOVAL_PREVIEW_LINES);
|
|
274
|
+
const overflow = item.preview.length - previewLines.length;
|
|
275
|
+
// body = the struck-through preview (at least one row) + an overflow row, plus
|
|
276
|
+
// the undo button's own row when selected (mirrors the draft card's Save/Cancel)
|
|
277
|
+
const contentRows =
|
|
278
|
+
Math.max(1, previewLines.length) + (overflow > 0 ? 1 : 0) + (isSelected ? 1 : 0);
|
|
279
|
+
// border = "what + who": a diff reject or a plan cut, always the reviewer's own
|
|
280
|
+
const title = ` ${item.source === "diff" ? "REJECT" : "CUT"} · me `;
|
|
281
|
+
return (
|
|
282
|
+
<box id={`removal-card-${item.id}`} onMouseUp={() => onSelect(item.id)}>
|
|
283
|
+
<Card
|
|
284
|
+
title={title}
|
|
285
|
+
contentRows={contentRows}
|
|
286
|
+
borderColor={isSelected ? tokens.red : tokens.border}
|
|
287
|
+
backgroundColor="transparent"
|
|
288
|
+
theme={theme}
|
|
289
|
+
>
|
|
290
|
+
{previewLines.length === 0 ? (
|
|
291
|
+
<text fg={tokens.textDim}> </text>
|
|
292
|
+
) : (
|
|
293
|
+
previewLines.map((line, lineIndex) => (
|
|
294
|
+
<text key={lineIndex} fg={tokens.textDim}>
|
|
295
|
+
<span attributes={REMOVAL_ATTRIBUTES}>
|
|
296
|
+
{truncateToSingleLine(line, REMOVAL_PREVIEW_WIDTH)}
|
|
297
|
+
</span>
|
|
298
|
+
</text>
|
|
299
|
+
))
|
|
300
|
+
)}
|
|
301
|
+
{overflow > 0 ? <text fg={tokens.textDim}>{` …+${overflow} more`}</text> : null}
|
|
302
|
+
{isSelected ? (
|
|
303
|
+
<Toolbar>
|
|
304
|
+
<Button variant="solid" onPress={() => onUndo(item.id)} theme={theme}>
|
|
305
|
+
{" undo "}
|
|
306
|
+
</Button>
|
|
307
|
+
</Toolbar>
|
|
308
|
+
) : null}
|
|
309
|
+
</Card>
|
|
310
|
+
</box>
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
170
314
|
/** Agent tab: who submitted, where the session stands, which revision. */
|
|
171
315
|
function AgentTab({ session, theme }: { session: ReviewSession; theme?: Theme }): React.ReactNode {
|
|
172
316
|
const tokens = useComponentTheme(theme);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { DARK } from "../theme";
|
|
3
|
+
import { THEME_PRESETS } from "../theme-presets";
|
|
3
4
|
import type { Story, StoryMeta } from "./story";
|
|
4
5
|
import { SettingsDialog, type SettingsCategory } from "./SettingsDialog";
|
|
5
6
|
|
|
@@ -12,7 +13,12 @@ const CATEGORIES: SettingsCategory[] = [
|
|
|
12
13
|
description: "identity and submission",
|
|
13
14
|
rows: [
|
|
14
15
|
{ key: "displayName", label: "Display name", kind: "text" },
|
|
15
|
-
{
|
|
16
|
+
{
|
|
17
|
+
key: "autoClose",
|
|
18
|
+
label: "Auto-close on submit",
|
|
19
|
+
kind: "cycle",
|
|
20
|
+
options: ["off", "3s", "on"],
|
|
21
|
+
},
|
|
16
22
|
],
|
|
17
23
|
},
|
|
18
24
|
{
|
|
@@ -20,13 +26,37 @@ const CATEGORIES: SettingsCategory[] = [
|
|
|
20
26
|
name: "Display",
|
|
21
27
|
description: "plan width and chrome",
|
|
22
28
|
rows: [
|
|
23
|
-
{
|
|
29
|
+
{
|
|
30
|
+
key: "planWidth",
|
|
31
|
+
label: "Plan width",
|
|
32
|
+
kind: "cycle",
|
|
33
|
+
options: ["default", "wide", "full"],
|
|
34
|
+
},
|
|
24
35
|
{ key: "showLineNumbers", label: "Line numbers", kind: "toggle" },
|
|
25
36
|
],
|
|
26
37
|
},
|
|
38
|
+
{
|
|
39
|
+
id: "appearance",
|
|
40
|
+
name: "Appearance",
|
|
41
|
+
description: "the color theme",
|
|
42
|
+
rows: [
|
|
43
|
+
{
|
|
44
|
+
key: "theme",
|
|
45
|
+
label: "Theme",
|
|
46
|
+
kind: "cycle",
|
|
47
|
+
options: ["cueloop", "Rosé Pine Moon", "Nord"],
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
},
|
|
27
51
|
];
|
|
28
52
|
|
|
29
|
-
const VALUES = {
|
|
53
|
+
const VALUES = {
|
|
54
|
+
displayName: "amber-heron",
|
|
55
|
+
autoClose: "3s",
|
|
56
|
+
planWidth: "default",
|
|
57
|
+
showLineNumbers: true,
|
|
58
|
+
theme: "Rosé Pine Moon",
|
|
59
|
+
};
|
|
30
60
|
|
|
31
61
|
export const NavZone: Story = {
|
|
32
62
|
render: () => (
|
|
@@ -61,3 +91,21 @@ export const BodyZone: Story = {
|
|
|
61
91
|
expectedColors: [DARK.green],
|
|
62
92
|
size: { width: 100, height: 30 },
|
|
63
93
|
};
|
|
94
|
+
|
|
95
|
+
export const AppearanceThemed: Story = {
|
|
96
|
+
render: () => (
|
|
97
|
+
<SettingsDialog
|
|
98
|
+
isOpen
|
|
99
|
+
categories={CATEGORIES}
|
|
100
|
+
values={VALUES}
|
|
101
|
+
activeCategoryId="appearance"
|
|
102
|
+
activeRowIndex={0}
|
|
103
|
+
activeZone="body"
|
|
104
|
+
onCategorySelect={() => {}}
|
|
105
|
+
onRowActivate={() => {}}
|
|
106
|
+
theme={THEME_PRESETS["rose-pine-moon"]}
|
|
107
|
+
/>
|
|
108
|
+
),
|
|
109
|
+
expectedColors: [THEME_PRESETS["rose-pine-moon"].accent],
|
|
110
|
+
size: { width: 100, height: 30 },
|
|
111
|
+
};
|