@stevecastaneda/paseo-progress 0.2.0 → 0.2.1
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/client/dialog-stack.tsx +4 -0
- package/client/narrow.tsx +13 -0
- package/client/panel.tsx +28 -13
- package/client/questions.tsx +9 -2
- package/client/when.tsx +4 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/client/dialog-stack.tsx
CHANGED
|
@@ -4,6 +4,7 @@ import React, { useEffect, useRef, useState } from "react";
|
|
|
4
4
|
import type { Dashboard, Question } from "../shared/dashboard";
|
|
5
5
|
import { type Attachment, attachmentIcon, useAttachmentOpener } from "./attachments";
|
|
6
6
|
import { useLastPresent } from "./motion";
|
|
7
|
+
import { NarrowProvider } from "./narrow";
|
|
7
8
|
import { BackButton, PreviewContent, previewable } from "./preview";
|
|
8
9
|
import { QuestionView, useCopy } from "./questions";
|
|
9
10
|
import { TicketStoryView } from "./ticket-dialog";
|
|
@@ -75,6 +76,8 @@ export function StackedDialog({ colors, root, onClose, questions, dashboard, now
|
|
|
75
76
|
open={Boolean(root)} onOpenChange={(next) => { if (!next) onClose(); }}>
|
|
76
77
|
{/* One Modal.Content for every step: replacing it would re-present the sheet on phones. */}
|
|
77
78
|
<Modal.Content scrollable={!(preview && previewLong)}>
|
|
79
|
+
{/* A dialog has room for clock times, however narrow the panel that opened it. */}
|
|
80
|
+
<NarrowProvider value={false}>
|
|
78
81
|
{preview ? (
|
|
79
82
|
<PreviewContent colors={colors} attachment={preview.attachment} workspaceId={context.workspaceId} workspaceDirectory={context.workspaceDirectory}
|
|
80
83
|
onOpenOnHost={() => { onClose(); void opener.openOnHost(preview.attachment); }} backLabel={back?.label} onBack={back?.onPress}
|
|
@@ -94,6 +97,7 @@ export function StackedDialog({ colors, root, onClose, questions, dashboard, now
|
|
|
94
97
|
) : null}
|
|
95
98
|
</>
|
|
96
99
|
)}
|
|
100
|
+
</NarrowProvider>
|
|
97
101
|
</Modal.Content>
|
|
98
102
|
</Modal>
|
|
99
103
|
);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
|
|
3
|
+
// Paseo opens the Explorer sidebar at 320px and plugins can't ask for more,
|
|
4
|
+
// so at or below this width the panel trims what it shows to fit.
|
|
5
|
+
export const NARROW_MAX = 400;
|
|
6
|
+
|
|
7
|
+
const Narrow = createContext(false);
|
|
8
|
+
|
|
9
|
+
export const NarrowProvider = Narrow.Provider;
|
|
10
|
+
|
|
11
|
+
export function useNarrow(): boolean {
|
|
12
|
+
return useContext(Narrow);
|
|
13
|
+
}
|
package/client/panel.tsx
CHANGED
|
@@ -21,6 +21,7 @@ import { TicketDialogs } from "./ticket-dialog";
|
|
|
21
21
|
import { PressableRow } from "./row";
|
|
22
22
|
import { ShowMoreRow, usePaged } from "./show-more";
|
|
23
23
|
import { notePanelOpened } from "./panel-opened";
|
|
24
|
+
import { NARROW_MAX, NarrowProvider, useNarrow } from "./narrow";
|
|
24
25
|
|
|
25
26
|
type Colors = PluginWorkspacePanelProps["theme"]["colors"];
|
|
26
27
|
|
|
@@ -40,9 +41,12 @@ function WorkspaceProgress({ theme, workspaceId, host, layout, navigation }: Plu
|
|
|
40
41
|
const query = useDashboard(host.id, workspaceId, directory);
|
|
41
42
|
const result = query.data ?? null;
|
|
42
43
|
useMarkPanelOpened(workspaceId, directory, Boolean(result?.configured && !result.panelOpened));
|
|
44
|
+
const [narrow, setNarrow] = useState(false);
|
|
43
45
|
|
|
44
46
|
return (
|
|
45
|
-
<
|
|
47
|
+
<NarrowProvider value={narrow}>
|
|
48
|
+
<ScrollView onLayout={(event) => setNarrow(event.nativeEvent.layout.width <= NARROW_MAX)}
|
|
49
|
+
style={{ flex: 1, backgroundColor: colors.surface0 }} contentContainerStyle={{ paddingBottom: 16 }}>
|
|
46
50
|
{query.isPending ? <ActivityIndicator color={colors.foregroundMuted} accessibilityLabel="Loading progress" style={{ padding: 12 }} /> : null}
|
|
47
51
|
{query.error ? (
|
|
48
52
|
<Text accessibilityRole="alert" selectable style={{ color: colors.statusDanger, fontSize: 12, lineHeight: 18, padding: 12 }}>
|
|
@@ -58,6 +62,7 @@ function WorkspaceProgress({ theme, workspaceId, host, layout, navigation }: Plu
|
|
|
58
62
|
workspaceId={workspaceId} workspaceDirectory={result.root} file={result.file} navigation={navigation} /> : null}
|
|
59
63
|
{result?.inRepo && directory ? <HistoryRow colors={colors} workspaceId={workspaceId} directory={directory} savedTo={result.savedTo} /> : null}
|
|
60
64
|
</ScrollView>
|
|
65
|
+
</NarrowProvider>
|
|
61
66
|
);
|
|
62
67
|
}
|
|
63
68
|
|
|
@@ -256,6 +261,7 @@ const historyTabs = new Map<string, HistoryTab>();
|
|
|
256
261
|
function HistoryCard({ colors, workspaceId, tabs }: { colors: Colors; workspaceId: string; tabs: HistoryTabEntry[] }) {
|
|
257
262
|
const [tab, setTab] = useState<HistoryTab>(() => historyTabs.get(workspaceId) ?? "activity");
|
|
258
263
|
const [showIcons, setShowIcons] = useState(true);
|
|
264
|
+
const narrow = useNarrow();
|
|
259
265
|
const choose = (next: HistoryTab) => {
|
|
260
266
|
historyTabs.set(workspaceId, next);
|
|
261
267
|
setTab(next);
|
|
@@ -264,9 +270,9 @@ function HistoryCard({ colors, workspaceId, tabs }: { colors: Colors; workspaceI
|
|
|
264
270
|
return (
|
|
265
271
|
<View onLayout={(event) => setShowIcons(event.nativeEvent.layout.width >= TAB_ICONS_MIN)}
|
|
266
272
|
style={{ margin: 12, marginBottom: 0, ...raised(colors), borderRadius: 6, overflow: "hidden" }}>
|
|
267
|
-
<View accessibilityRole="tablist" style={{ flexDirection: "row", flexWrap: "wrap", columnGap: 16, paddingHorizontal: 10, borderBottomWidth: 1, borderBottomColor: colors.border }}>
|
|
273
|
+
<View accessibilityRole="tablist" style={{ flexDirection: "row", flexWrap: "wrap", columnGap: narrow ? 10 : 16, paddingHorizontal: narrow ? 8 : 10, borderBottomWidth: 1, borderBottomColor: colors.border }}>
|
|
268
274
|
{tabs.map((entry) => {
|
|
269
|
-
return <Tab key={entry.id} colors={colors} label={entry.label} icon={showIcons ? entry.icon : null} count={entry.count} selected={entry.id === current.id} onPress={() => choose(entry.id)} />;
|
|
275
|
+
return <Tab key={entry.id} colors={colors} narrow={narrow} label={entry.label} icon={showIcons ? entry.icon : null} count={entry.count} selected={entry.id === current.id} onPress={() => choose(entry.id)} />;
|
|
270
276
|
})}
|
|
271
277
|
</View>
|
|
272
278
|
{current.count === 0 ? (
|
|
@@ -277,13 +283,14 @@ function HistoryCard({ colors, workspaceId, tabs }: { colors: Colors; workspaceI
|
|
|
277
283
|
}
|
|
278
284
|
|
|
279
285
|
// Instant feedback: tabs switch often, so hover and press change color only.
|
|
280
|
-
|
|
286
|
+
// Narrow tightens the spacing so all three tabs stay on one line at the sidebar's 320px.
|
|
287
|
+
function Tab({ colors, narrow, label, icon, count, selected, onPress }: { colors: Colors; narrow: boolean; label: string; icon: string | null; count: number; selected: boolean; onPress(): void }) {
|
|
281
288
|
const [hovered, setHovered] = useState(false);
|
|
282
289
|
return (
|
|
283
290
|
<Pressable accessibilityRole="tab" accessibilityState={{ selected }} onPress={onPress}
|
|
284
291
|
onHoverIn={() => setHovered(true)} onHoverOut={() => setHovered(false)}
|
|
285
292
|
style={({ pressed }) => ({
|
|
286
|
-
flexDirection: "row", alignItems: "center", gap: 6, paddingTop: 8, paddingBottom: 6, marginBottom: -1,
|
|
293
|
+
flexDirection: "row", alignItems: "center", gap: narrow ? 4 : 6, paddingTop: 8, paddingBottom: 6, marginBottom: -1,
|
|
287
294
|
borderBottomWidth: 2,
|
|
288
295
|
borderBottomColor: selected ? colors.accent : pressed || hovered ? colors.border : "transparent",
|
|
289
296
|
})}>
|
|
@@ -341,11 +348,12 @@ function Card({ colors, icon, title, children }: { colors: Colors; icon: string;
|
|
|
341
348
|
}
|
|
342
349
|
|
|
343
350
|
function ProgressBar({ colors, progress, live }: { colors: Colors; progress: Dashboard["progress"]; live: boolean }) {
|
|
351
|
+
const narrow = useNarrow();
|
|
344
352
|
return (
|
|
345
353
|
<View style={{ paddingHorizontal: 12, paddingTop: 12, paddingBottom: 12, gap: 8, borderBottomWidth: 1, borderBottomColor: colors.border }}>
|
|
346
354
|
<View style={{ flexDirection: "row", alignItems: "baseline", gap: 8 }}>
|
|
347
355
|
<Text style={{ flex: 1, color: colors.foregroundMuted, fontSize: 13, lineHeight: 18 }}>
|
|
348
|
-
<Text style={{ color: colors.foreground, fontWeight: "600" }}>{progress.percent}%</Text> of estimated work done
|
|
356
|
+
<Text style={{ color: colors.foreground, fontWeight: "600" }}>{progress.percent}%</Text> {narrow ? "done" : "of estimated work done"}
|
|
349
357
|
</Text>
|
|
350
358
|
<Text style={{ color: colors.foregroundMuted, fontSize: 12, lineHeight: 18 }}>
|
|
351
359
|
{formatWorkDone(progress.doneMin, progress.totalMin, progress.tookMin)}
|
|
@@ -393,6 +401,7 @@ function Shimmer() {
|
|
|
393
401
|
const STUCK_ICON: Record<StuckItem["kind"], string> = { blocked: "Ban", overdue: "Hourglass", manual: "Flag" };
|
|
394
402
|
|
|
395
403
|
function StuckSection({ colors, items, now }: { colors: Colors; items: StuckItem[]; now: number }) {
|
|
404
|
+
const narrow = useNarrow();
|
|
396
405
|
return (
|
|
397
406
|
<View style={{ margin: 12, marginBottom: 0, borderWidth: 1, borderColor: colors.statusDanger, borderRadius: 6, overflow: "hidden" }}>
|
|
398
407
|
<SectionTitle colors={colors} icon="OctagonAlert" title="Stuck" color={colors.statusDanger} style={{ borderBottomWidth: 1, borderBottomColor: colors.border }} />
|
|
@@ -404,7 +413,7 @@ function StuckSection({ colors, items, now }: { colors: Colors; items: StuckItem
|
|
|
404
413
|
<Text style={{ color: colors.foreground, fontSize: 13, lineHeight: 18 }}>{item.title}</Text>
|
|
405
414
|
<Text style={{ color: colors.foregroundMuted, fontSize: 12, lineHeight: 17 }}>
|
|
406
415
|
{item.kind === "overdue" ? `No update for longer than its ${formatMinutes(item.estimateMin)} estimate. Last update ` : null}
|
|
407
|
-
{item.kind === "blocked" ? `Blocked${item.note ? `: ${item.note}` : ""}. Since ` : null}
|
|
416
|
+
{item.kind === "blocked" ? `Blocked${item.note ? `: ${item.note}` : ""}${narrow ? ", " : ". Since "}` : null}
|
|
408
417
|
{item.kind === "manual" ? `${item.ticketId ? `${item.reason}. ` : ""}Flagged ` : null}
|
|
409
418
|
<When colors={colors} iso={item.since} now={now} />
|
|
410
419
|
</Text>
|
|
@@ -450,6 +459,7 @@ function FinishedRow({ colors, tickets, shown, onPress }: { colors: Colors; tick
|
|
|
450
459
|
const label = [done ? `${done} done` : "", skipped ? `${skipped} skipped` : ""].filter(Boolean).join(", ");
|
|
451
460
|
const estimateMin = tickets.reduce((sum, ticket) => sum + (ticket.status === "done" ? ticket.estimateMin : 0), 0);
|
|
452
461
|
const chevron = shown ? "ChevronUp" : "ChevronDown";
|
|
462
|
+
const narrow = useNarrow();
|
|
453
463
|
return (
|
|
454
464
|
<PressableRow colors={colors} accessibilityRole="button" accessibilityState={{ expanded: shown }}
|
|
455
465
|
accessibilityLabel={`${shown ? "Hide" : "Show"} ${label}`} onPress={onPress}
|
|
@@ -458,8 +468,11 @@ function FinishedRow({ colors, tickets, shown, onPress }: { colors: Colors; tick
|
|
|
458
468
|
<Icon name={done ? "Check" : "CircleSlash"} size={14} color={done ? colors.statusSuccess : colors.foregroundMuted} />
|
|
459
469
|
</View>
|
|
460
470
|
<Text style={{ flex: 1, minWidth: 0, color: colors.foregroundMuted, fontSize: 12, lineHeight: 18, fontWeight: "600", fontVariant: ["tabular-nums"] }}>{label}</Text>
|
|
461
|
-
|
|
462
|
-
|
|
471
|
+
{/* Narrow ticket rows drop their chevron, so this total would sit off their estimate column. */}
|
|
472
|
+
{narrow ? null : <>
|
|
473
|
+
<View style={{ width: 12 }} />
|
|
474
|
+
<Text style={{ minWidth: 40, flexShrink: 0, textAlign: "right", color: colors.foregroundMuted, fontSize: 12, lineHeight: 18, fontVariant: ["tabular-nums"] }}>{estimateMin ? formatEstimate(estimateMin) : ""}</Text>
|
|
475
|
+
</>}
|
|
463
476
|
<View style={{ paddingTop: 2 }}>
|
|
464
477
|
<IconSwap swapKey={chevron} size={14}><Icon name={chevron} size={14} color={colors.foregroundMuted} /></IconSwap>
|
|
465
478
|
</View>
|
|
@@ -471,12 +484,14 @@ function FinishedRow({ colors, tickets, shown, onPress }: { colors: Colors; tick
|
|
|
471
484
|
function TicketRow({ colors, ticket, live, now, onOpen, deliverables }: { colors: Colors; ticket: Ticket; live: boolean; now: number; onOpen(): void; deliverables: number }) {
|
|
472
485
|
const muted = ticket.status === "skipped";
|
|
473
486
|
const working = ticket.status === "working";
|
|
487
|
+
// Narrow: tighter gaps and no chevron (the whole row still presses), so the title keeps its width.
|
|
488
|
+
const narrow = useNarrow();
|
|
474
489
|
return (
|
|
475
490
|
<PressableRow colors={colors} onSurface1={working} transparentAtRest={!working}
|
|
476
491
|
accessibilityRole="button" accessibilityLabel={`${ticket.id} details`} onPress={onOpen}
|
|
477
|
-
style={{ flexDirection: "row", alignItems: "flex-start", gap: 10, paddingHorizontal: 10, paddingVertical: 8, borderTopWidth: 1, borderTopColor: colors.border }}>
|
|
492
|
+
style={{ flexDirection: "row", alignItems: "flex-start", gap: narrow ? 6 : 10, paddingHorizontal: 10, paddingVertical: 8, borderTopWidth: 1, borderTopColor: colors.border }}>
|
|
478
493
|
{/* The icon is the only status mark on the row; the ticket dialog spells it out in a badge. */}
|
|
479
|
-
<View accessible accessibilityLabel={ticket.waitingFor ? "Waiting" : STATUS_LABEL[ticket.status]} style={{ width: 16, paddingTop: 2, alignItems: "center" }}>
|
|
494
|
+
<View accessible accessibilityLabel={ticket.waitingFor ? "Waiting" : STATUS_LABEL[ticket.status]} style={{ width: 16, paddingTop: 2, alignItems: "center", marginRight: narrow ? 2 : 0 }}>
|
|
480
495
|
<IconSwap swapKey={ticket.waitingFor || ticket.status === "not_started" ? "waiting" : ticket.status === "working" ? `working-${live}` : ticket.status} size={14}><StatusIcon colors={colors} status={ticket.status} live={live} waiting={Boolean(ticket.waitingFor)} /></IconSwap>
|
|
481
496
|
</View>
|
|
482
497
|
<View style={{ flex: 1, minWidth: 0, gap: 2 }}>
|
|
@@ -490,7 +505,7 @@ function TicketRow({ colors, ticket, live, now, onOpen, deliverables }: { colors
|
|
|
490
505
|
) : null}
|
|
491
506
|
{ticket.status === "working" && ticket.workingSince ? (
|
|
492
507
|
<Text style={{ color: colors.foregroundMuted, fontSize: 12, lineHeight: 17 }}>
|
|
493
|
-
Working since <When colors={colors} iso={ticket.workingSince} now={now} />
|
|
508
|
+
{narrow ? "Started working " : "Working since "}<When colors={colors} iso={ticket.workingSince} now={now} />
|
|
494
509
|
</Text>
|
|
495
510
|
) : null}
|
|
496
511
|
{ticket.waitingFor ? (
|
|
@@ -503,7 +518,7 @@ function TicketRow({ colors, ticket, live, now, onOpen, deliverables }: { colors
|
|
|
503
518
|
{deliverables ? <Icon name="Package" size={12} color={colors.foregroundMuted} /> : null}
|
|
504
519
|
</View>
|
|
505
520
|
<Text style={{ minWidth: 40, flexShrink: 0, textAlign: "right", color: colors.foregroundMuted, fontSize: 12, lineHeight: 18, fontVariant: ["tabular-nums"] }}>{formatEstimate(ticket.estimateMin)}</Text>
|
|
506
|
-
<View style={{ paddingTop: 2 }}><Icon name="ChevronRight" size={14} color={colors.foregroundMuted} /></View>
|
|
521
|
+
{narrow ? null : <View style={{ paddingTop: 2 }}><Icon name="ChevronRight" size={14} color={colors.foregroundMuted} /></View>}
|
|
507
522
|
</PressableRow>
|
|
508
523
|
);
|
|
509
524
|
}
|
package/client/questions.tsx
CHANGED
|
@@ -8,6 +8,7 @@ import { PressableRow } from "./row";
|
|
|
8
8
|
import { ShowMoreRow, usePaged } from "./show-more";
|
|
9
9
|
import { PressScale } from "./motion";
|
|
10
10
|
import { StackedDialog } from "./dialog-stack";
|
|
11
|
+
import { useNarrow } from "./narrow";
|
|
11
12
|
import { raised } from "./surfaces";
|
|
12
13
|
import { When } from "./when";
|
|
13
14
|
|
|
@@ -140,6 +141,9 @@ function QuestionRow({ colors, question, first, onOpen, onCopy }: {
|
|
|
140
141
|
onOpen(): void;
|
|
141
142
|
onCopy(): void;
|
|
142
143
|
}) {
|
|
144
|
+
// Narrow: the badge goes under the question, so the question keeps the row's width.
|
|
145
|
+
const narrow = useNarrow();
|
|
146
|
+
const badge = <DefaultBadge colors={colors} value={question.default} waits={question.waits} />;
|
|
143
147
|
return (
|
|
144
148
|
<View style={{ flexDirection: "row", gap: 8, paddingHorizontal: 10, paddingVertical: 10, borderTopWidth: first ? 0 : 1, borderTopColor: colors.border }}>
|
|
145
149
|
<PressScale accessibilityRole="button" accessibilityLabel={`Copy ${question.id} for your reply`} onPress={onCopy} hitSlop={6}
|
|
@@ -154,8 +158,11 @@ function QuestionRow({ colors, question, first, onOpen, onCopy }: {
|
|
|
154
158
|
</PressScale>
|
|
155
159
|
<PressableRow colors={colors} accessibilityRole="button" accessibilityLabel={`${question.id} choices`} onPress={onOpen}
|
|
156
160
|
style={{ flex: 1, minWidth: 0, flexDirection: "row", alignItems: "flex-start", gap: 8, borderRadius: 4, margin: -4, padding: 4 }}>
|
|
157
|
-
<
|
|
158
|
-
|
|
161
|
+
<View style={{ flex: 1, minWidth: 0, gap: 6, alignItems: "flex-start" }}>
|
|
162
|
+
<Text style={{ alignSelf: "stretch", color: colors.foreground, fontSize: 13, lineHeight: 19 }}>{question.question}</Text>
|
|
163
|
+
{narrow ? badge : null}
|
|
164
|
+
</View>
|
|
165
|
+
{narrow ? null : badge}
|
|
159
166
|
{/* Optical: centers the 14px chevron on the 19px first line. */}
|
|
160
167
|
<View style={{ paddingTop: 2.5 }}><Icon name="ChevronRight" size={14} color={colors.foregroundMuted} /></View>
|
|
161
168
|
</PressableRow>
|
package/client/when.tsx
CHANGED
|
@@ -2,11 +2,14 @@ import type { PluginWorkspacePanelProps } from "@getpaseo/plugin/client";
|
|
|
2
2
|
import React from "react";
|
|
3
3
|
import { Text } from "react-native";
|
|
4
4
|
import { formatAgo, formatClock } from "../shared/format";
|
|
5
|
+
import { useNarrow } from "./narrow";
|
|
5
6
|
|
|
6
7
|
type Colors = PluginWorkspacePanelProps["theme"]["colors"];
|
|
7
8
|
|
|
8
|
-
// "Sep 27, 8:14 PM (10 h 2 min ago)", inline inside other text.
|
|
9
|
+
// "Sep 27, 8:14 PM (10 h 2 min ago)", inline inside other text. A narrow
|
|
10
|
+
// panel shows only "10 h 2 min ago"; the ticket dialog keeps the clock time.
|
|
9
11
|
export function When({ colors, iso, now }: { colors: Colors; iso: string; now: number }) {
|
|
12
|
+
if (useNarrow()) return <Text>{formatAgo(iso, now)}</Text>;
|
|
10
13
|
return (
|
|
11
14
|
<Text>
|
|
12
15
|
{formatClock(iso)} <Text style={{ color: colors.foregroundMuted, opacity: 0.7 }}>({formatAgo(iso, now)})</Text>
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stevecastaneda/paseo-progress",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@stevecastaneda/paseo-progress",
|
|
9
|
-
"version": "0.2.
|
|
9
|
+
"version": "0.2.1",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@getpaseo/client": "^0.9.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stevecastaneda/paseo-progress",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "A live progress dashboard per worktree for Paseo, drawn from a file agents write with the paseo-progress command.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Steve Castaneda",
|