@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.
Files changed (67) hide show
  1. package/README.md +24 -17
  2. package/client/attachments.tsx +122 -28
  3. package/client/blocks.tsx +33 -8
  4. package/client/chat-row-view.tsx +19 -6
  5. package/client/chat-row.ts +13 -2
  6. package/client/dashboard-query.ts +9 -4
  7. package/client/deliverables.tsx +82 -17
  8. package/client/dialog-stack.tsx +104 -42
  9. package/client/directory.ts +1 -3
  10. package/client/errors.ts +6 -1
  11. package/client/history.tsx +233 -79
  12. package/client/i18n.ts +10 -5
  13. package/client/launcher.tsx +57 -15
  14. package/client/locales/en.ts +28 -22
  15. package/client/locales/zh-CN.ts +18 -17
  16. package/client/messages.ts +1 -0
  17. package/client/motion.tsx +38 -14
  18. package/client/panel-opened.ts +3 -1
  19. package/client/panel.tsx +442 -160
  20. package/client/pdf.tsx +18 -4
  21. package/client/pieces.ts +6 -1
  22. package/client/pill-popover.tsx +108 -23
  23. package/client/pills.tsx +35 -12
  24. package/client/preview.tsx +265 -79
  25. package/client/questions.tsx +173 -52
  26. package/client/row.tsx +14 -3
  27. package/client/section-title.tsx +11 -2
  28. package/client/settings.tsx +76 -24
  29. package/client/show-more.tsx +23 -6
  30. package/client/spinner.tsx +11 -7
  31. package/client/status-badge.tsx +9 -7
  32. package/client/tabs.tsx +98 -20
  33. package/client/ticket-dialog.tsx +241 -73
  34. package/client/time-bar.tsx +5 -2
  35. package/client/video.tsx +15 -6
  36. package/client/whats-new.tsx +94 -36
  37. package/client/when.tsx +2 -1
  38. package/client/write-up.tsx +79 -12
  39. package/dist/server/cli.js +199 -101
  40. package/index.client.tsx +28 -13
  41. package/index.server.ts +19 -1
  42. package/package.json +15 -14
  43. package/server/cli.ts +72 -32
  44. package/server/dashboard.ts +9 -13
  45. package/server/launcher.ts +33 -17
  46. package/server/lock.ts +14 -7
  47. package/server/open.ts +32 -98
  48. package/server/progress-file.ts +6 -6
  49. package/server/question-mode.ts +12 -6
  50. package/server/skill.ts +22 -16
  51. package/server/store.ts +26 -41
  52. package/server/system.ts +113 -0
  53. package/server/tool.ts +89 -44
  54. package/server/whats-new.ts +25 -13
  55. package/shared/attachments.ts +40 -8
  56. package/shared/blocks.ts +9 -6
  57. package/shared/dashboard.ts +46 -29
  58. package/shared/events.ts +12 -10
  59. package/shared/markdown.ts +16 -8
  60. package/shared/question-modes.ts +8 -5
  61. package/shared/rpc.ts +9 -3
  62. package/shared/show.ts +20 -8
  63. package/shared/ticket-story.ts +1 -3
  64. package/skills/paseo-progress/SKILL.md +1 -0
  65. package/tsconfig.json +3 -10
  66. package/client/page-opener.ts +0 -9
  67. package/client/page-screen.tsx +0 -97
@@ -38,38 +38,84 @@ export function WhatsNewBanner({ colors, host }: { colors: Colors; host: PluginW
38
38
 
39
39
  return (
40
40
  <>
41
- <Presence show={Boolean(whatsNew)}>{whatsNew ? (
42
- // The headline gets the full width, with the button under it on the text's edge.
43
- <View style={{ gap: 8, margin: 12, marginBottom: 0, padding: 8, borderRadius: 12, ...raised(colors), backgroundColor: colors.surface1 }}>
44
- <View style={{ flexDirection: "row", alignItems: "flex-start", gap: 10 }}>
45
- <View style={{ height: 17, justifyContent: "center" }}><Icon name="Gift" size={14} color={colors.foregroundMuted} /></View>
46
- <Text selectable style={{ flex: 1, color: colors.foregroundMuted, fontSize: 12, lineHeight: 17 }}>
47
- <Text style={{ color: colors.foreground, fontWeight: "500" }}>{whatsNew.releases.length > 1 ? m.updatedTo(whatsNew.version) : m.newVersion(whatsNew.version)}</Text> {whatsNew.headline}
48
- </Text>
49
- {/* Centered on the first line without making it taller. */}
50
- <PressScale accessibilityRole="button" accessibilityLabel={m.dismissWhatsNew} hitSlop={6} onPress={() => close(whatsNew.version)}
51
- style={({ pressed, hovered }) => ({ marginVertical: -4.5, padding: 5, borderRadius: 4, backgroundColor: pressed ? colors.border : hovered ? colors.surface2 : "transparent" })}>
52
- <Icon name="X" size={16} color={colors.foregroundMuted} />
41
+ <Presence show={Boolean(whatsNew)}>
42
+ {whatsNew ? (
43
+ // The headline gets the full width, with the button under it on the text's edge.
44
+ <View style={{ gap: 8, margin: 12, marginBottom: 0, padding: 8, borderRadius: 12, ...raised(colors), backgroundColor: colors.surface1 }}>
45
+ <View style={{ flexDirection: "row", alignItems: "flex-start", gap: 10 }}>
46
+ <View style={{ height: 17, justifyContent: "center" }}>
47
+ <Icon name="Gift" size={14} color={colors.foregroundMuted} />
48
+ </View>
49
+ <Text selectable style={{ flex: 1, color: colors.foregroundMuted, fontSize: 12, lineHeight: 17 }}>
50
+ <Text style={{ color: colors.foreground, fontWeight: "500" }}>
51
+ {whatsNew.releases.length > 1 ? m.updatedTo(whatsNew.version) : m.newVersion(whatsNew.version)}
52
+ </Text>{" "}
53
+ {whatsNew.headline}
54
+ </Text>
55
+ {/* Centered on the first line without making it taller. */}
56
+ <PressScale
57
+ accessibilityRole="button"
58
+ accessibilityLabel={m.dismissWhatsNew}
59
+ hitSlop={6}
60
+ onPress={() => close(whatsNew.version)}
61
+ style={({ pressed, hovered }) => ({
62
+ marginVertical: -4.5,
63
+ padding: 5,
64
+ borderRadius: 4,
65
+ backgroundColor: pressed ? colors.border : hovered ? colors.surface2 : "transparent",
66
+ })}
67
+ >
68
+ <Icon name="X" size={16} color={colors.foregroundMuted} />
69
+ </PressScale>
70
+ </View>
71
+ <PressScale
72
+ accessibilityRole="button"
73
+ onPress={() => {
74
+ setShown(whatsNew);
75
+ setOpen(true);
76
+ }}
77
+ style={({ pressed, hovered }) => ({
78
+ alignSelf: "flex-start",
79
+ marginStart: 24,
80
+ paddingHorizontal: 10,
81
+ paddingVertical: 5,
82
+ borderRadius: 4,
83
+ backgroundColor: colors.accent,
84
+ opacity: pressed ? 0.7 : hovered ? 0.85 : 1,
85
+ })}
86
+ >
87
+ <Text style={{ color: colors.accentForeground, fontSize: 12, lineHeight: 16, fontWeight: "600" }}>{m.seeWhatsNew}</Text>
53
88
  </PressScale>
54
89
  </View>
55
- <PressScale accessibilityRole="button" onPress={() => { setShown(whatsNew); setOpen(true); }}
56
- style={({ pressed, hovered }) => ({ alignSelf: "flex-start", marginStart: 24, paddingHorizontal: 10, paddingVertical: 5, borderRadius: 4, backgroundColor: colors.accent, opacity: pressed ? 0.7 : hovered ? 0.85 : 1 })}>
57
- <Text style={{ color: colors.accentForeground, fontSize: 12, lineHeight: 16, fontWeight: "600" }}>{m.seeWhatsNew}</Text>
58
- </PressScale>
59
- </View>
60
- ) : null}</Presence>
61
- <Modal title={!shown ? "" : shown.releases.length > 1 ? m.whatsNewAll : m.whatsNewTitle(shown.version)} icon={<Icon name="Gift" size={16} color={colors.foregroundMuted} />}
62
- open={open} onOpenChange={(next) => { if (!next) closeDialog(); }}>
90
+ ) : null}
91
+ </Presence>
92
+ <Modal
93
+ title={!shown ? "" : shown.releases.length > 1 ? m.whatsNewAll : m.whatsNewTitle(shown.version)}
94
+ icon={<Icon name="Gift" size={16} color={colors.foregroundMuted} />}
95
+ open={open}
96
+ onOpenChange={(next) => {
97
+ if (!next) closeDialog();
98
+ }}
99
+ >
63
100
  <Modal.Content>
64
- <ProgressLanguageProvider>
65
- {shown ? <Releases colors={colors} releases={shown.releases} /> : null}
66
- <View style={{ flexDirection: "row", justifyContent: "flex-end", marginTop: 8 }}>
67
- <PressScale accessibilityRole="button" onPress={closeDialog}
68
- style={({ pressed, hovered }) => ({ paddingHorizontal: 12, paddingVertical: 6, borderRadius: 6, backgroundColor: colors.accent, opacity: pressed ? 0.7 : hovered ? 0.85 : 1 })}>
69
- <Text style={{ color: colors.accentForeground, fontSize: 12, lineHeight: 16, fontWeight: "600" }}>{m.gotIt}</Text>
70
- </PressScale>
71
- </View>
72
- </ProgressLanguageProvider>
101
+ <ProgressLanguageProvider>
102
+ {shown ? <Releases colors={colors} releases={shown.releases} /> : null}
103
+ <View style={{ flexDirection: "row", justifyContent: "flex-end", marginTop: 8 }}>
104
+ <PressScale
105
+ accessibilityRole="button"
106
+ onPress={closeDialog}
107
+ style={({ pressed, hovered }) => ({
108
+ paddingHorizontal: 12,
109
+ paddingVertical: 6,
110
+ borderRadius: 6,
111
+ backgroundColor: colors.accent,
112
+ opacity: pressed ? 0.7 : hovered ? 0.85 : 1,
113
+ })}
114
+ >
115
+ <Text style={{ color: colors.accentForeground, fontSize: 12, lineHeight: 16, fontWeight: "600" }}>{m.gotIt}</Text>
116
+ </PressScale>
117
+ </View>
118
+ </ProgressLanguageProvider>
73
119
  </Modal.Content>
74
120
  </Modal>
75
121
  </>
@@ -84,7 +130,9 @@ function Releases({ colors, releases }: { colors: Colors; releases: WhatsNewRele
84
130
  <View style={{ gap: 40 }}>
85
131
  {releases.map((release) => (
86
132
  <View key={release.version} style={{ gap: 12 }}>
87
- <Text accessibilityRole="header" selectable style={{ color: colors.foreground, fontSize: 12, lineHeight: 16, fontWeight: "600" }}>{release.version}</Text>
133
+ <Text accessibilityRole="header" selectable style={{ color: colors.foreground, fontSize: 12, lineHeight: 16, fontWeight: "600" }}>
134
+ {release.version}
135
+ </Text>
88
136
  <Notes colors={colors} notes={release.notes} />
89
137
  </View>
90
138
  ))}
@@ -96,9 +144,13 @@ function Releases({ colors, releases }: { colors: Colors; releases: WhatsNewRele
96
144
  // written without titles stays a bulleted list.
97
145
  function Notes({ colors, notes }: { colors: Colors; notes: WhatsNewNote[] }) {
98
146
  if (!notes.some((note) => note.title || note.kind)) {
99
- return <View style={{ gap: 12 }}>{notes.map((note, index) => (
100
- <BlockView key={index} colors={colors} block={{ kind: "item", marker: "•", depth: 0, spans: spans(note.body) }} first={index === 0} />
101
- ))}</View>;
147
+ return (
148
+ <View style={{ gap: 12 }}>
149
+ {notes.map((note, index) => (
150
+ <BlockView key={index} colors={colors} block={{ kind: "item", marker: "•", depth: 0, spans: spans(note.body) }} first={index === 0} />
151
+ ))}
152
+ </View>
153
+ );
102
154
  }
103
155
  return (
104
156
  <View style={{ gap: 20 }}>
@@ -107,7 +159,11 @@ function Notes({ colors, notes }: { colors: Colors; notes: WhatsNewNote[] }) {
107
159
  {note.title || note.kind ? (
108
160
  <View style={{ flexDirection: "row", alignItems: "flex-start", gap: 8 }}>
109
161
  {note.kind ? <KindTag colors={colors} kind={note.kind} /> : null}
110
- {note.title ? <Text accessibilityRole="header" selectable style={{ flex: 1, color: colors.foreground, fontSize: 14, lineHeight: 20, fontWeight: "600" }}>{note.title}</Text> : null}
162
+ {note.title ? (
163
+ <Text accessibilityRole="header" selectable style={{ flex: 1, color: colors.foreground, fontSize: 14, lineHeight: 20, fontWeight: "600" }}>
164
+ {note.title}
165
+ </Text>
166
+ ) : null}
111
167
  </View>
112
168
  ) : null}
113
169
  {note.body ? (
@@ -124,7 +180,7 @@ function Notes({ colors, notes }: { colors: Colors; notes: WhatsNewNote[] }) {
124
180
  // Paseo has no violet for plugins, so it's Paseo's own "merged" purple, which
125
181
  // shares the status colors' lightness and chroma.
126
182
  const TONE_COLOR: Record<NoteTone, (colors: Colors) => string> = {
127
- violet: (colors) => isLight(colors) ? "#7347af" : "#a890d5",
183
+ violet: (colors) => (isLight(colors) ? "#7347af" : "#a890d5"),
128
184
  green: (colors) => colors.statusSuccess,
129
185
  amber: (colors) => colors.statusWarning,
130
186
  };
@@ -136,7 +192,9 @@ function KindTag({ colors, kind }: { colors: Colors; kind: NoteKind }) {
136
192
  const color = TONE_COLOR[NOTE_KINDS[kind].tone](colors);
137
193
  return (
138
194
  <View style={{ marginTop: 1, minWidth: 64, borderRadius: 4, paddingHorizontal: 7, backgroundColor: tint(color, colors) }}>
139
- <Text numberOfLines={1} style={{ textAlign: "center", color, fontSize: 11, lineHeight: 18, fontWeight: "500" }}>{m.noteKinds[kind]}</Text>
195
+ <Text numberOfLines={1} style={{ textAlign: "center", color, fontSize: 11, lineHeight: 18, fontWeight: "500" }}>
196
+ {m.noteKinds[kind]}
197
+ </Text>
140
198
  </View>
141
199
  );
142
200
  }
package/client/when.tsx CHANGED
@@ -21,7 +21,8 @@ export function When({ colors, iso, now }: { colors: Colors; iso: string; now: n
21
21
  if (useNarrow()) return <Text>{ago}</Text>;
22
22
  return (
23
23
  <Text>
24
- {formatClientClock(locale, iso)}<Text style={{ color: colors.foregroundMuted, opacity: 0.7 }}>{m.parenthetical(ago)}</Text>
24
+ {formatClientClock(locale, iso)}
25
+ <Text style={{ color: colors.foregroundMuted, opacity: 0.7 }}>{m.parenthetical(ago)}</Text>
25
26
  </Text>
26
27
  );
27
28
  }
@@ -2,11 +2,11 @@ import { type PluginWorkspacePanelProps, useRpc } from "@getpaseo/plugin/client"
2
2
  import { Icon, Markdown } from "@getpaseo/plugin/client/ui";
3
3
  import { useQuery } from "@tanstack/react-query";
4
4
  import React, { useMemo, useState } from "react";
5
- import { Platform, Text, View } from "react-native";
5
+ import { Text, View } from "react-native";
6
6
  import { previewKind } from "../shared/attachments";
7
7
  import { previewDeliverable } from "../shared/rpc";
8
8
  import { flattenHeadings, dropTitle, markdownExcerpt, textExcerpt } from "../shared/markdown";
9
- import { type Attachment, attachmentIcon } from "./attachments";
9
+ import { type Attachment, absolutePath, attachmentIcon, followPageLink, PageLink, pageLinkRef } from "./attachments";
10
10
  import { PressableRow } from "./row";
11
11
  import { errorText } from "./errors";
12
12
  import { useProgressLanguage } from "./language";
@@ -18,7 +18,13 @@ const EXCERPT_CHARS = 280;
18
18
 
19
19
  // What the ticket is: the start of its write-up in a quiet inset, with the
20
20
  // rest a press away. A link or a file that isn't text shows only its name.
21
- export function WriteUp({ colors, source, workspaceId, workspaceDirectory, onOpen }: {
21
+ export function WriteUp({
22
+ colors,
23
+ source,
24
+ workspaceId,
25
+ workspaceDirectory,
26
+ onOpen,
27
+ }: {
22
28
  colors: Colors;
23
29
  source: Attachment;
24
30
  workspaceId: string;
@@ -40,7 +46,13 @@ export function WriteUp({ colors, source, workspaceId, workspaceDirectory, onOpe
40
46
  const parts = useMemo(() => {
41
47
  if (data?.kind === "markdown") {
42
48
  const excerpt = markdownExcerpt(data.markdown, EXCERPT_CHARS);
43
- return { markdown: true, excerpt: flattenHeadings(excerpt.text, WRITE_UP_HEADING), whole: flattenHeadings(dropTitle(data.markdown), WRITE_UP_HEADING), more: excerpt.more, truncated: data.truncated };
49
+ return {
50
+ markdown: true,
51
+ excerpt: flattenHeadings(excerpt.text, WRITE_UP_HEADING),
52
+ whole: flattenHeadings(dropTitle(data.markdown), WRITE_UP_HEADING),
53
+ more: excerpt.more,
54
+ truncated: data.truncated,
55
+ };
44
56
  }
45
57
  if (data?.kind === "text") {
46
58
  const excerpt = textExcerpt(data.text, EXCERPT_CHARS);
@@ -49,16 +61,36 @@ export function WriteUp({ colors, source, workspaceId, workspaceDirectory, onOpe
49
61
  return null;
50
62
  }, [data]);
51
63
  const shown = parts ? (all ? parts.whole : parts.excerpt) : "";
64
+ const link = pageLinkRef(source);
52
65
  const muted = { color: colors.foregroundMuted, fontSize: 12, lineHeight: 18 } as const;
53
66
  return (
54
67
  <View style={{ gap: 12, padding: 14, borderRadius: 8, backgroundColor: colors.surface0 }}>
55
- {shown.trim() ? (parts?.markdown ? <Markdown>{shown}</Markdown>
56
- : <Text selectable style={{ color: colors.foreground, fontSize: 13, lineHeight: 19 }}>{shown}</Text>) : null}
68
+ {shown.trim() ? (
69
+ parts?.markdown ? (
70
+ <Markdown>{shown}</Markdown>
71
+ ) : (
72
+ <Text selectable style={{ color: colors.foreground, fontSize: 13, lineHeight: 19 }}>
73
+ {shown}
74
+ </Text>
75
+ )
76
+ ) : null}
57
77
  {all && parts?.truncated ? <Text style={muted}>{m.truncatedText}</Text> : null}
58
78
  {preview.error ? <Text style={muted}>{m.readError(errorText(preview.error, m.unknownError))}</Text> : null}
59
79
  {/* The file on the left, opening it whole; expanding in place on the right. */}
60
80
  <View style={{ flexDirection: "row", alignItems: "center", gap: 12 }}>
61
- <TextButton colors={colors} icon={attachmentIcon(source)} label={source.title} accessibilityLabel={m.openItem(source.title)} onPress={() => onOpen(source, [source])} grow />
81
+ <TextButton
82
+ colors={colors}
83
+ icon={attachmentIcon(source)}
84
+ label={source.title}
85
+ accessibilityLabel={m.openItem(source.title)}
86
+ onPress={() => {
87
+ if (link && !followPageLink(link)) return;
88
+ onOpen(source, [source]);
89
+ }}
90
+ grow
91
+ >
92
+ {link ? <PageLink link={link} path={absolutePath(source.path!, workspaceDirectory)} /> : null}
93
+ </TextButton>
62
94
  <View style={{ flex: 1 }} />
63
95
  {parts?.more ? <TextButton colors={colors} label={all ? m.showLess : m.readMore} onPress={() => setAll(!all)} expanded={all} /> : null}
64
96
  </View>
@@ -67,14 +99,49 @@ export function WriteUp({ colors, source, workspaceId, workspaceDirectory, onOpe
67
99
  }
68
100
 
69
101
  // A quiet text button for the inset's footer: muted, tinted on hover.
70
- function TextButton({ colors, icon, label, accessibilityLabel, onPress, grow = false, expanded }: {
71
- colors: Colors; icon?: string; label: string; accessibilityLabel?: string; onPress(): void; grow?: boolean; expanded?: boolean;
102
+ function TextButton({
103
+ colors,
104
+ icon,
105
+ label,
106
+ accessibilityLabel,
107
+ onPress,
108
+ grow = false,
109
+ expanded,
110
+ children,
111
+ }: {
112
+ colors: Colors;
113
+ icon?: string;
114
+ label: string;
115
+ accessibilityLabel?: string;
116
+ onPress(): void;
117
+ grow?: boolean;
118
+ expanded?: boolean;
119
+ children?: React.ReactNode;
72
120
  }) {
73
121
  return (
74
- <PressableRow colors={colors} accessibilityRole="button" accessibilityLabel={accessibilityLabel ?? label} aria-expanded={expanded} onPress={onPress}
75
- style={{ flexDirection: "row", alignItems: "center", gap: 6, paddingVertical: 3, paddingHorizontal: 6, marginHorizontal: -6, borderRadius: 4, flexShrink: grow ? 1 : 0, minWidth: 0 }}>
122
+ <PressableRow
123
+ colors={colors}
124
+ accessibilityRole="button"
125
+ accessibilityLabel={accessibilityLabel ?? label}
126
+ aria-expanded={expanded}
127
+ onPress={onPress}
128
+ style={{
129
+ flexDirection: "row",
130
+ alignItems: "center",
131
+ gap: 6,
132
+ paddingVertical: 3,
133
+ paddingHorizontal: 6,
134
+ marginHorizontal: -6,
135
+ borderRadius: 4,
136
+ flexShrink: grow ? 1 : 0,
137
+ minWidth: 0,
138
+ }}
139
+ >
76
140
  {icon ? <Icon name={icon} size={12} color={colors.foregroundMuted} /> : null}
77
- <Text numberOfLines={1} style={{ flexShrink: 1, color: colors.foregroundMuted, fontSize: 12, lineHeight: 17, fontWeight: "500" }}>{label}</Text>
141
+ <Text numberOfLines={1} style={{ flexShrink: 1, color: colors.foregroundMuted, fontSize: 12, lineHeight: 17, fontWeight: "500" }}>
142
+ {label}
143
+ </Text>
144
+ {children}
78
145
  </PressableRow>
79
146
  );
80
147
  }