@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.
@@ -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
- <ScrollView style={{ flex: 1, backgroundColor: colors.surface0 }} contentContainerStyle={{ paddingBottom: 16 }}>
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
- function Tab({ colors, label, icon, count, selected, onPress }: { colors: Colors; label: string; icon: string | null; count: number; selected: boolean; onPress(): void }) {
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
- <View style={{ width: 12 }} />
462
- <Text style={{ minWidth: 40, flexShrink: 0, textAlign: "right", color: colors.foregroundMuted, fontSize: 12, lineHeight: 18, fontVariant: ["tabular-nums"] }}>{estimateMin ? formatEstimate(estimateMin) : ""}</Text>
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
  }
@@ -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
- <Text style={{ flex: 1, color: colors.foreground, fontSize: 13, lineHeight: 19 }}>{question.question}</Text>
158
- <DefaultBadge colors={colors} value={question.default} waits={question.waits} />
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>
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@stevecastaneda/paseo-progress",
3
- "version": "0.2.0",
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.0",
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.0",
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",