@stevecastaneda/paseo-progress 0.1.3 → 0.1.5
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 +1 -1
- package/client/panel.tsx +6 -5
- package/client/spinner.tsx +2 -2
- package/client/ticket-dialog.tsx +1 -2
- package/dist/shared/format.js +7 -0
- package/dist/shared/show.js +2 -2
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/shared/format.ts +7 -0
- package/shared/show.ts +2 -2
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ The dashboard shows:
|
|
|
10
10
|
- a "possibly stale" warning after 15 minutes with no update while tickets remain, unless Paseo shows an agent in the workspace still running (for example one waiting on its subagents), and never once every ticket is done or skipped or the run is finished; working items swap their spinner for a pulsing amber warning
|
|
11
11
|
- percent of estimated work done, time done of time estimated (like "1 h 30 min of 2 h"; once everything is done, how long it really took), and a bar with one segment per ticket
|
|
12
12
|
- **Stuck:** blocked tickets, working tickets with no update for longer than their estimate, and blockers the agent flags
|
|
13
|
-
- **Tickets:** estimate, status, and the working ticket's stage. A ticket that waits for another (`--waits-for T03`) shows
|
|
13
|
+
- **Tickets:** estimate, status, and the working ticket's stage. A ticket that waits for another (`--waits-for T03`) shows "Waits for Ticket 03" under its title until that one is done; waiting is the order of work, so it never counts as stuck, even if the agent also marked it blocked. Press a ticket for its story in a dialog: a timeline of its stages and statuses with how long each took, notes, time worked against the estimate, and the deliverables, questions and activity that belong to it. Items tagged with `--ticket` belong exactly; older, untagged ones are matched by when they happened and marked "by time"
|
|
14
14
|
- **Questions:** each with a permanent reference (Q1, Q2, ...), lettered options, and the default the agent is using. Press a question to open it in a dialog, then press **Copy Q2 B** to copy `Q2 (title): B` for your reply. Files and links the agent attached (`--file`) open like deliverables: images and text files preview in Paseo (with **Back** to the question), links open in Paseo's browser, and other files open in their default app. From the pill's popover, the preview opens over the popover, which stays open so you can go back to it. Answers stay listed under the same reference.
|
|
15
15
|
- **Activity**, **Answered** questions and **Deliverables** (press one to open it), sharing one card with a tab for each; the panel remembers the tab you picked. Each shows the newest 10, with **Show 10 older** below
|
|
16
16
|
|
package/client/panel.tsx
CHANGED
|
@@ -12,7 +12,7 @@ import React, { useEffect, useRef, useState } from "react";
|
|
|
12
12
|
import { ActivityIndicator, Animated, Platform, Pressable, ScrollView, Text, View } from "react-native";
|
|
13
13
|
import { shortTitle, type Activity, type Dashboard, type FileIssue, type ProgressSegment, type StuckItem, type Ticket } from "../shared/dashboard";
|
|
14
14
|
import { PROGRESS_FILE, type TicketStatus } from "../shared/events";
|
|
15
|
-
import { formatMinutes, formatWorkDone, minutesSince } from "../shared/format";
|
|
15
|
+
import { formatEstimate, formatMinutes, formatWorkDone, minutesSince } from "../shared/format";
|
|
16
16
|
import { markPanelOpened } from "../shared/rpc";
|
|
17
17
|
import { useDashboard } from "./dashboard-query";
|
|
18
18
|
import { STATUS_LABEL } from "./status-badge";
|
|
@@ -425,7 +425,7 @@ function TicketRow({ colors, ticket, live, now, onOpen, deliverables }: { colors
|
|
|
425
425
|
style={{ flexDirection: "row", alignItems: "flex-start", gap: 10, paddingHorizontal: 10, paddingVertical: 8, borderTopWidth: 1, borderTopColor: colors.border }}>
|
|
426
426
|
{/* The icon is the only status mark on the row; the ticket dialog spells it out in a badge. */}
|
|
427
427
|
<View accessible accessibilityLabel={ticket.waitingFor ? "Waiting" : STATUS_LABEL[ticket.status]} style={{ width: 16, paddingTop: 2, alignItems: "center" }}>
|
|
428
|
-
<IconSwap swapKey={ticket.waitingFor ? "waiting" : ticket.status === "working" ? `working-${live}` : ticket.status} size={14}><StatusIcon colors={colors} status={ticket.status} live={live} waiting={Boolean(ticket.waitingFor)} /></IconSwap>
|
|
428
|
+
<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>
|
|
429
429
|
</View>
|
|
430
430
|
<View style={{ flex: 1, minWidth: 0, gap: 2 }}>
|
|
431
431
|
<Text style={{ color: muted ? colors.foregroundMuted : colors.foreground, fontSize: 13, lineHeight: 18, textDecorationLine: muted ? "line-through" : "none" }}>
|
|
@@ -446,11 +446,11 @@ function TicketRow({ colors, ticket, live, now, onOpen, deliverables }: { colors
|
|
|
446
446
|
) : null}
|
|
447
447
|
{ticket.note ? <Text style={{ color: colors.foregroundMuted, fontSize: 12, lineHeight: 17 }}>{ticket.note}</Text> : null}
|
|
448
448
|
</View>
|
|
449
|
-
{/* Fixed slots so the Package icon and estimates line up down the list, whether or not a row has deliverables; they're listed in its dialog. */}
|
|
449
|
+
{/* Fixed slots so the Package icon and estimates line up down the list, whether or not a row has deliverables; they're listed in its dialog. The estimate slot fits "45 min" and "10.5 h"; longer ones grow rather than clip. */}
|
|
450
450
|
<View accessibilityLabel={deliverables ? (deliverables === 1 ? "Has a deliverable" : `Has ${deliverables} deliverables`) : undefined} style={{ width: 12, height: 18, justifyContent: "center" }}>
|
|
451
451
|
{deliverables ? <Icon name="Package" size={12} color={colors.foregroundMuted} /> : null}
|
|
452
452
|
</View>
|
|
453
|
-
<Text style={{ minWidth:
|
|
453
|
+
<Text style={{ minWidth: 40, flexShrink: 0, textAlign: "right", color: colors.foregroundMuted, fontSize: 12, lineHeight: 18, fontVariant: ["tabular-nums"] }}>{formatEstimate(ticket.estimateMin)}</Text>
|
|
454
454
|
<View style={{ paddingTop: 2 }}><Icon name="ChevronRight" size={14} color={colors.foregroundMuted} /></View>
|
|
455
455
|
</PressableRow>
|
|
456
456
|
);
|
|
@@ -468,7 +468,8 @@ function StatusIcon({ colors, status, live, waiting }: { colors: Colors; status:
|
|
|
468
468
|
case "skipped":
|
|
469
469
|
return <Icon name="CircleSlash" size={14} color={colors.foregroundMuted} />;
|
|
470
470
|
default:
|
|
471
|
-
|
|
471
|
+
// Every step still ahead gets the same dot; "Waits for" says which ones are held up.
|
|
472
|
+
return <WaitingDot color={colors.foregroundMuted} size={14} />;
|
|
472
473
|
}
|
|
473
474
|
}
|
|
474
475
|
|
package/client/spinner.tsx
CHANGED
|
@@ -34,8 +34,8 @@ export function StalledPulse({ color, size = 12 }: { color: string; size?: numbe
|
|
|
34
34
|
);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
// A ticket waiting on another one: a still, faded dot, so it
|
|
38
|
-
// rather than busy. Centered in the same slot an icon would take.
|
|
37
|
+
// A ticket not started yet, or waiting on another one: a still, faded dot, so it
|
|
38
|
+
// reads as parked rather than busy. Centered in the same slot an icon would take.
|
|
39
39
|
export function WaitingDot({ color, size = 12 }: { color: string; size?: number }) {
|
|
40
40
|
const dot = Math.round(size / 2);
|
|
41
41
|
return (
|
package/client/ticket-dialog.tsx
CHANGED
|
@@ -158,12 +158,11 @@ function LinkNote({ colors, link }: { colors: Colors; link: Link }) {
|
|
|
158
158
|
// The same icons as the ticket rows, so a step reads like the status it was.
|
|
159
159
|
// Past stage steps get an accent ringed dot: work that happened, now over.
|
|
160
160
|
function Marker({ colors, step }: { colors: Colors; step: TimelineStep & { waiting?: boolean } }) {
|
|
161
|
-
if (step.waiting) return <WaitingDot color={colors.foregroundMuted} />;
|
|
161
|
+
if (step.waiting || (step.status === "not_started" && step.label !== "Added")) return <WaitingDot color={colors.foregroundMuted} />;
|
|
162
162
|
const icon = step.label === "Added" ? { name: "CirclePlus", color: colors.foregroundMuted }
|
|
163
163
|
: step.status === "done" ? { name: "CircleCheck", color: colors.statusSuccess }
|
|
164
164
|
: step.status === "blocked" ? { name: "Ban", color: colors.statusDanger }
|
|
165
165
|
: step.status === "skipped" ? { name: "CircleSlash", color: colors.foregroundMuted }
|
|
166
|
-
: step.status === "not_started" ? { name: "Circle", color: colors.foregroundMuted }
|
|
167
166
|
: { name: "CircleDot", color: colors.accent };
|
|
168
167
|
return <Icon name={icon.name} size={12} color={icon.color} />;
|
|
169
168
|
}
|
package/dist/shared/format.js
CHANGED
|
@@ -11,6 +11,13 @@ export function formatMinutes(totalMin) {
|
|
|
11
11
|
return `${minutes} min`;
|
|
12
12
|
return minutes ? `${hours} h ${minutes} min` : `${hours} h`;
|
|
13
13
|
}
|
|
14
|
+
// A ticket's estimate, the way people say one: "45 min" under an hour, then
|
|
15
|
+
// hours to one decimal: "1 h", "1.5 h", "2.1 h".
|
|
16
|
+
export function formatEstimate(totalMin) {
|
|
17
|
+
if (totalMin < 60)
|
|
18
|
+
return formatMinutes(totalMin);
|
|
19
|
+
return `${Math.round(totalMin / 6) / 10} h`;
|
|
20
|
+
}
|
|
14
21
|
// Estimated work done out of the total, in the units people estimate in:
|
|
15
22
|
// "5 of 10 min", "1 h 30 min of 2 h". Under an hour on both sides, the unit is
|
|
16
23
|
// said once.
|
package/dist/shared/show.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { headlineText, shortTitle } from "./dashboard.js";
|
|
2
|
-
import { formatAgo, formatClock, formatMinutes, formatWorkDone, minutesSince } from "./format.js";
|
|
2
|
+
import { formatAgo, formatClock, formatEstimate, formatMinutes, formatWorkDone, minutesSince } from "./format.js";
|
|
3
3
|
const STATUS = { not_started: "Not started", working: "Working", blocked: "Blocked", done: "Done", skipped: "Skipped" };
|
|
4
4
|
// The dashboard as plain text, so an agent can check what the user sees.
|
|
5
5
|
export function dashboardText(dashboard, nowMs) {
|
|
@@ -40,7 +40,7 @@ export function dashboardText(dashboard, nowMs) {
|
|
|
40
40
|
for (const ticket of dashboard.tickets) {
|
|
41
41
|
const stage = ticket.status === "working" && ticket.stage ? `, ${ticket.stage} stage` : "";
|
|
42
42
|
const status = ticket.waitingFor ? `Waiting for ${ticket.waitingFor.id}` : STATUS[ticket.status];
|
|
43
|
-
lines.push(` ${ticket.id} ${status}${stage} ${ticket.estimateMin}
|
|
43
|
+
lines.push(` ${ticket.id} ${status}${stage} ${formatEstimate(ticket.estimateMin)} ${ticket.title}`);
|
|
44
44
|
}
|
|
45
45
|
if (dashboard.questions.open.length) {
|
|
46
46
|
lines.push("", "Questions waiting");
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stevecastaneda/paseo-progress",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@stevecastaneda/paseo-progress",
|
|
9
|
-
"version": "0.1.
|
|
9
|
+
"version": "0.1.5",
|
|
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.1.
|
|
3
|
+
"version": "0.1.5",
|
|
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",
|
package/shared/format.ts
CHANGED
|
@@ -12,6 +12,13 @@ export function formatMinutes(totalMin: number): string {
|
|
|
12
12
|
return minutes ? `${hours} h ${minutes} min` : `${hours} h`;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
// A ticket's estimate, the way people say one: "45 min" under an hour, then
|
|
16
|
+
// hours to one decimal: "1 h", "1.5 h", "2.1 h".
|
|
17
|
+
export function formatEstimate(totalMin: number): string {
|
|
18
|
+
if (totalMin < 60) return formatMinutes(totalMin);
|
|
19
|
+
return `${Math.round(totalMin / 6) / 10} h`;
|
|
20
|
+
}
|
|
21
|
+
|
|
15
22
|
// Estimated work done out of the total, in the units people estimate in:
|
|
16
23
|
// "5 of 10 min", "1 h 30 min of 2 h". Under an hour on both sides, the unit is
|
|
17
24
|
// said once.
|
package/shared/show.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Dashboard } from "./dashboard.ts";
|
|
2
2
|
import { headlineText, shortTitle } from "./dashboard.ts";
|
|
3
|
-
import { formatAgo, formatClock, formatMinutes, formatWorkDone, minutesSince } from "./format.ts";
|
|
3
|
+
import { formatAgo, formatClock, formatEstimate, formatMinutes, formatWorkDone, minutesSince } from "./format.ts";
|
|
4
4
|
|
|
5
5
|
const STATUS: Record<string, string> = { not_started: "Not started", working: "Working", blocked: "Blocked", done: "Done", skipped: "Skipped" };
|
|
6
6
|
|
|
@@ -38,7 +38,7 @@ export function dashboardText(dashboard: Dashboard, nowMs: number): string {
|
|
|
38
38
|
for (const ticket of dashboard.tickets) {
|
|
39
39
|
const stage = ticket.status === "working" && ticket.stage ? `, ${ticket.stage} stage` : "";
|
|
40
40
|
const status = ticket.waitingFor ? `Waiting for ${ticket.waitingFor.id}` : STATUS[ticket.status];
|
|
41
|
-
lines.push(` ${ticket.id} ${status}${stage} ${ticket.estimateMin}
|
|
41
|
+
lines.push(` ${ticket.id} ${status}${stage} ${formatEstimate(ticket.estimateMin)} ${ticket.title}`);
|
|
42
42
|
}
|
|
43
43
|
if (dashboard.questions.open.length) {
|
|
44
44
|
lines.push("", "Questions waiting");
|