@stevecastaneda/paseo-progress 0.1.6 → 0.1.8
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/attachments.tsx +4 -3
- package/client/panel.tsx +7 -8
- package/client/pill-popover.tsx +4 -2
- package/dist/server/cli.js +3 -16
- package/dist/server/paths.js +17 -1
- package/dist/shared/dashboard.js +11 -9
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/server/cli.ts +3 -15
- package/server/dashboard.ts +6 -4
- package/server/open.ts +4 -3
- package/server/paths.ts +16 -1
- package/shared/dashboard.ts +12 -9
- package/shared/rpc.ts +3 -1
package/client/attachments.tsx
CHANGED
|
@@ -16,10 +16,11 @@ export function isPreviewable(attachment: Attachment): boolean {
|
|
|
16
16
|
return canPreview(attachment, { browser: Platform.OS === "web" });
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
// Full path on the daemon host for a stored path.
|
|
20
|
-
|
|
19
|
+
// Full path on the daemon host for a stored path. `root` is the worktree
|
|
20
|
+
// root the dashboard was read from, not the workspace directory.
|
|
21
|
+
export function absolutePath(path: string, root: string): string {
|
|
21
22
|
if (path.startsWith("/") || /^[A-Za-z]:[\\/]/.test(path)) return path;
|
|
22
|
-
const base =
|
|
23
|
+
const base = root.replace(/[\\/]+$/, "");
|
|
23
24
|
return path === "./" ? `${base}/` : `${base}/${path}`;
|
|
24
25
|
}
|
|
25
26
|
|
package/client/panel.tsx
CHANGED
|
@@ -10,7 +10,7 @@ import { raised } from "./surfaces";
|
|
|
10
10
|
import { When } from "./when";
|
|
11
11
|
import React, { useEffect, useRef, useState } from "react";
|
|
12
12
|
import { ActivityIndicator, Animated, Platform, Pressable, ScrollView, Text, View } from "react-native";
|
|
13
|
-
import {
|
|
13
|
+
import { finishedToFold, isFinished, 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
15
|
import { formatEstimate, formatMinutes, formatWorkDone, minutesSince } from "../shared/format";
|
|
16
16
|
import { markPanelOpened } from "../shared/rpc";
|
|
@@ -54,7 +54,7 @@ function WorkspaceProgress({ theme, workspaceId, host, layout, navigation }: Plu
|
|
|
54
54
|
<Presence show={Boolean(result && !result.configured)}>{result && !result.configured ? <EmptyState colors={colors} /> : null}</Presence>
|
|
55
55
|
{/* Keyed by run: a new run starts with fresh tabs, pages and dialogs. */}
|
|
56
56
|
{result?.configured ? <DashboardView key={result.dashboard.run?.startedAt ?? "none"} colors={colors} dashboard={result.dashboard} agentRunning={agentRunning} compact={layout.compact}
|
|
57
|
-
workspaceId={workspaceId} workspaceDirectory={
|
|
57
|
+
workspaceId={workspaceId} workspaceDirectory={result.root} navigation={navigation} /> : null}
|
|
58
58
|
</ScrollView>
|
|
59
59
|
);
|
|
60
60
|
}
|
|
@@ -416,26 +416,25 @@ function StuckSection({ colors, items, now }: { colors: Colors; items: StuckItem
|
|
|
416
416
|
// the app session like the history tab.
|
|
417
417
|
const finishedShown = new Map<string, boolean>();
|
|
418
418
|
|
|
419
|
-
// Finished tickets
|
|
420
|
-
// near the top without scrolling (see
|
|
419
|
+
// Finished tickets fold into one row at the top, so the work still ahead is
|
|
420
|
+
// near the top without scrolling (see finishedToFold).
|
|
421
421
|
function TicketList({ colors, dashboard, live, now, workspaceId, onOpen }: { colors: Colors; dashboard: Dashboard; live: boolean; now: number; workspaceId: string; onOpen(id: string): void }) {
|
|
422
422
|
const [shown, setShown] = useState(() => finishedShown.get(workspaceId) ?? false);
|
|
423
423
|
const toggle = () => {
|
|
424
424
|
finishedShown.set(workspaceId, !shown);
|
|
425
425
|
setShown(!shown);
|
|
426
426
|
};
|
|
427
|
-
const
|
|
427
|
+
const finished = finishedToFold(dashboard.tickets);
|
|
428
428
|
const row = (ticket: Ticket) => (
|
|
429
429
|
<TicketRow key={ticket.id} colors={colors} ticket={ticket} live={live} now={now} onOpen={() => onOpen(ticket.id)}
|
|
430
430
|
deliverables={dashboard.deliverables.filter((deliverable) => deliverable.ticketId === ticket.id).length} />
|
|
431
431
|
);
|
|
432
|
-
if (!
|
|
433
|
-
const finished = dashboard.tickets.slice(0, folded);
|
|
432
|
+
if (!finished.length) return <>{dashboard.tickets.map(row)}</>;
|
|
434
433
|
return (
|
|
435
434
|
<>
|
|
436
435
|
<FinishedRow colors={colors} tickets={finished} shown={shown} onPress={toggle} />
|
|
437
436
|
{shown ? finished.map(row) : null}
|
|
438
|
-
{dashboard.tickets.
|
|
437
|
+
{dashboard.tickets.filter((ticket) => !isFinished(ticket)).map(row)}
|
|
439
438
|
</>
|
|
440
439
|
);
|
|
441
440
|
}
|
package/client/pill-popover.tsx
CHANGED
|
@@ -28,12 +28,14 @@ export function AttentionPopover({ theme, host, layout, workspaceId, close, open
|
|
|
28
28
|
const directory = useWorkspace(workspaceId, (workspace) => workspace.directory);
|
|
29
29
|
const query = useDashboard(host.id, workspaceId, directory);
|
|
30
30
|
const dashboard = query.data?.configured ? query.data.dashboard : null;
|
|
31
|
+
// Stored paths are relative to the worktree root, which can sit above the workspace directory.
|
|
32
|
+
const root = query.data?.root ?? directory ?? "";
|
|
31
33
|
const copy = useCopy();
|
|
32
34
|
// Previews open in a dialog over the popover, which stays open to return to.
|
|
33
35
|
// Links and other files leave Paseo, so the popover closes for those.
|
|
34
36
|
const [previewing, setPreviewing] = useState<{ attachment: Attachment; group: Attachment[] } | null>(null);
|
|
35
37
|
const group = useRef<Attachment[]>([]);
|
|
36
|
-
const opener = useAttachmentOpener({ workspaceId, workspaceDirectory:
|
|
38
|
+
const opener = useAttachmentOpener({ workspaceId, workspaceDirectory: root, onPreview: (attachment) => setPreviewing({ attachment, group: previewable(group.current) }) });
|
|
37
39
|
return (
|
|
38
40
|
<View style={{ width: 360, maxWidth: "100%", gap: 8 }}>
|
|
39
41
|
{dashboard ? (
|
|
@@ -52,7 +54,7 @@ export function AttentionPopover({ theme, host, layout, workspaceId, close, open
|
|
|
52
54
|
{query.error ? `Could not read progress: ${query.error.message}` : "Loading…"}
|
|
53
55
|
</Text>
|
|
54
56
|
)}
|
|
55
|
-
<PreviewDialog colors={colors} attachment={previewing?.attachment ?? null} workspaceId={workspaceId} workspaceDirectory={
|
|
57
|
+
<PreviewDialog colors={colors} attachment={previewing?.attachment ?? null} workspaceId={workspaceId} workspaceDirectory={root}
|
|
56
58
|
onClose={() => setPreviewing(null)} onOpenOnHost={(attachment) => void opener.openOnHost(attachment)}
|
|
57
59
|
gallery={previewing ? { items: previewing.group, onSelect: (attachment) => setPreviewing({ ...previewing, attachment }) } : undefined} />
|
|
58
60
|
<View style={{ paddingTop: touch ? 12 : 8, borderTopWidth: 1, borderTopColor: colors.border }}>
|
package/dist/server/cli.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
// The command agents run to record progress. Each command appends one event
|
|
2
2
|
// to the worktree's progress file and prints what it recorded.
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { realpathSync, statSync } from "node:fs";
|
|
4
|
+
import { relative, resolve } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { parseArgs } from "node:util";
|
|
7
7
|
import { isSettled } from "../shared/dashboard.js";
|
|
8
8
|
import { DELIVERABLE_KINDS, PROGRESS_FILE, TICKET_STATUSES } from "../shared/events.js";
|
|
9
9
|
import { appendProgress, InvalidEvent, readProgress } from "./progress-file.js";
|
|
10
|
-
import { outside } from "./paths.js";
|
|
10
|
+
import { findRoot, outside } from "./paths.js";
|
|
11
11
|
import { installLauncher } from "./launcher.js";
|
|
12
12
|
import { dashboardText } from "../shared/show.js";
|
|
13
13
|
import { imageMimeType } from "../shared/attachments.js";
|
|
@@ -436,19 +436,6 @@ function help() {
|
|
|
436
436
|
` ${INSTALL_SUMMARY}`,
|
|
437
437
|
].join("\n");
|
|
438
438
|
}
|
|
439
|
-
// The worktree root is the nearest directory with a `.git` entry (a folder in
|
|
440
|
-
// a checkout, a file in a linked worktree). Outside git, use the directory.
|
|
441
|
-
export function findRoot(cwd) {
|
|
442
|
-
let directory = resolve(cwd);
|
|
443
|
-
while (true) {
|
|
444
|
-
if (existsSync(join(directory, ".git")))
|
|
445
|
-
return directory;
|
|
446
|
-
const parent = dirname(directory);
|
|
447
|
-
if (parent === directory)
|
|
448
|
-
return resolve(cwd);
|
|
449
|
-
directory = parent;
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
439
|
export async function runCli(argv, options) {
|
|
453
440
|
const out = options.out ?? ((line) => console.log(line));
|
|
454
441
|
const now = options.now ?? (() => new Date());
|
package/dist/server/paths.js
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
2
3
|
// Whether a path from relative(root, target) leaves the root. "..notes" is a
|
|
3
4
|
// name inside it, not a step out.
|
|
4
5
|
export function outside(inside) {
|
|
5
6
|
return inside === ".." || inside.startsWith(`..${sep}`) || isAbsolute(inside);
|
|
6
7
|
}
|
|
8
|
+
// The worktree root is the nearest directory with a `.git` entry (a folder in
|
|
9
|
+
// a checkout, a file in a linked worktree). Outside git, use the directory.
|
|
10
|
+
// The command and the panel both start from here, so a Paseo project set at a
|
|
11
|
+
// subfolder of the repo still reads the file the agent writes.
|
|
12
|
+
export function findRoot(cwd) {
|
|
13
|
+
let directory = resolve(cwd);
|
|
14
|
+
while (true) {
|
|
15
|
+
if (existsSync(join(directory, ".git")))
|
|
16
|
+
return directory;
|
|
17
|
+
const parent = dirname(directory);
|
|
18
|
+
if (parent === directory)
|
|
19
|
+
return resolve(cwd);
|
|
20
|
+
directory = parent;
|
|
21
|
+
}
|
|
22
|
+
}
|
package/dist/shared/dashboard.js
CHANGED
|
@@ -328,15 +328,17 @@ export function shortTitle(title) {
|
|
|
328
328
|
const index = title.indexOf(":");
|
|
329
329
|
return index > 0 && index <= 24 ? title.slice(0, index) : title;
|
|
330
330
|
}
|
|
331
|
-
//
|
|
332
|
-
//
|
|
333
|
-
//
|
|
334
|
-
//
|
|
335
|
-
//
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
331
|
+
// The finished tickets (done or skipped) the panel folds into one row at the
|
|
332
|
+
// top, in plan order, so the work still ahead sits near the top. A ticket
|
|
333
|
+
// finished out of order folds too. A single one isn't worth a fold row. Once
|
|
334
|
+
// every ticket is finished nothing is ahead, and the whole list is the record
|
|
335
|
+
// of the run, so nothing folds.
|
|
336
|
+
export function finishedToFold(tickets) {
|
|
337
|
+
const finished = tickets.filter(isFinished);
|
|
338
|
+
return finished.length >= 2 && finished.length < tickets.length ? finished : [];
|
|
339
|
+
}
|
|
340
|
+
export function isFinished(ticket) {
|
|
341
|
+
return ticket.status === "done" || ticket.status === "skipped";
|
|
340
342
|
}
|
|
341
343
|
function plural(count, noun) {
|
|
342
344
|
return `${count} ${count === 1 ? noun : `${noun}s`}`;
|
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.8",
|
|
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.8",
|
|
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.8",
|
|
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/server/cli.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
// The command agents run to record progress. Each command appends one event
|
|
2
2
|
// to the worktree's progress file and prints what it recorded.
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { realpathSync, statSync } from "node:fs";
|
|
4
|
+
import { relative, resolve } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { parseArgs } from "node:util";
|
|
7
7
|
import { isSettled, type Dashboard, type NextIds } from "../shared/dashboard.ts";
|
|
8
8
|
import { DELIVERABLE_KINDS, PROGRESS_FILE, TICKET_STATUSES } from "../shared/events.ts";
|
|
9
9
|
import { appendProgress, InvalidEvent, readProgress, type EventDraft } from "./progress-file.ts";
|
|
10
|
-
import { outside } from "./paths.ts";
|
|
10
|
+
import { findRoot, outside } from "./paths.ts";
|
|
11
11
|
import { installLauncher } from "./launcher.ts";
|
|
12
12
|
import { dashboardText } from "../shared/show.ts";
|
|
13
13
|
import { imageMimeType } from "../shared/attachments.ts";
|
|
@@ -449,18 +449,6 @@ function help(): string {
|
|
|
449
449
|
].join("\n");
|
|
450
450
|
}
|
|
451
451
|
|
|
452
|
-
// The worktree root is the nearest directory with a `.git` entry (a folder in
|
|
453
|
-
// a checkout, a file in a linked worktree). Outside git, use the directory.
|
|
454
|
-
export function findRoot(cwd: string): string {
|
|
455
|
-
let directory = resolve(cwd);
|
|
456
|
-
while (true) {
|
|
457
|
-
if (existsSync(join(directory, ".git"))) return directory;
|
|
458
|
-
const parent = dirname(directory);
|
|
459
|
-
if (parent === directory) return resolve(cwd);
|
|
460
|
-
directory = parent;
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
|
|
464
452
|
export async function runCli(argv: string[], options: CliOptions): Promise<number> {
|
|
465
453
|
const out = options.out ?? ((line: string) => console.log(line));
|
|
466
454
|
const now = options.now ?? (() => new Date());
|
package/server/dashboard.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { createHash } from "node:crypto";
|
|
|
3
3
|
import type { Dashboard } from "../shared/dashboard.ts";
|
|
4
4
|
import type { AttentionResult, DashboardResult } from "../shared/rpc.ts";
|
|
5
5
|
import { markPanelOpened, readProgress } from "./progress-file.ts";
|
|
6
|
+
import { findRoot } from "./paths.ts";
|
|
6
7
|
|
|
7
8
|
const versions = new WeakMap<Dashboard, { panelOpened: boolean; version: string }>();
|
|
8
9
|
|
|
@@ -10,7 +11,7 @@ export async function handleMarkPanelOpened(
|
|
|
10
11
|
input: { workspaceId: string; workspaceDirectory: string },
|
|
11
12
|
_context: PluginHandlerContext,
|
|
12
13
|
) {
|
|
13
|
-
await markPanelOpened(input.workspaceDirectory);
|
|
14
|
+
await markPanelOpened(findRoot(input.workspaceDirectory));
|
|
14
15
|
return {};
|
|
15
16
|
}
|
|
16
17
|
|
|
@@ -20,14 +21,15 @@ export async function handleGetDashboard(
|
|
|
20
21
|
input: { workspaceId: string; workspaceDirectory: string; since?: string },
|
|
21
22
|
_context: PluginHandlerContext,
|
|
22
23
|
): Promise<DashboardResult | { unchanged: true; version: string }> {
|
|
23
|
-
const
|
|
24
|
+
const root = findRoot(input.workspaceDirectory);
|
|
25
|
+
const result = await readProgress(root);
|
|
24
26
|
let cached = versions.get(result.dashboard);
|
|
25
27
|
if (!cached || cached.panelOpened !== result.panelOpened) {
|
|
26
28
|
cached = { panelOpened: result.panelOpened, version: createHash("sha1").update(JSON.stringify(result)).digest("base64url") };
|
|
27
29
|
versions.set(result.dashboard, cached);
|
|
28
30
|
}
|
|
29
31
|
const { version } = cached;
|
|
30
|
-
return input.since === version ? { unchanged: true, version } : { ...result, version };
|
|
32
|
+
return input.since === version ? { unchanged: true, version } : { ...result, root, version };
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
// Just what a message-box pill needs, so polling every workspace stays small.
|
|
@@ -35,7 +37,7 @@ export async function handleGetAttention(
|
|
|
35
37
|
input: { workspaceId: string; workspaceDirectory: string },
|
|
36
38
|
_context: PluginHandlerContext,
|
|
37
39
|
): Promise<AttentionResult> {
|
|
38
|
-
const { configured, dashboard, panelOpened } = await readProgress(input.workspaceDirectory);
|
|
40
|
+
const { configured, dashboard, panelOpened } = await readProgress(findRoot(input.workspaceDirectory));
|
|
39
41
|
return {
|
|
40
42
|
configured,
|
|
41
43
|
questions: dashboard.questions.open.length,
|
package/server/open.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { join, relative, resolve } from "node:path";
|
|
|
6
6
|
import { promisify } from "node:util";
|
|
7
7
|
import { findAttachment, imageMimeType, previewKind } from "../shared/attachments.ts";
|
|
8
8
|
import { readProgress } from "./progress-file.ts";
|
|
9
|
-
import { outside } from "./paths.ts";
|
|
9
|
+
import { findRoot, outside } from "./paths.ts";
|
|
10
10
|
|
|
11
11
|
export type Opener = (path: string) => Promise<void>;
|
|
12
12
|
|
|
@@ -43,10 +43,11 @@ export const systemOpener: Opener = async (path) => {
|
|
|
43
43
|
// so the panel can never reach an arbitrary path. Only files inside the
|
|
44
44
|
// worktree resolve.
|
|
45
45
|
async function resolveAttachment(directory: string, ref: string, verb: string): Promise<{ path: string; target: string }> {
|
|
46
|
-
const
|
|
46
|
+
const worktree = findRoot(directory);
|
|
47
|
+
const { dashboard } = await readProgress(worktree);
|
|
47
48
|
const path = findAttachment(dashboard, ref)?.path;
|
|
48
49
|
if (!path) throw new Error(`No local ${ref.startsWith("Q") ? "attachment" : "deliverable"} ${ref} in this worktree.`);
|
|
49
|
-
const root = await realpath(
|
|
50
|
+
const root = await realpath(worktree);
|
|
50
51
|
const target = await realpath(resolve(root, path)).catch(() => null);
|
|
51
52
|
if (!target) throw new Error(`${path} no longer exists.`);
|
|
52
53
|
const inside = relative(root, target);
|
package/server/paths.ts
CHANGED
|
@@ -1,7 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
2
3
|
|
|
3
4
|
// Whether a path from relative(root, target) leaves the root. "..notes" is a
|
|
4
5
|
// name inside it, not a step out.
|
|
5
6
|
export function outside(inside: string): boolean {
|
|
6
7
|
return inside === ".." || inside.startsWith(`..${sep}`) || isAbsolute(inside);
|
|
7
8
|
}
|
|
9
|
+
|
|
10
|
+
// The worktree root is the nearest directory with a `.git` entry (a folder in
|
|
11
|
+
// a checkout, a file in a linked worktree). Outside git, use the directory.
|
|
12
|
+
// The command and the panel both start from here, so a Paseo project set at a
|
|
13
|
+
// subfolder of the repo still reads the file the agent writes.
|
|
14
|
+
export function findRoot(cwd: string): string {
|
|
15
|
+
let directory = resolve(cwd);
|
|
16
|
+
while (true) {
|
|
17
|
+
if (existsSync(join(directory, ".git"))) return directory;
|
|
18
|
+
const parent = dirname(directory);
|
|
19
|
+
if (parent === directory) return resolve(cwd);
|
|
20
|
+
directory = parent;
|
|
21
|
+
}
|
|
22
|
+
}
|
package/shared/dashboard.ts
CHANGED
|
@@ -449,15 +449,18 @@ export function shortTitle(title: string): string {
|
|
|
449
449
|
return index > 0 && index <= 24 ? title.slice(0, index) : title;
|
|
450
450
|
}
|
|
451
451
|
|
|
452
|
-
//
|
|
453
|
-
//
|
|
454
|
-
//
|
|
455
|
-
//
|
|
456
|
-
//
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
452
|
+
// The finished tickets (done or skipped) the panel folds into one row at the
|
|
453
|
+
// top, in plan order, so the work still ahead sits near the top. A ticket
|
|
454
|
+
// finished out of order folds too. A single one isn't worth a fold row. Once
|
|
455
|
+
// every ticket is finished nothing is ahead, and the whole list is the record
|
|
456
|
+
// of the run, so nothing folds.
|
|
457
|
+
export function finishedToFold(tickets: Ticket[]): Ticket[] {
|
|
458
|
+
const finished = tickets.filter(isFinished);
|
|
459
|
+
return finished.length >= 2 && finished.length < tickets.length ? finished : [];
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
export function isFinished(ticket: Ticket): boolean {
|
|
463
|
+
return ticket.status === "done" || ticket.status === "skipped";
|
|
461
464
|
}
|
|
462
465
|
|
|
463
466
|
function plural(count: number, noun: string): string {
|
package/shared/rpc.ts
CHANGED
|
@@ -3,8 +3,10 @@ import { z } from "zod";
|
|
|
3
3
|
import type { Dashboard } from "./dashboard.ts";
|
|
4
4
|
|
|
5
5
|
// `panelOpened`: the Progress panel has been opened for this worktree before.
|
|
6
|
+
// `root`: the worktree root the file was read from, which stored paths are
|
|
7
|
+
// relative to. It can sit above the workspace directory.
|
|
6
8
|
// `version` changes whenever anything else in the result does.
|
|
7
|
-
export type DashboardResult = { configured: boolean; dashboard: Dashboard; panelOpened: boolean; version: string };
|
|
9
|
+
export type DashboardResult = { configured: boolean; dashboard: Dashboard; panelOpened: boolean; root: string; version: string };
|
|
8
10
|
|
|
9
11
|
export const getDashboard = defineRpc({
|
|
10
12
|
name: "progress.get",
|