@stevecastaneda/paseo-progress 1.14.2 → 1.14.3
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/README.md +24 -17
- package/client/attachments.tsx +122 -28
- package/client/blocks.tsx +33 -8
- package/client/chat-row-view.tsx +19 -6
- package/client/chat-row.ts +13 -2
- package/client/dashboard-query.ts +9 -4
- package/client/deliverables.tsx +82 -17
- package/client/dialog-stack.tsx +104 -42
- package/client/directory.ts +1 -3
- package/client/errors.ts +6 -1
- package/client/history.tsx +233 -79
- package/client/i18n.ts +10 -5
- package/client/launcher.tsx +57 -15
- package/client/locales/en.ts +28 -22
- package/client/locales/zh-CN.ts +18 -17
- package/client/messages.ts +1 -0
- package/client/motion.tsx +38 -14
- package/client/panel-opened.ts +3 -1
- package/client/panel.tsx +442 -160
- package/client/pdf.tsx +18 -4
- package/client/pieces.ts +6 -1
- package/client/pill-popover.tsx +108 -23
- package/client/pills.tsx +35 -12
- package/client/preview.tsx +265 -79
- package/client/questions.tsx +173 -52
- package/client/row.tsx +14 -3
- package/client/section-title.tsx +11 -2
- package/client/settings.tsx +76 -24
- package/client/show-more.tsx +23 -6
- package/client/spinner.tsx +11 -7
- package/client/status-badge.tsx +9 -7
- package/client/tabs.tsx +98 -20
- package/client/ticket-dialog.tsx +241 -73
- package/client/time-bar.tsx +5 -2
- package/client/video.tsx +15 -6
- package/client/whats-new.tsx +94 -36
- package/client/when.tsx +2 -1
- package/client/write-up.tsx +79 -12
- package/dist/server/cli.js +199 -101
- package/index.client.tsx +28 -13
- package/index.server.ts +19 -1
- package/package.json +15 -14
- package/server/cli.ts +72 -32
- package/server/dashboard.ts +9 -13
- package/server/launcher.ts +33 -17
- package/server/lock.ts +14 -7
- package/server/open.ts +32 -98
- package/server/progress-file.ts +6 -6
- package/server/question-mode.ts +12 -6
- package/server/skill.ts +22 -16
- package/server/store.ts +26 -41
- package/server/system.ts +113 -0
- package/server/tool.ts +89 -44
- package/server/whats-new.ts +25 -13
- package/shared/attachments.ts +40 -8
- package/shared/blocks.ts +9 -6
- package/shared/dashboard.ts +46 -29
- package/shared/events.ts +12 -10
- package/shared/markdown.ts +16 -8
- package/shared/question-modes.ts +8 -5
- package/shared/rpc.ts +9 -3
- package/shared/show.ts +20 -8
- package/shared/ticket-story.ts +1 -3
- package/skills/paseo-progress/SKILL.md +1 -0
- package/tsconfig.json +3 -10
- package/client/page-opener.ts +0 -9
- package/client/page-screen.tsx +0 -97
package/client/questions.tsx
CHANGED
|
@@ -44,7 +44,13 @@ export interface AttachmentContext {
|
|
|
44
44
|
navigation?: PluginWorkspacePanelProps["navigation"];
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
export function QuestionsSection({
|
|
47
|
+
export function QuestionsSection({
|
|
48
|
+
colors,
|
|
49
|
+
questions,
|
|
50
|
+
answered,
|
|
51
|
+
now,
|
|
52
|
+
context,
|
|
53
|
+
}: {
|
|
48
54
|
colors: Colors;
|
|
49
55
|
questions: Question[];
|
|
50
56
|
// So a question answered while its dialog is open shows the answer instead of vanishing.
|
|
@@ -57,31 +63,53 @@ export function QuestionsSection({ colors, questions, answered, now, context }:
|
|
|
57
63
|
const copy = useCopy();
|
|
58
64
|
return (
|
|
59
65
|
<View style={{ margin: 12, marginBottom: 0, borderWidth: 1, borderColor: colors.accent, borderRadius: 6, overflow: "hidden" }}>
|
|
60
|
-
<View
|
|
66
|
+
<View
|
|
67
|
+
style={{
|
|
68
|
+
flexDirection: "row",
|
|
69
|
+
alignItems: "center",
|
|
70
|
+
flexWrap: "wrap",
|
|
71
|
+
gap: 8,
|
|
72
|
+
paddingHorizontal: 10,
|
|
73
|
+
paddingVertical: 8,
|
|
74
|
+
borderBottomWidth: 1,
|
|
75
|
+
borderBottomColor: colors.border,
|
|
76
|
+
}}
|
|
77
|
+
>
|
|
61
78
|
<Icon name="MessageCircleQuestion" size={14} color={colors.accent} />
|
|
62
|
-
<Text accessibilityRole="header" style={{ color: colors.foreground, fontSize: 13, lineHeight: 18, fontWeight: "600" }}>
|
|
63
|
-
|
|
79
|
+
<Text accessibilityRole="header" style={{ color: colors.foreground, fontSize: 13, lineHeight: 18, fontWeight: "600" }}>
|
|
80
|
+
{m.questions}
|
|
81
|
+
</Text>
|
|
82
|
+
<View
|
|
83
|
+
style={{ borderWidth: 1, borderColor: colors.accent, backgroundColor: colors.accent, borderRadius: 999, paddingHorizontal: 8, paddingVertical: 1 }}
|
|
84
|
+
>
|
|
64
85
|
<Text style={{ color: colors.accentForeground, fontSize: 11, lineHeight: 16 }}>{m.waitingCount(questions.length)}</Text>
|
|
65
86
|
</View>
|
|
66
87
|
</View>
|
|
67
88
|
{questions.map((question, index) => (
|
|
68
|
-
<QuestionRow
|
|
69
|
-
|
|
89
|
+
<QuestionRow
|
|
90
|
+
key={question.id}
|
|
91
|
+
colors={colors}
|
|
92
|
+
question={question}
|
|
93
|
+
first={index === 0}
|
|
94
|
+
onOpen={() => setOpenId(question.id)}
|
|
95
|
+
onCopy={() => void copy(question)}
|
|
96
|
+
/>
|
|
70
97
|
))}
|
|
71
|
-
<StackedDialog
|
|
72
|
-
|
|
98
|
+
<StackedDialog
|
|
99
|
+
colors={colors}
|
|
100
|
+
root={openId ? { kind: "question", id: openId } : null}
|
|
101
|
+
onClose={() => setOpenId(null)}
|
|
102
|
+
questions={[...questions, ...answered]}
|
|
103
|
+
now={now}
|
|
104
|
+
context={context}
|
|
105
|
+
/>
|
|
73
106
|
</View>
|
|
74
107
|
);
|
|
75
108
|
}
|
|
76
109
|
|
|
77
110
|
// Settled questions, one line each (title and the choice). Press a row for the rest.
|
|
78
111
|
// Rows only: the panel's tabbed card supplies the frame.
|
|
79
|
-
export function AnsweredQuestionsList({ colors, questions, now, context }: {
|
|
80
|
-
colors: Colors;
|
|
81
|
-
questions: Question[];
|
|
82
|
-
now: number;
|
|
83
|
-
context: AttachmentContext;
|
|
84
|
-
}) {
|
|
112
|
+
export function AnsweredQuestionsList({ colors, questions, now, context }: { colors: Colors; questions: Question[]; now: number; context: AttachmentContext }) {
|
|
85
113
|
const { messages: m } = useProgressLanguage();
|
|
86
114
|
const [openId, setOpenId] = useState<string | null>(null);
|
|
87
115
|
const paged = usePaged(questions);
|
|
@@ -92,12 +120,31 @@ export function AnsweredQuestionsList({ colors, questions, now, context }: {
|
|
|
92
120
|
const chosen = question.options.find((option) => option.letter === answer.choice);
|
|
93
121
|
return (
|
|
94
122
|
<View key={question.id} style={{ borderTopWidth: index ? 1 : 0, borderTopColor: colors.border }}>
|
|
95
|
-
<PressableRow
|
|
96
|
-
|
|
123
|
+
<PressableRow
|
|
124
|
+
colors={colors}
|
|
125
|
+
accessibilityRole="button"
|
|
126
|
+
accessibilityLabel={m.details(question.id)}
|
|
127
|
+
onPress={() => setOpenId(question.id)}
|
|
128
|
+
style={{ flexDirection: "row", alignItems: "center", gap: 8, paddingHorizontal: 10, paddingVertical: 8 }}
|
|
129
|
+
>
|
|
97
130
|
{/* Room for two-digit ids so titles line up; longer ones widen instead of wrapping. */}
|
|
98
|
-
<Text
|
|
131
|
+
<Text
|
|
132
|
+
numberOfLines={1}
|
|
133
|
+
style={{
|
|
134
|
+
color: colors.foregroundMuted,
|
|
135
|
+
fontSize: 12,
|
|
136
|
+
lineHeight: 17,
|
|
137
|
+
fontWeight: "600",
|
|
138
|
+
fontVariant: ["tabular-nums"],
|
|
139
|
+
minWidth: 30,
|
|
140
|
+
flexShrink: 0,
|
|
141
|
+
}}
|
|
142
|
+
>
|
|
143
|
+
{question.id}
|
|
144
|
+
</Text>
|
|
99
145
|
<Text numberOfLines={1} style={{ flex: 1, minWidth: 0, color: colors.foregroundMuted, fontSize: 12, lineHeight: 17 }}>
|
|
100
|
-
{question.title}
|
|
146
|
+
{question.title}
|
|
147
|
+
{" "}
|
|
101
148
|
<Text style={{ color: colors.statusSuccess, fontWeight: "600" }}>{answer.choice}</Text>
|
|
102
149
|
{chosen ? <Text style={{ color: colors.foreground }}> {chosen.label}</Text> : null}
|
|
103
150
|
</Text>
|
|
@@ -107,8 +154,14 @@ export function AnsweredQuestionsList({ colors, questions, now, context }: {
|
|
|
107
154
|
);
|
|
108
155
|
})}
|
|
109
156
|
<ShowMoreRow colors={colors} total={questions.length} paged={paged} />
|
|
110
|
-
<StackedDialog
|
|
111
|
-
|
|
157
|
+
<StackedDialog
|
|
158
|
+
colors={colors}
|
|
159
|
+
root={openId ? { kind: "question", id: openId } : null}
|
|
160
|
+
onClose={() => setOpenId(null)}
|
|
161
|
+
questions={questions}
|
|
162
|
+
now={now}
|
|
163
|
+
context={context}
|
|
164
|
+
/>
|
|
112
165
|
</View>
|
|
113
166
|
);
|
|
114
167
|
}
|
|
@@ -123,28 +176,26 @@ function AnswerSummary({ colors, question, now }: { colors: Colors; question: Qu
|
|
|
123
176
|
<Text style={{ color: colors.statusSuccess, fontWeight: "600" }}>{answer.choice}</Text>
|
|
124
177
|
{chosen ? ` ${chosen.label}.` : ""}
|
|
125
178
|
{answer.words ? <Text style={{ color: colors.foregroundMuted }}> “{answer.words}”</Text> : null}
|
|
126
|
-
<Text style={{ color: colors.foregroundMuted }}>
|
|
127
|
-
{answer.changedCourse ? m.changedCourse : ""}
|
|
128
|
-
</Text>
|
|
179
|
+
<Text style={{ color: colors.foregroundMuted }}>{answer.changedCourse ? m.changedCourse : ""}</Text>
|
|
129
180
|
</Text>
|
|
130
181
|
<Text style={{ color: colors.foregroundMuted, fontSize: 11, lineHeight: 16 }}>
|
|
131
|
-
{m.answerRecorded}
|
|
182
|
+
{m.answerRecorded}
|
|
183
|
+
<When colors={colors} iso={answer.at} now={now} />
|
|
184
|
+
{question.raisedBy ? m.raisedBy(question.raisedBy) : ""}
|
|
132
185
|
</Text>
|
|
133
186
|
</View>
|
|
134
187
|
);
|
|
135
188
|
}
|
|
136
189
|
|
|
137
|
-
function QuestionRow({ colors, question, first, onOpen, onCopy }: {
|
|
138
|
-
colors: Colors;
|
|
139
|
-
question: Question;
|
|
140
|
-
first: boolean;
|
|
141
|
-
onOpen(): void;
|
|
142
|
-
onCopy(): void;
|
|
143
|
-
}) {
|
|
190
|
+
function QuestionRow({ colors, question, first, onOpen, onCopy }: { colors: Colors; question: Question; first: boolean; onOpen(): void; onCopy(): void }) {
|
|
144
191
|
const { messages: m } = useProgressLanguage();
|
|
145
192
|
return (
|
|
146
193
|
<View style={{ flexDirection: "row", gap: 8, paddingHorizontal: 10, paddingVertical: 10, borderTopWidth: first ? 0 : 1, borderTopColor: colors.border }}>
|
|
147
|
-
<PressScale
|
|
194
|
+
<PressScale
|
|
195
|
+
accessibilityRole="button"
|
|
196
|
+
accessibilityLabel={m.copyQuestion(question.id)}
|
|
197
|
+
onPress={onCopy}
|
|
198
|
+
hitSlop={6}
|
|
148
199
|
outerStyle={{ alignSelf: "flex-start", marginTop: 1 }}
|
|
149
200
|
style={({ pressed }) => ({
|
|
150
201
|
// One width for every id, so the questions beside them start on one edge.
|
|
@@ -154,27 +205,45 @@ function QuestionRow({ colors, question, first, onOpen, onCopy }: {
|
|
|
154
205
|
borderRadius: 999,
|
|
155
206
|
backgroundColor: pressed ? colors.surface2 : colors.surface1,
|
|
156
207
|
...raised(colors),
|
|
157
|
-
})}
|
|
208
|
+
})}
|
|
209
|
+
>
|
|
158
210
|
<Text style={{ color: colors.foreground, fontSize: 13, lineHeight: 17, fontWeight: "600" }}>{question.id}</Text>
|
|
159
211
|
</PressScale>
|
|
160
|
-
<PressableRow
|
|
161
|
-
|
|
212
|
+
<PressableRow
|
|
213
|
+
colors={colors}
|
|
214
|
+
accessibilityRole="button"
|
|
215
|
+
accessibilityLabel={m.questionChoices(question.id)}
|
|
216
|
+
onPress={onOpen}
|
|
217
|
+
style={{ flex: 1, minWidth: 0, flexDirection: "row", alignItems: "flex-start", gap: 8, borderRadius: 4, margin: -4, padding: 4 }}
|
|
218
|
+
>
|
|
162
219
|
<View style={{ flex: 1, minWidth: 0, gap: 2 }}>
|
|
163
220
|
<Text style={{ color: colors.foreground, fontSize: 13, lineHeight: 19 }}>{question.question}</Text>
|
|
164
221
|
<QuestionState colors={colors} question={question} />
|
|
165
222
|
</View>
|
|
166
223
|
{/* Optical: centers the 14px chevron on the 19px first line. */}
|
|
167
|
-
<View style={{ paddingTop: 2.5 }}
|
|
224
|
+
<View style={{ paddingTop: 2.5 }}>
|
|
225
|
+
<Icon name="ChevronRight" size={14} color={colors.foregroundMuted} />
|
|
226
|
+
</View>
|
|
168
227
|
</PressableRow>
|
|
169
228
|
</View>
|
|
170
229
|
);
|
|
171
230
|
}
|
|
172
231
|
|
|
173
232
|
// A question dialog's body, also shown inside a ticket's dialog. `children` go at the end.
|
|
174
|
-
export function QuestionView({
|
|
233
|
+
export function QuestionView({
|
|
234
|
+
colors,
|
|
235
|
+
question,
|
|
236
|
+
now,
|
|
237
|
+
root,
|
|
238
|
+
onCopy,
|
|
239
|
+
onOpenAttachment,
|
|
240
|
+
children,
|
|
241
|
+
}: {
|
|
175
242
|
colors: Colors;
|
|
176
243
|
question: Question;
|
|
177
244
|
now: number;
|
|
245
|
+
// The worktree root the attachments' paths are relative to.
|
|
246
|
+
root: string;
|
|
178
247
|
onCopy(letter: string): void;
|
|
179
248
|
onOpenAttachment(attachment: Attachment, group: Attachment[]): void;
|
|
180
249
|
children?: React.ReactNode;
|
|
@@ -186,7 +255,7 @@ export function QuestionView({ colors, question, now, onCopy, onOpenAttachment,
|
|
|
186
255
|
{!question.answer ? <QuestionState colors={colors} question={question} /> : null}
|
|
187
256
|
</View>
|
|
188
257
|
{question.answer ? <AnswerSummary colors={colors} question={question} now={now} /> : null}
|
|
189
|
-
<QuestionDetail colors={colors} question={question} now={now} onSurface1 onCopy={onCopy} onOpenAttachment={onOpenAttachment} />
|
|
258
|
+
<QuestionDetail colors={colors} question={question} now={now} root={root} onSurface1 onCopy={onCopy} onOpenAttachment={onOpenAttachment} />
|
|
190
259
|
{children}
|
|
191
260
|
</View>
|
|
192
261
|
);
|
|
@@ -201,11 +270,33 @@ export function QuestionState({ colors, question }: { colors: Colors; question:
|
|
|
201
270
|
const pick = question.options.find((option) => option.letter === question.recommended);
|
|
202
271
|
return (
|
|
203
272
|
<Text style={{ color: colors.foregroundMuted, fontSize: 12, lineHeight: 17 }}>
|
|
204
|
-
{question.state === "building" ?
|
|
273
|
+
{question.state === "building" ? (
|
|
274
|
+
<>
|
|
275
|
+
{m.buildingWith}
|
|
276
|
+
<Text style={value}>
|
|
277
|
+
{question.recommended}
|
|
278
|
+
{pick ? `${m.colon}${pick.label}` : ""}
|
|
279
|
+
</Text>
|
|
280
|
+
</>
|
|
281
|
+
) : (
|
|
205
282
|
<>
|
|
206
|
-
{!question.options.length && question.recommended ?
|
|
283
|
+
{!question.options.length && question.recommended ? (
|
|
284
|
+
<>
|
|
285
|
+
{m.recommended}
|
|
286
|
+
{m.colon}
|
|
287
|
+
<Text style={value}>{question.recommended}</Text>
|
|
288
|
+
{m.sentenceBreak}
|
|
289
|
+
</>
|
|
290
|
+
) : null}
|
|
207
291
|
{question.state === "stopped" ? m.agentStopped : m.waitingForYou}
|
|
208
|
-
{question.big ?
|
|
292
|
+
{question.big ? (
|
|
293
|
+
<>
|
|
294
|
+
{m.sentenceBreak}
|
|
295
|
+
<Text style={value}>{m.big}</Text>
|
|
296
|
+
{m.colon}
|
|
297
|
+
{question.big}
|
|
298
|
+
</>
|
|
299
|
+
) : null}
|
|
209
300
|
</>
|
|
210
301
|
)}
|
|
211
302
|
</Text>
|
|
@@ -214,10 +305,19 @@ export function QuestionState({ colors, question }: { colors: Colors; question:
|
|
|
214
305
|
|
|
215
306
|
// `onSurface1` when drawn on a surface1 background (Paseo's dialogs), so the
|
|
216
307
|
// Copy buttons still stand off it.
|
|
217
|
-
export function QuestionDetail({
|
|
308
|
+
export function QuestionDetail({
|
|
309
|
+
colors,
|
|
310
|
+
question,
|
|
311
|
+
now,
|
|
312
|
+
root,
|
|
313
|
+
onCopy,
|
|
314
|
+
onOpenAttachment,
|
|
315
|
+
onSurface1 = false,
|
|
316
|
+
}: {
|
|
218
317
|
colors: Colors;
|
|
219
318
|
question: Question;
|
|
220
319
|
now: number;
|
|
320
|
+
root: string;
|
|
221
321
|
onCopy(letter: string): void;
|
|
222
322
|
onOpenAttachment(attachment: Attachment, group: Attachment[]): void;
|
|
223
323
|
onSurface1?: boolean;
|
|
@@ -233,7 +333,10 @@ export function QuestionDetail({ colors, question, now, onCopy, onOpenAttachment
|
|
|
233
333
|
const borderColor = question.answer ? (chosen ? colors.statusSuccess : colors.border) : recommended ? colors.accent : colors.border;
|
|
234
334
|
const settled = Boolean(question.answer);
|
|
235
335
|
return (
|
|
236
|
-
<View
|
|
336
|
+
<View
|
|
337
|
+
key={option.letter}
|
|
338
|
+
style={{ flexDirection: "row", alignItems: "flex-start", gap: 8, padding: 8, borderRadius: 12, borderWidth: 1, borderColor }}
|
|
339
|
+
>
|
|
237
340
|
<Text style={{ color: colors.foreground, fontSize: 13, lineHeight: 18, fontWeight: "600", width: 14 }}>{option.letter}</Text>
|
|
238
341
|
<View style={{ flex: 1, gap: 2 }}>
|
|
239
342
|
<Text selectable style={{ color: colors.foreground, fontSize: 13, lineHeight: 18 }}>
|
|
@@ -241,12 +344,30 @@ export function QuestionDetail({ colors, question, now, onCopy, onOpenAttachment
|
|
|
241
344
|
{chosen ? <Text style={{ color: colors.statusSuccess }}>{" " + m.yourAnswer}</Text> : null}
|
|
242
345
|
{recommended ? <Text style={{ color: settled ? colors.foregroundMuted : colors.accent }}>{" " + m.recommended}</Text> : null}
|
|
243
346
|
</Text>
|
|
244
|
-
{option.consequence ?
|
|
347
|
+
{option.consequence ? (
|
|
348
|
+
<Text selectable style={{ color: colors.foregroundMuted, fontSize: 12, lineHeight: 17 }}>
|
|
349
|
+
{option.consequence}
|
|
350
|
+
</Text>
|
|
351
|
+
) : null}
|
|
245
352
|
</View>
|
|
246
353
|
{!question.answer ? (
|
|
247
|
-
<PressScale
|
|
248
|
-
|
|
249
|
-
|
|
354
|
+
<PressScale
|
|
355
|
+
accessibilityRole="button"
|
|
356
|
+
accessibilityLabel={m.copyChoiceForAgent(question.id, option.letter)}
|
|
357
|
+
onPress={() => onCopy(option.letter)}
|
|
358
|
+
hitSlop={4}
|
|
359
|
+
style={({ pressed }) => ({
|
|
360
|
+
flexDirection: "row",
|
|
361
|
+
alignItems: "center",
|
|
362
|
+
gap: 4,
|
|
363
|
+
paddingLeft: 6,
|
|
364
|
+
paddingRight: 8,
|
|
365
|
+
paddingVertical: 3,
|
|
366
|
+
borderRadius: 4,
|
|
367
|
+
...raised(colors),
|
|
368
|
+
backgroundColor: pressed ? button.pressed : button.rest,
|
|
369
|
+
})}
|
|
370
|
+
>
|
|
250
371
|
<Icon name="Copy" size={12} color={colors.foreground} />
|
|
251
372
|
<Text style={{ color: colors.foreground, fontSize: 12, lineHeight: 16 }}>{m.copyChoice(question.id, option.letter)}</Text>
|
|
252
373
|
</PressScale>
|
|
@@ -254,12 +375,12 @@ export function QuestionDetail({ colors, question, now, onCopy, onOpenAttachment
|
|
|
254
375
|
</View>
|
|
255
376
|
);
|
|
256
377
|
})}
|
|
257
|
-
{question.background ?
|
|
258
|
-
|
|
259
|
-
) : null}
|
|
260
|
-
<AttachmentList colors={colors} attachments={questionAttachments(question)} onOpen={onOpenAttachment} />
|
|
378
|
+
{question.background ? <Markdown>{question.background}</Markdown> : null}
|
|
379
|
+
<AttachmentList colors={colors} attachments={questionAttachments(question)} root={root} onOpen={onOpenAttachment} />
|
|
261
380
|
<Text style={{ color: colors.foregroundMuted, fontSize: 11, lineHeight: 16 }}>
|
|
262
|
-
{m.asked}
|
|
381
|
+
{m.asked}
|
|
382
|
+
<When colors={colors} iso={question.askedAt} now={now} />
|
|
383
|
+
{question.raisedBy ? m.raisedBy(question.raisedBy) : ""}
|
|
263
384
|
</Text>
|
|
264
385
|
</View>
|
|
265
386
|
);
|
package/client/row.tsx
CHANGED
|
@@ -13,7 +13,14 @@ export function rowTint(colors: Colors, state: { hovered: boolean; pressed: bool
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
// A full-width row that opens something. `resting` is its background at rest.
|
|
16
|
-
export function PressableRow({
|
|
16
|
+
export function PressableRow({
|
|
17
|
+
colors,
|
|
18
|
+
onSurface1 = false,
|
|
19
|
+
transparentAtRest = true,
|
|
20
|
+
style,
|
|
21
|
+
children,
|
|
22
|
+
...props
|
|
23
|
+
}: Omit<PressableProps, "style" | "children"> & {
|
|
17
24
|
colors: Colors;
|
|
18
25
|
onSurface1?: boolean;
|
|
19
26
|
// Rows on a surface1 background rest transparent; the working ticket rests on surface1.
|
|
@@ -23,11 +30,15 @@ export function PressableRow({ colors, onSurface1 = false, transparentAtRest = t
|
|
|
23
30
|
}) {
|
|
24
31
|
const [hovered, setHovered] = useState(false);
|
|
25
32
|
return (
|
|
26
|
-
<Pressable
|
|
33
|
+
<Pressable
|
|
34
|
+
{...props}
|
|
35
|
+
onHoverIn={() => setHovered(true)}
|
|
36
|
+
onHoverOut={() => setHovered(false)}
|
|
27
37
|
style={({ pressed }) => {
|
|
28
38
|
const tint = rowTint(colors, { hovered, pressed }, onSurface1);
|
|
29
39
|
return [style, { backgroundColor: !hovered && !pressed && transparentAtRest ? "transparent" : tint }];
|
|
30
|
-
}}
|
|
40
|
+
}}
|
|
41
|
+
>
|
|
31
42
|
{children}
|
|
32
43
|
</Pressable>
|
|
33
44
|
);
|
package/client/section-title.tsx
CHANGED
|
@@ -6,7 +6,14 @@ import { Text, View, type ViewStyle } from "react-native";
|
|
|
6
6
|
type Colors = PluginWorkspacePanelProps["theme"]["colors"];
|
|
7
7
|
|
|
8
8
|
// A card's heading: a 14px Lucide icon beside the 13px semibold title.
|
|
9
|
-
export function SectionTitle({
|
|
9
|
+
export function SectionTitle({
|
|
10
|
+
colors,
|
|
11
|
+
icon,
|
|
12
|
+
title,
|
|
13
|
+
color,
|
|
14
|
+
style,
|
|
15
|
+
children,
|
|
16
|
+
}: {
|
|
10
17
|
colors: Colors;
|
|
11
18
|
icon: string;
|
|
12
19
|
title: string;
|
|
@@ -17,7 +24,9 @@ export function SectionTitle({ colors, icon, title, color, style, children }: {
|
|
|
17
24
|
return (
|
|
18
25
|
<View style={[{ flexDirection: "row", alignItems: "center", gap: 6, paddingHorizontal: 10, paddingVertical: 8 }, style]}>
|
|
19
26
|
<Icon name={icon} size={14} color={color ?? colors.foregroundMuted} />
|
|
20
|
-
<Text accessibilityRole="header" style={{ color: color ?? colors.foreground, fontSize: 13, lineHeight: 18, fontWeight: "600" }}>
|
|
27
|
+
<Text accessibilityRole="header" style={{ color: color ?? colors.foreground, fontSize: 13, lineHeight: 18, fontWeight: "600" }}>
|
|
28
|
+
{title}
|
|
29
|
+
</Text>
|
|
21
30
|
{children}
|
|
22
31
|
</View>
|
|
23
32
|
);
|
package/client/settings.tsx
CHANGED
|
@@ -14,7 +14,14 @@ import { useSkill, useToolsState } from "./launcher";
|
|
|
14
14
|
import { BlockView } from "./blocks";
|
|
15
15
|
|
|
16
16
|
export function QuestionSettings({ theme, host }: PluginSurfaceProps) {
|
|
17
|
-
return
|
|
17
|
+
return (
|
|
18
|
+
<>
|
|
19
|
+
<QuestionModeSettings theme={theme} />
|
|
20
|
+
<DisplaySettings theme={theme} />
|
|
21
|
+
<SkillSettings colors={theme.colors} hostId={host.id} />
|
|
22
|
+
<ToolsSettings colors={theme.colors} hostId={host.id} />
|
|
23
|
+
</>
|
|
24
|
+
);
|
|
18
25
|
}
|
|
19
26
|
|
|
20
27
|
type Colors = PluginSurfaceProps["theme"]["colors"];
|
|
@@ -38,11 +45,23 @@ function DisplaySettings({ theme }: Pick<PluginSurfaceProps, "theme">) {
|
|
|
38
45
|
const options = LANGUAGE_PREFERENCES.map((value) => ({ value, label: value === "system" ? m.systemLanguage : value === "en" ? "English" : "简体中文" }));
|
|
39
46
|
return (
|
|
40
47
|
<Section colors={theme.colors} icon="Languages" title={m.display}>
|
|
41
|
-
{settings.status === "loading" ?
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
48
|
+
{settings.status === "loading" ? (
|
|
49
|
+
<Text style={muted}>{m.loadingSettings}</Text>
|
|
50
|
+
) : settings.status !== "ready" ? (
|
|
51
|
+
<SettingsRecovery colors={theme.colors} settings={settings} />
|
|
52
|
+
) : (
|
|
53
|
+
<SettingsCard>
|
|
54
|
+
<SettingsSelect
|
|
55
|
+
label={m.language}
|
|
56
|
+
hint={m.languageHint}
|
|
57
|
+
value={settings.values.language}
|
|
58
|
+
options={options}
|
|
59
|
+
disabled={settings.saving}
|
|
60
|
+
error={settings.saveError}
|
|
61
|
+
onValueChange={(language) => void settings.save({ ...settings.values, language }, settings.revision)}
|
|
62
|
+
/>
|
|
63
|
+
</SettingsCard>
|
|
64
|
+
)}
|
|
46
65
|
</Section>
|
|
47
66
|
);
|
|
48
67
|
}
|
|
@@ -60,16 +79,29 @@ function SkillSettings({ colors, hostId }: { colors: Colors; hostId: string }) {
|
|
|
60
79
|
<SettingsCard>
|
|
61
80
|
<SettingsAction label={m.skillName} hint={document.data?.description || undefined} actionLabel={m.viewSkill} onPress={() => setOpen(true)} />
|
|
62
81
|
{skill.notice?.action ? (
|
|
63
|
-
<SettingsAction
|
|
64
|
-
|
|
82
|
+
<SettingsAction
|
|
83
|
+
label={skill.state === "outdated" ? m.skillLinkOutdated : m.skillNotInstalled}
|
|
84
|
+
hint={skill.notice.text}
|
|
85
|
+
actionLabel={skill.pending ? m.installing : skill.notice.action}
|
|
86
|
+
disabled={skill.pending}
|
|
87
|
+
onPress={skill.install}
|
|
88
|
+
/>
|
|
65
89
|
) : null}
|
|
66
90
|
</SettingsCard>
|
|
67
91
|
{/* Paseo draws the modal outside this tree, past the language context, so it only gets finished text. */}
|
|
68
92
|
<Modal title={m.skillName} icon={<Icon name="Sparkles" size={16} color={colors.foregroundMuted} />} open={open} onOpenChange={setOpen}>
|
|
69
93
|
<Modal.Content>
|
|
70
|
-
{document.error ?
|
|
71
|
-
|
|
72
|
-
|
|
94
|
+
{document.error ? (
|
|
95
|
+
<Text style={muted}>{m.readError(errorText(document.error, m.unknownError))}</Text>
|
|
96
|
+
) : !document.data ? (
|
|
97
|
+
<Text style={muted}>{m.loading}</Text>
|
|
98
|
+
) : (
|
|
99
|
+
<View style={{ gap: 8 }}>
|
|
100
|
+
{blocks.map((block, index) => (
|
|
101
|
+
<BlockView key={index} colors={colors} block={block} first={index === 0} />
|
|
102
|
+
))}
|
|
103
|
+
</View>
|
|
104
|
+
)}
|
|
73
105
|
</Modal.Content>
|
|
74
106
|
</Modal>
|
|
75
107
|
</Section>
|
|
@@ -98,26 +130,46 @@ function QuestionModeSettings({ theme }: Pick<PluginSurfaceProps, "theme">) {
|
|
|
98
130
|
const muted = { color: theme.colors.foregroundMuted, fontSize: 13, lineHeight: 19 };
|
|
99
131
|
return (
|
|
100
132
|
<Section colors={theme.colors} icon="MessageCircleQuestion" title={m.questions}>
|
|
101
|
-
{settings.status === "loading" ?
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
133
|
+
{settings.status === "loading" ? (
|
|
134
|
+
<Text style={muted}>{m.loadingSettings}</Text>
|
|
135
|
+
) : settings.status !== "ready" ? (
|
|
136
|
+
<SettingsRecovery colors={theme.colors} settings={settings} resetLabel={m.resetTo(m.questionModes[DEFAULT_QUESTION_MODE].label)} />
|
|
137
|
+
) : (
|
|
138
|
+
<SettingsCard>
|
|
139
|
+
<SettingsSelect
|
|
140
|
+
label={m.questionMode}
|
|
141
|
+
hint={m.questionModes[settings.values.mode].hint}
|
|
142
|
+
error={settings.saveError}
|
|
143
|
+
value={settings.values.mode}
|
|
144
|
+
options={QUESTION_MODES.map((value) => ({ value, label: m.questionModes[value].label }))}
|
|
145
|
+
disabled={settings.saving}
|
|
146
|
+
onValueChange={(mode) => void settings.save({ ...settings.values, mode }, settings.revision)}
|
|
147
|
+
/>
|
|
148
|
+
</SettingsCard>
|
|
149
|
+
)}
|
|
106
150
|
</Section>
|
|
107
151
|
);
|
|
108
152
|
}
|
|
109
153
|
|
|
110
|
-
function SettingsRecovery({
|
|
154
|
+
function SettingsRecovery({
|
|
155
|
+
colors,
|
|
156
|
+
settings,
|
|
157
|
+
resetLabel,
|
|
158
|
+
}: {
|
|
111
159
|
colors: Colors;
|
|
112
160
|
settings: { status: "error" | "invalid"; error: string; reload(): Promise<void>; reset(): Promise<boolean> };
|
|
113
161
|
resetLabel?: string;
|
|
114
162
|
}) {
|
|
115
163
|
const { messages: m } = useProgressLanguage();
|
|
116
|
-
return
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
<
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
164
|
+
return (
|
|
165
|
+
<>
|
|
166
|
+
<Text style={{ color: colors.foregroundMuted, fontSize: 13, lineHeight: 19 }}>{settings.error}</Text>
|
|
167
|
+
<SettingsCard>
|
|
168
|
+
<SettingsAction label={m.tryAgain} actionLabel={m.reload} onPress={settings.reload} />
|
|
169
|
+
{settings.status === "invalid" ? (
|
|
170
|
+
<SettingsAction label={resetLabel ?? m.resetTo(m.systemLanguage)} actionLabel={m.reset} onPress={settings.reset} />
|
|
171
|
+
) : null}
|
|
172
|
+
</SettingsCard>
|
|
173
|
+
</>
|
|
174
|
+
);
|
|
123
175
|
}
|
package/client/show-more.tsx
CHANGED
|
@@ -25,14 +25,31 @@ export function usePaged<T>(items: T[]): { shown: T[]; older: number; showOlder(
|
|
|
25
25
|
// The row under a paged list: "Show 10 older", or "Show fewer" once all are shown.
|
|
26
26
|
export function ShowMoreRow({ colors, total, paged }: { colors: Colors; total: number; paged: ReturnType<typeof usePaged> }) {
|
|
27
27
|
const { messages: m } = useProgressLanguage();
|
|
28
|
-
const more =
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
const more =
|
|
29
|
+
paged.older > 0
|
|
30
|
+
? { label: m.showOlder(Math.min(paged.older, PAGE)), icon: "ChevronDown", onPress: paged.showOlder }
|
|
31
|
+
: total > PAGE
|
|
32
|
+
? { label: m.showFewer, icon: "ChevronUp", onPress: paged.showFewer }
|
|
33
|
+
: null;
|
|
31
34
|
if (!more) return null;
|
|
32
35
|
return (
|
|
33
|
-
<PressableRow
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
<PressableRow
|
|
37
|
+
colors={colors}
|
|
38
|
+
accessibilityRole="button"
|
|
39
|
+
onPress={more.onPress}
|
|
40
|
+
style={{
|
|
41
|
+
flexDirection: "row",
|
|
42
|
+
alignItems: "center",
|
|
43
|
+
gap: 6,
|
|
44
|
+
paddingHorizontal: 10,
|
|
45
|
+
paddingVertical: 8,
|
|
46
|
+
borderTopWidth: 1,
|
|
47
|
+
borderTopColor: colors.border,
|
|
48
|
+
}}
|
|
49
|
+
>
|
|
50
|
+
<IconSwap swapKey={more.icon} size={14}>
|
|
51
|
+
<Icon name={more.icon} size={14} color={colors.foregroundMuted} />
|
|
52
|
+
</IconSwap>
|
|
36
53
|
<Text style={{ color: colors.foregroundMuted, fontSize: 12, lineHeight: 18, fontWeight: "600", fontVariant: ["tabular-nums"] }}>{more.label}</Text>
|
|
37
54
|
</PressableRow>
|
|
38
55
|
);
|
package/client/spinner.tsx
CHANGED
|
@@ -10,9 +10,11 @@ export function Spinner({ color, size = 12 }: { color: string; size?: number })
|
|
|
10
10
|
const { messages: m } = useProgressLanguage();
|
|
11
11
|
return (
|
|
12
12
|
<View accessibilityLabel={m.status.working} style={{ width: size, height: size, alignItems: "center", justifyContent: "center" }}>
|
|
13
|
-
{Platform.OS === "ios"
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
{Platform.OS === "ios" ? (
|
|
14
|
+
<ActivityIndicator size="small" color={color} style={{ transform: [{ scale: size / 20 }] }} />
|
|
15
|
+
) : (
|
|
16
|
+
<ActivityIndicator size={size} color={color} />
|
|
17
|
+
)}
|
|
16
18
|
</View>
|
|
17
19
|
);
|
|
18
20
|
}
|
|
@@ -23,10 +25,12 @@ export function StalledPulse({ color, size = 12 }: { color: string; size?: numbe
|
|
|
23
25
|
const { messages: m } = useProgressLanguage();
|
|
24
26
|
const opacity = useRef(new Animated.Value(1)).current;
|
|
25
27
|
useEffect(() => {
|
|
26
|
-
const loop = Animated.loop(
|
|
27
|
-
Animated.
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
const loop = Animated.loop(
|
|
29
|
+
Animated.sequence([
|
|
30
|
+
Animated.timing(opacity, { toValue: 0.35, duration: 700, easing: Easing.inOut(Easing.ease), useNativeDriver: nativeDriver() }),
|
|
31
|
+
Animated.timing(opacity, { toValue: 1, duration: 700, easing: Easing.inOut(Easing.ease), useNativeDriver: nativeDriver() }),
|
|
32
|
+
]),
|
|
33
|
+
);
|
|
30
34
|
loop.start();
|
|
31
35
|
return () => loop.stop();
|
|
32
36
|
}, [opacity]);
|
package/client/status-badge.tsx
CHANGED
|
@@ -9,13 +9,15 @@ type Colors = PluginWorkspacePanelProps["theme"]["colors"];
|
|
|
9
9
|
// `waiting`: held for another ticket. Shown as calm as "Not started", whatever the status.
|
|
10
10
|
export function StatusBadge({ colors, status, waiting = false }: { colors: Colors; status: TicketStatus; waiting?: boolean }) {
|
|
11
11
|
const { messages: m } = useProgressLanguage();
|
|
12
|
-
const tone = waiting
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
const tone = waiting
|
|
13
|
+
? { fg: colors.foregroundMuted, bg: "transparent", border: colors.border }
|
|
14
|
+
: {
|
|
15
|
+
done: { fg: colors.statusSuccess, bg: "transparent", border: colors.statusSuccess },
|
|
16
|
+
working: { fg: colors.accentForeground, bg: colors.accent, border: colors.accent },
|
|
17
|
+
blocked: { fg: colors.statusDanger, bg: "transparent", border: colors.statusDanger },
|
|
18
|
+
skipped: { fg: colors.foregroundMuted, bg: "transparent", border: colors.border },
|
|
19
|
+
not_started: { fg: colors.foregroundMuted, bg: "transparent", border: colors.border },
|
|
20
|
+
}[status];
|
|
19
21
|
return (
|
|
20
22
|
<View style={{ borderWidth: 1, borderColor: tone.border, backgroundColor: tone.bg, borderRadius: 999, paddingHorizontal: 8, paddingVertical: 1 }}>
|
|
21
23
|
<Text style={{ color: tone.fg, fontSize: 11, lineHeight: 16 }}>{waiting ? m.waiting : m.status[status]}</Text>
|