@stevecastaneda/paseo-progress 1.14.2 → 1.14.4
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 +28 -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 +7 -0
- package/tsconfig.json +3 -10
- package/client/page-opener.ts +0 -9
- package/client/page-screen.tsx +0 -97
package/client/pdf.tsx
CHANGED
|
@@ -29,15 +29,29 @@ function PdfFile({ file, colors }: { file: PluginFile; colors: Colors }) {
|
|
|
29
29
|
const { messages: m } = useProgressLanguage();
|
|
30
30
|
const url = useFileUrl(file);
|
|
31
31
|
if (url.status === "loading") return <Loading />;
|
|
32
|
-
if (url.status === "error")
|
|
33
|
-
|
|
32
|
+
if (url.status === "error")
|
|
33
|
+
return (
|
|
34
|
+
<Note colors={colors} alert>
|
|
35
|
+
{m.previewError(errorText(url.error, m.unknownError))}
|
|
36
|
+
</Note>
|
|
37
|
+
);
|
|
38
|
+
return (
|
|
39
|
+
<View style={{ flex: 1, minHeight: 0 }}>
|
|
40
|
+
<DocumentFrame url={url.url} />
|
|
41
|
+
</View>
|
|
42
|
+
);
|
|
34
43
|
}
|
|
35
44
|
|
|
36
45
|
function Note({ colors, alert = false, children }: { colors: Colors; alert?: boolean; children: string }) {
|
|
37
46
|
return (
|
|
38
47
|
<View style={{ flex: 1, alignItems: "center", justifyContent: "center", padding: 24 }}>
|
|
39
|
-
<Text
|
|
40
|
-
|
|
48
|
+
<Text
|
|
49
|
+
accessibilityRole={alert ? "alert" : undefined}
|
|
50
|
+
selectable
|
|
51
|
+
style={{ maxWidth: 360, textAlign: "center", color: alert ? colors.statusDanger : colors.foregroundMuted, fontSize: 13, lineHeight: 19 }}
|
|
52
|
+
>
|
|
53
|
+
{children}
|
|
54
|
+
</Text>
|
|
41
55
|
</View>
|
|
42
56
|
);
|
|
43
57
|
}
|
package/client/pieces.ts
CHANGED
|
@@ -10,7 +10,12 @@ export type FileBlob = { readonly size: number };
|
|
|
10
10
|
|
|
11
11
|
export class FileChangedError extends Error {}
|
|
12
12
|
|
|
13
|
-
export async function loadPieces({
|
|
13
|
+
export async function loadPieces({
|
|
14
|
+
bytes,
|
|
15
|
+
mimeType,
|
|
16
|
+
readPiece,
|
|
17
|
+
onProgress,
|
|
18
|
+
}: {
|
|
14
19
|
bytes: number;
|
|
15
20
|
mimeType: string;
|
|
16
21
|
readPiece(offset: number): Promise<{ data: string; bytes: number }>;
|
package/client/pill-popover.tsx
CHANGED
|
@@ -44,31 +44,64 @@ export function AttentionPopover({ theme, host, layout, workspaceId, close, open
|
|
|
44
44
|
// Links and other files leave Paseo, so the popover closes for those.
|
|
45
45
|
const [previewing, setPreviewing] = useState<{ attachment: Attachment; group: Attachment[] } | null>(null);
|
|
46
46
|
const group = useRef<Attachment[]>([]);
|
|
47
|
-
const opener = useAttachmentOpener({
|
|
47
|
+
const opener = useAttachmentOpener({
|
|
48
|
+
workspaceId,
|
|
49
|
+
workspaceDirectory: root,
|
|
50
|
+
onPreview: (attachment) => setPreviewing({ attachment, group: galleryFiles(group.current) }),
|
|
51
|
+
});
|
|
48
52
|
return (
|
|
49
53
|
<View style={{ width: 360, maxWidth: "100%", gap: 8 }}>
|
|
50
54
|
{dashboard ? (
|
|
51
|
-
<AttentionList
|
|
55
|
+
<AttentionList
|
|
56
|
+
colors={colors}
|
|
57
|
+
dashboard={dashboard}
|
|
58
|
+
workspaceId={workspaceId}
|
|
59
|
+
root={root}
|
|
60
|
+
compact={layout.compact}
|
|
61
|
+
touch={touch}
|
|
62
|
+
onSurface1={onSurface1}
|
|
52
63
|
onCopy={async (question, letter) => {
|
|
53
64
|
await copy(question, letter);
|
|
54
65
|
close();
|
|
55
|
-
}}
|
|
66
|
+
}}
|
|
67
|
+
onOpenAttachment={(attachment, from) => {
|
|
56
68
|
group.current = from;
|
|
57
69
|
if (!isPreviewable(attachment)) close();
|
|
58
70
|
void opener.open(attachment);
|
|
59
|
-
}}
|
|
71
|
+
}}
|
|
72
|
+
/>
|
|
60
73
|
) : (
|
|
61
74
|
<Text style={{ color: colors.foregroundMuted, fontSize: 12, lineHeight: 18 }}>
|
|
62
75
|
{query.error ? m.readProgressError(errorText(query.error, m.unknownError)) : m.loading}
|
|
63
76
|
</Text>
|
|
64
77
|
)}
|
|
65
|
-
<StackedDialog
|
|
66
|
-
|
|
78
|
+
<StackedDialog
|
|
79
|
+
colors={colors}
|
|
80
|
+
now={Date.now()}
|
|
81
|
+
onClose={() => setPreviewing(null)}
|
|
82
|
+
context={{ workspaceId, workspaceDirectory: root }}
|
|
83
|
+
root={previewing ? { kind: "preview", ...previewing, fullScreen: false } : null}
|
|
84
|
+
/>
|
|
67
85
|
<View style={{ paddingTop: touch ? 12 : 8, borderTopWidth: 1, borderTopColor: colors.border }}>
|
|
68
86
|
{/* A full 44pt touch target on phones; desktop keeps the compact button. */}
|
|
69
|
-
<PressScale
|
|
70
|
-
|
|
71
|
-
|
|
87
|
+
<PressScale
|
|
88
|
+
accessibilityRole="button"
|
|
89
|
+
onPress={() => {
|
|
90
|
+
close();
|
|
91
|
+
openPanel();
|
|
92
|
+
}}
|
|
93
|
+
style={({ pressed }) => ({
|
|
94
|
+
flexDirection: "row",
|
|
95
|
+
justifyContent: "center",
|
|
96
|
+
alignItems: "center",
|
|
97
|
+
gap: touch ? 8 : 6,
|
|
98
|
+
paddingVertical: 6,
|
|
99
|
+
minHeight: touch ? TOUCH_TARGET : undefined,
|
|
100
|
+
borderRadius: touch ? 8 : 4,
|
|
101
|
+
...raised(colors),
|
|
102
|
+
backgroundColor: onSurface1 ? (pressed ? colors.surface1 : colors.surface2) : pressed ? colors.surface2 : colors.surface1,
|
|
103
|
+
})}
|
|
104
|
+
>
|
|
72
105
|
{/* Beside the chat on desktop; on a phone it opens as its own tab, shown with the tab's icon. */}
|
|
73
106
|
<Icon name={touch ? "ListChecks" : "PanelRight"} size={touch ? 16 : 12} color={colors.foreground} />
|
|
74
107
|
<Text style={{ color: colors.foreground, fontSize: touch ? 15 : 12, lineHeight: touch ? 20 : 16, fontWeight: "600" }}>{m.openProgress}</Text>
|
|
@@ -92,10 +125,14 @@ function List({ compact, maxHeight, surface, children }: { compact: boolean; max
|
|
|
92
125
|
};
|
|
93
126
|
return (
|
|
94
127
|
<View style={{ position: "relative" }}>
|
|
95
|
-
<ScrollView
|
|
128
|
+
<ScrollView
|
|
129
|
+
style={{ maxHeight }}
|
|
130
|
+
contentContainerStyle={{ gap: 12 }}
|
|
131
|
+
scrollEventThrottle={16}
|
|
96
132
|
onLayout={(event) => measure({ viewport: event.nativeEvent.layout.height })}
|
|
97
133
|
onContentSizeChange={(_, height) => measure({ content: height })}
|
|
98
|
-
onScroll={(event) => measure({ offset: event.nativeEvent.contentOffset.y })}
|
|
134
|
+
onScroll={(event) => measure({ offset: event.nativeEvent.contentOffset.y })}
|
|
135
|
+
>
|
|
99
136
|
{children}
|
|
100
137
|
</ScrollView>
|
|
101
138
|
{moreBelow ? <BottomFade color={surface} /> : null}
|
|
@@ -116,10 +153,22 @@ function BottomFade({ color }: { color: string }) {
|
|
|
116
153
|
);
|
|
117
154
|
}
|
|
118
155
|
|
|
119
|
-
export function AttentionList({
|
|
156
|
+
export function AttentionList({
|
|
157
|
+
colors,
|
|
158
|
+
dashboard,
|
|
159
|
+
workspaceId,
|
|
160
|
+
root,
|
|
161
|
+
compact,
|
|
162
|
+
touch,
|
|
163
|
+
onSurface1,
|
|
164
|
+
onCopy,
|
|
165
|
+
onOpenAttachment,
|
|
166
|
+
}: {
|
|
120
167
|
colors: Colors;
|
|
121
168
|
dashboard: Dashboard;
|
|
122
169
|
workspaceId: string;
|
|
170
|
+
// The worktree root the attachments' paths are relative to.
|
|
171
|
+
root: string;
|
|
123
172
|
compact: boolean;
|
|
124
173
|
touch: boolean;
|
|
125
174
|
onSurface1: boolean;
|
|
@@ -150,7 +199,9 @@ export function AttentionList({ colors, dashboard, workspaceId, compact, touch,
|
|
|
150
199
|
{stepping ? (
|
|
151
200
|
<View style={{ flexDirection: "row", alignItems: "center", justifyContent: "space-between", gap: 8 }}>
|
|
152
201
|
<Text style={{ color: colors.foregroundMuted, fontSize: 12, lineHeight: 17, fontVariant: ["tabular-nums"] }}>
|
|
153
|
-
{position[0]}
|
|
202
|
+
{position[0]}
|
|
203
|
+
<Text style={{ color: colors.foreground, fontWeight: "600" }}>{index + 1}</Text>
|
|
204
|
+
{position[1]}
|
|
154
205
|
</Text>
|
|
155
206
|
<View style={{ flexDirection: "row", gap: touch ? stepSlop * 2 : 4 }}>
|
|
156
207
|
<StepButton {...step} icon="ChevronLeft" label={m.previousQuestion} target={open[index - 1]} />
|
|
@@ -158,7 +209,12 @@ export function AttentionList({ colors, dashboard, workspaceId, compact, touch,
|
|
|
158
209
|
</View>
|
|
159
210
|
</View>
|
|
160
211
|
) : null}
|
|
161
|
-
<List
|
|
212
|
+
<List
|
|
213
|
+
key={question?.id}
|
|
214
|
+
compact={compact}
|
|
215
|
+
surface={onSurface1 ? colors.surface1 : colors.surface0}
|
|
216
|
+
maxHeight={stepping ? LIST_MAX_HEIGHT - stepSize(touch) - HEADER_GAP : LIST_MAX_HEIGHT}
|
|
217
|
+
>
|
|
162
218
|
{question ? (
|
|
163
219
|
<View style={{ gap: 6 }}>
|
|
164
220
|
<View style={{ flexDirection: "row", alignItems: "flex-start", gap: 8 }}>
|
|
@@ -168,10 +224,18 @@ export function AttentionList({ colors, dashboard, workspaceId, compact, touch,
|
|
|
168
224
|
<QuestionState colors={colors} question={question} />
|
|
169
225
|
</View>
|
|
170
226
|
</View>
|
|
171
|
-
<QuestionDetail
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
227
|
+
<QuestionDetail
|
|
228
|
+
colors={colors}
|
|
229
|
+
question={question}
|
|
230
|
+
now={now}
|
|
231
|
+
root={root}
|
|
232
|
+
onSurface1={onSurface1}
|
|
233
|
+
onOpenAttachment={onOpenAttachment}
|
|
234
|
+
onCopy={(letter) => {
|
|
235
|
+
advancePast(question);
|
|
236
|
+
onCopy(question, letter);
|
|
237
|
+
}}
|
|
238
|
+
/>
|
|
175
239
|
</View>
|
|
176
240
|
) : null}
|
|
177
241
|
{dashboard.stuck.length ? (
|
|
@@ -198,7 +262,15 @@ export function AttentionList({ colors, dashboard, workspaceId, compact, touch,
|
|
|
198
262
|
const stepSize = (touch: boolean) => (touch ? 36 : 24);
|
|
199
263
|
const stepSlop = (TOUCH_TARGET - stepSize(true)) / 2;
|
|
200
264
|
|
|
201
|
-
function StepButton({
|
|
265
|
+
function StepButton({
|
|
266
|
+
colors,
|
|
267
|
+
touch,
|
|
268
|
+
onSurface1,
|
|
269
|
+
icon,
|
|
270
|
+
label,
|
|
271
|
+
target,
|
|
272
|
+
onPress,
|
|
273
|
+
}: {
|
|
202
274
|
colors: Colors;
|
|
203
275
|
touch: boolean;
|
|
204
276
|
onSurface1: boolean;
|
|
@@ -210,10 +282,23 @@ function StepButton({ colors, touch, onSurface1, icon, label, target, onPress }:
|
|
|
210
282
|
const button = onSurface1 ? { rest: colors.surface2, pressed: colors.surface1 } : { rest: colors.surface1, pressed: colors.surface2 };
|
|
211
283
|
const size = stepSize(touch);
|
|
212
284
|
return (
|
|
213
|
-
<PressScale
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
285
|
+
<PressScale
|
|
286
|
+
accessibilityRole="button"
|
|
287
|
+
accessibilityLabel={label}
|
|
288
|
+
disabled={!target}
|
|
289
|
+
onPress={target ? () => onPress(target) : undefined}
|
|
290
|
+
hitSlop={touch ? stepSlop : undefined}
|
|
291
|
+
style={({ pressed }) => ({
|
|
292
|
+
width: size,
|
|
293
|
+
height: size,
|
|
294
|
+
alignItems: "center",
|
|
295
|
+
justifyContent: "center",
|
|
296
|
+
borderRadius: touch ? 8 : 4,
|
|
297
|
+
opacity: target ? 1 : 0.35,
|
|
298
|
+
...raised(colors),
|
|
299
|
+
backgroundColor: pressed ? button.pressed : button.rest,
|
|
300
|
+
})}
|
|
301
|
+
>
|
|
217
302
|
<Icon name={icon} size={touch ? 16 : 14} color={colors.foreground} />
|
|
218
303
|
</PressScale>
|
|
219
304
|
);
|
package/client/pills.tsx
CHANGED
|
@@ -53,8 +53,14 @@ export function contributePills(client: PluginClientContext) {
|
|
|
53
53
|
let stopped = false;
|
|
54
54
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
55
55
|
|
|
56
|
+
// The rule doesn't count <LocalizedPillIcon />, which closes over this scope, as a use of it.
|
|
57
|
+
// oxlint-disable-next-line unicorn/consistent-function-scoping
|
|
56
58
|
function PillIcon(props: PluginButtonIconProps) {
|
|
57
|
-
return
|
|
59
|
+
return (
|
|
60
|
+
<ProgressLanguageProvider>
|
|
61
|
+
<LocalizedPillIcon {...props} />
|
|
62
|
+
</ProgressLanguageProvider>
|
|
63
|
+
);
|
|
58
64
|
}
|
|
59
65
|
|
|
60
66
|
function LocalizedPillIcon(props: PluginButtonIconProps) {
|
|
@@ -65,15 +71,25 @@ export function contributePills(client: PluginClientContext) {
|
|
|
65
71
|
publish();
|
|
66
72
|
}, [language.locale]);
|
|
67
73
|
const icon = useSyncExternalStore(
|
|
68
|
-
(listener) => {
|
|
74
|
+
(listener) => {
|
|
75
|
+
listeners.add(listener);
|
|
76
|
+
return () => {
|
|
77
|
+
listeners.delete(listener);
|
|
78
|
+
};
|
|
79
|
+
},
|
|
69
80
|
() => pillOf(props.workspaceId)?.icon ?? "open",
|
|
70
81
|
);
|
|
71
82
|
return (
|
|
72
83
|
<IconSwap swapKey={icon} size={props.size}>
|
|
73
|
-
{icon === "stuck" ?
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
84
|
+
{icon === "stuck" ? (
|
|
85
|
+
<Icon name="OctagonAlert" size={props.size} color={props.theme.colors.statusDanger} />
|
|
86
|
+
) : icon === "questions" ? (
|
|
87
|
+
<Icon name="MessageCircleQuestion" size={props.size} color={props.color} />
|
|
88
|
+
) : icon === "started" ? (
|
|
89
|
+
<Spinner color={props.color} size={props.size} />
|
|
90
|
+
) : (
|
|
91
|
+
<Icon name="ListChecks" size={props.size} color={props.color} />
|
|
92
|
+
)}
|
|
77
93
|
</IconSwap>
|
|
78
94
|
);
|
|
79
95
|
}
|
|
@@ -83,12 +99,17 @@ export function contributePills(client: PluginClientContext) {
|
|
|
83
99
|
client.openPanel(panel.id, { workspaceId, location: panel.location });
|
|
84
100
|
};
|
|
85
101
|
|
|
86
|
-
const buttonFor = (workspaceId: string, mode: Pill["mode"], label: string) =>
|
|
87
|
-
|
|
88
|
-
|
|
102
|
+
const buttonFor = (workspaceId: string, mode: Pill["mode"], label: string) =>
|
|
103
|
+
mode === "attention"
|
|
104
|
+
? { title: messagesFor(locale).needsYou, icon: PillIcon, label, behavior: { kind: "popover" as const, Content: PillContent } }
|
|
105
|
+
: { title: messagesFor(locale).openProgress, icon: PillIcon, label, behavior: { kind: "action" as const, onPress: () => openPanel(workspaceId) } };
|
|
89
106
|
|
|
90
107
|
function PillContent(props: PluginButtonContentProps) {
|
|
91
|
-
return
|
|
108
|
+
return (
|
|
109
|
+
<ProgressLanguageProvider>
|
|
110
|
+
<AttentionPopover {...props} openPanel={() => openPanel(props.workspaceId)} />
|
|
111
|
+
</ProgressLanguageProvider>
|
|
112
|
+
);
|
|
92
113
|
}
|
|
93
114
|
|
|
94
115
|
const publish = () => {
|
|
@@ -149,7 +170,9 @@ export function contributePills(client: PluginClientContext) {
|
|
|
149
170
|
if (isIdle(result)) idleWait.set(workspaceId, IDLE_POLL_EVERY - 1);
|
|
150
171
|
else idleWait.delete(workspaceId);
|
|
151
172
|
publish();
|
|
152
|
-
} catch {
|
|
173
|
+
} catch {
|
|
174
|
+
/* Try again on the next poll. */
|
|
175
|
+
} finally {
|
|
153
176
|
pending.delete(workspaceId);
|
|
154
177
|
}
|
|
155
178
|
};
|
|
@@ -182,7 +205,7 @@ export function contributePills(client: PluginClientContext) {
|
|
|
182
205
|
select: (message) => (message.type === "workspace_update" ? message.payload : undefined),
|
|
183
206
|
snapshot: (entries) => {
|
|
184
207
|
const next = new Map((entries as Workspace[]).map((workspace) => [workspace.id, directoryOf(workspace)]));
|
|
185
|
-
for (const id of
|
|
208
|
+
for (const id of Array.from(workspaces.keys())) if (!next.has(id)) forget(id);
|
|
186
209
|
const hasAgent = new Set(agents.map((agent) => agent.workspaceId));
|
|
187
210
|
for (const [id, directory] of next) {
|
|
188
211
|
if (workspaces.get(id) !== directory) {
|