@omercnet/paseo-beads 0.1.0-next.72.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/CHANGELOG.md +22 -0
- package/LICENSE +21 -0
- package/README.md +115 -0
- package/client/beads-view.ts +155 -0
- package/client/paseo-beads.tsx +1222 -0
- package/client/web.ts +15 -0
- package/docs/images/paseo-beads-compact.png +0 -0
- package/docs/images/paseo-beads-wide.png +0 -0
- package/index.client.tsx +39 -0
- package/index.server.ts +9 -0
- package/package.json +70 -0
- package/paseo-plugin.json +6 -0
- package/server/beads.ts +324 -0
- package/shared/beads.ts +89 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.1.0](https://github.com/omercnet/paseo-plugins/compare/paseo-beads-v0.0.1...paseo-beads-v0.1.0) (2026-09-11)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **paseo-beads:** add workspace bead viewer ([#13](https://github.com/omercnet/paseo-plugins/issues/13)) ([96e02e5](https://github.com/omercnet/paseo-plugins/commit/96e02e5521521b738cf11e8240d3926849898c3e))
|
|
9
|
+
|
|
10
|
+
## 0.0.1 (2026-09-11)
|
|
11
|
+
|
|
12
|
+
### Features
|
|
13
|
+
|
|
14
|
+
- add a read-only, workspace-scoped Beads Explorer panel and Command Center entry
|
|
15
|
+
- group active issues into Ready frontier, In progress, Blocked, and Other lanes using an
|
|
16
|
+
authoritative `bd list --ready` snapshot
|
|
17
|
+
- add virtualized issue lists, search, and All, P0–P1, and Assigned filters
|
|
18
|
+
- add responsive issue details with context, acceptance criteria, dependencies, and dependents
|
|
19
|
+
- bound CLI output and RPC payloads while keeping unexpected CLI details out of client-facing errors
|
|
20
|
+
- add descriptive accessibility labels and focus restoration for compact list/detail navigation
|
|
21
|
+
- poll list and selected detail data every 10 seconds through read-only `bd` CLI calls
|
|
22
|
+
- report unavailable, uninitialized, empty, failed, missing-detail, and truncated states
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Omer Cohen
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Paseo Beads
|
|
2
|
+
|
|
3
|
+
A dependency-aware Beads work queue for every Paseo workspace. Paseo Beads turns active issues into
|
|
4
|
+
a read-only delivery view that makes the ready frontier, work in progress, and blockers immediately
|
|
5
|
+
visible.
|
|
6
|
+
|
|
7
|
+
It adds a workspace-scoped **Beads** Explorer panel and an **Open Beads** Command Center item.
|
|
8
|
+
|
|
9
|
+
## Screenshots
|
|
10
|
+
|
|
11
|
+
Issue IDs, titles, assignees, labels, and issue details in these screenshots are synthetic. They do
|
|
12
|
+
not contain data from a real Beads project.
|
|
13
|
+
|
|
14
|
+
### Wide
|
|
15
|
+
|
|
16
|
+

|
|
17
|
+
|
|
18
|
+
### Compact
|
|
19
|
+
|
|
20
|
+

|
|
21
|
+
|
|
22
|
+
## What it shows
|
|
23
|
+
|
|
24
|
+
- Active issues grouped into **Ready frontier**, **In progress**, **Blocked**, and **Other** lanes.
|
|
25
|
+
- Priority, issue ID, title, type, assignee, labels, and relationship and comment counts on each row.
|
|
26
|
+
- Lane totals, text search over ID, title, assignee, and labels, and **All**, **P0–P1**, and
|
|
27
|
+
**Assigned** filters.
|
|
28
|
+
- A detail view with status, readiness, priority, type, assignee, parent, labels, description,
|
|
29
|
+
acceptance criteria, design, notes, dependencies, dependents, and update time.
|
|
30
|
+
- A side-by-side list and detail view when space permits, with list/detail navigation in compact
|
|
31
|
+
layouts.
|
|
32
|
+
- A virtualized issue list, descriptive accessibility labels, and focus restoration when navigating
|
|
33
|
+
between the compact list and detail view.
|
|
34
|
+
|
|
35
|
+
Within each lane, issues are ordered by priority, most recent update, then ID. In-progress and hooked
|
|
36
|
+
issues enter **In progress** first; remaining blocked issues enter **Blocked**; ready issues enter
|
|
37
|
+
**Ready frontier**; all remaining active issues enter **Other**.
|
|
38
|
+
|
|
39
|
+
## How it works
|
|
40
|
+
|
|
41
|
+
The panel calls workspace-scoped Paseo plugin RPC handlers. On the daemon host, each handler resolves
|
|
42
|
+
the workspace directory through Paseo and executes the `bd` CLI with `--readonly` and `-C`:
|
|
43
|
+
|
|
44
|
+
- The list handler runs `bd list --json --sort priority`, then runs one authoritative
|
|
45
|
+
`bd list --ready --json --limit 0` query and intersects its IDs with the displayed issues.
|
|
46
|
+
- Selecting an issue runs `bd show <issue-id> --json --include-dependents` plus the same authoritative
|
|
47
|
+
readiness query.
|
|
48
|
+
|
|
49
|
+
The server validates and normalizes the CLI JSON before returning bounded data to the panel. The
|
|
50
|
+
plugin never reads or writes `.beads` storage directly and exposes no issue mutation controls.
|
|
51
|
+
|
|
52
|
+
The list and selected detail poll every 10 seconds. **Refresh** requests the list immediately. A
|
|
53
|
+
failed background refresh keeps the previous data visible with an inline error; initial and detail
|
|
54
|
+
failures provide a retry action.
|
|
55
|
+
|
|
56
|
+
## Limits
|
|
57
|
+
|
|
58
|
+
- Paseo `^0.8.0` with plugins enabled is required.
|
|
59
|
+
- Beads `bd` 1.0 or newer must be available on the Paseo daemon's `PATH`.
|
|
60
|
+
- A Beads project must be initialized in the workspace for issue data to appear.
|
|
61
|
+
- CLI calls time out after 10 seconds and accept at most 8 MiB of output. Unexpected CLI details stay
|
|
62
|
+
in Paseo plugin logs; the panel receives a stable, sanitized error message.
|
|
63
|
+
- At most 500 issues are displayed. When more exist, the panel warns that lane counts and local
|
|
64
|
+
search results may be incomplete.
|
|
65
|
+
- The server rejects oversized IDs, titles, body fields, labels, relationships, comments, and
|
|
66
|
+
readiness sets rather than transferring unbounded data to the client.
|
|
67
|
+
- Search and filters operate only on the displayed snapshot.
|
|
68
|
+
- The panel distinguishes an unavailable `bd` binary, an uninitialized Beads project, an empty
|
|
69
|
+
project, request failures, and missing issue details.
|
|
70
|
+
- This plugin is read-only and workspace-scoped. It uses the `bd` CLI exclusively and does not
|
|
71
|
+
create, edit, close, or assign issues.
|
|
72
|
+
|
|
73
|
+
## Install
|
|
74
|
+
|
|
75
|
+
Paseo plugins are trusted, unsandboxed code. Review the source before installing it on the daemon
|
|
76
|
+
host.
|
|
77
|
+
|
|
78
|
+
From npm:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
paseo plugin install npm:@omercnet/paseo-beads
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
From GitHub:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
paseo plugin add omercnet/paseo-plugins:paseo-beads
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
From a local checkout on the Paseo daemon host:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
git clone https://github.com/omercnet/paseo-plugins.git
|
|
94
|
+
cd paseo-plugins/paseo-beads
|
|
95
|
+
npm ci
|
|
96
|
+
paseo plugin install "$PWD"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Open a workspace, choose **New tab** in Explorer, then select **Beads**. You can also run **Open
|
|
100
|
+
Beads** from the Command Center while viewing a workspace or one of its agents.
|
|
101
|
+
|
|
102
|
+
## Develop
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
npm ci
|
|
106
|
+
npm run check
|
|
107
|
+
npm test
|
|
108
|
+
npm run test:coverage
|
|
109
|
+
npm run typecheck
|
|
110
|
+
paseo plugin install "$PWD"
|
|
111
|
+
paseo plugin reload paseo-beads
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The package is `@omercnet/paseo-beads` at version `0.0.1`. Release Please maintains versions,
|
|
115
|
+
changelog entries, component tags, and GitHub releases from Conventional Commits in the monorepo.
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import type { BeadSummary } from "../shared/beads";
|
|
2
|
+
|
|
3
|
+
export const BEAD_LANES = ["ready", "in_progress", "blocked", "other"] as const;
|
|
4
|
+
|
|
5
|
+
export type BeadLane = (typeof BEAD_LANES)[number];
|
|
6
|
+
|
|
7
|
+
export const BEAD_LANE_TITLES: Record<BeadLane, string> = {
|
|
8
|
+
ready: "Ready",
|
|
9
|
+
in_progress: "In progress",
|
|
10
|
+
blocked: "Blocked",
|
|
11
|
+
other: "Other",
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const BEAD_LANE_METADATA: readonly { id: BeadLane; title: string }[] = BEAD_LANES.map(
|
|
15
|
+
(id) => ({ id, title: BEAD_LANE_TITLES[id] }),
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
export type BeadsFilter = "all" | "high_priority" | "assigned";
|
|
19
|
+
|
|
20
|
+
export interface BeadsViewOptions {
|
|
21
|
+
query?: string;
|
|
22
|
+
filter?: BeadsFilter;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type BeadLanes = Record<BeadLane, BeadSummary[]>;
|
|
26
|
+
export type BeadLaneCounts = Record<BeadLane, number>;
|
|
27
|
+
|
|
28
|
+
export interface BeadsView {
|
|
29
|
+
lanes: BeadLanes;
|
|
30
|
+
counts: BeadLaneCounts;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface BeadSection {
|
|
34
|
+
lane: BeadLane;
|
|
35
|
+
title: string;
|
|
36
|
+
data: BeadSummary[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function buildBeadSections(view: BeadsView, activeFiltering: boolean): BeadSection[] {
|
|
40
|
+
const sections: BeadSection[] = [];
|
|
41
|
+
|
|
42
|
+
for (const lane of BEAD_LANES) {
|
|
43
|
+
const data = view.lanes[lane];
|
|
44
|
+
if (activeFiltering && data.length === 0) continue;
|
|
45
|
+
|
|
46
|
+
sections.push({
|
|
47
|
+
lane,
|
|
48
|
+
title: lane === "ready" ? "Ready frontier" : BEAD_LANE_TITLES[lane],
|
|
49
|
+
data,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return sections;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function issueAccessibilityLabel(issue: BeadSummary, lane: BeadLane): string {
|
|
57
|
+
const laneDescription = lane === "ready" ? "Ready frontier" : `${BEAD_LANE_TITLES[lane]} lane`;
|
|
58
|
+
const assigneeDescription = issue.assignee ? `Assigned to ${issue.assignee}` : "Unassigned";
|
|
59
|
+
const activity: string[] = [];
|
|
60
|
+
|
|
61
|
+
if (issue.dependencyCount > 0) {
|
|
62
|
+
activity.push(
|
|
63
|
+
`${issue.dependencyCount} ${issue.dependencyCount === 1 ? "dependency" : "dependencies"}`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
if (issue.dependentCount > 0) {
|
|
67
|
+
activity.push(
|
|
68
|
+
`${issue.dependentCount} ${issue.dependentCount === 1 ? "dependent" : "dependents"}`,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
if (issue.commentCount > 0) {
|
|
72
|
+
activity.push(`${issue.commentCount} ${issue.commentCount === 1 ? "comment" : "comments"}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const descriptions = [
|
|
76
|
+
`Open issue ${issue.id}: ${issue.title}`,
|
|
77
|
+
`Priority P${issue.priority}`,
|
|
78
|
+
laneDescription,
|
|
79
|
+
`Issue type ${issue.issueType}`,
|
|
80
|
+
assigneeDescription,
|
|
81
|
+
...activity,
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
return `${descriptions.join(". ")}.`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function beadLaneFor(issue: BeadSummary): BeadLane {
|
|
88
|
+
if (issue.status === "in_progress" || issue.status === "hooked") return "in_progress";
|
|
89
|
+
if (issue.isBlocked) return "blocked";
|
|
90
|
+
if (issue.isReady) return "ready";
|
|
91
|
+
return "other";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function matchesSearch(issue: BeadSummary, query: string): boolean {
|
|
95
|
+
if (!query) return true;
|
|
96
|
+
if (issue.id.toLowerCase().includes(query)) return true;
|
|
97
|
+
if (issue.title.toLowerCase().includes(query)) return true;
|
|
98
|
+
if (issue.assignee?.toLowerCase().includes(query)) return true;
|
|
99
|
+
return issue.labels.some((label) => label.toLowerCase().includes(query));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function matchesFilter(issue: BeadSummary, filter: BeadsFilter): boolean {
|
|
103
|
+
if (filter === "high_priority") return issue.priority <= 1;
|
|
104
|
+
if (filter === "assigned") return issue.assignee !== null;
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function updatedTimestamp(updatedAt: string | null): number {
|
|
109
|
+
if (updatedAt === null) return Number.NEGATIVE_INFINITY;
|
|
110
|
+
const timestamp = Date.parse(updatedAt);
|
|
111
|
+
return Number.isFinite(timestamp) ? timestamp : Number.NEGATIVE_INFINITY;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function compareBeads(left: BeadSummary, right: BeadSummary): number {
|
|
115
|
+
const byPriority = left.priority - right.priority;
|
|
116
|
+
if (byPriority !== 0) return byPriority;
|
|
117
|
+
|
|
118
|
+
const leftUpdatedAt = updatedTimestamp(left.updatedAt);
|
|
119
|
+
const rightUpdatedAt = updatedTimestamp(right.updatedAt);
|
|
120
|
+
if (leftUpdatedAt !== rightUpdatedAt) return rightUpdatedAt > leftUpdatedAt ? 1 : -1;
|
|
121
|
+
|
|
122
|
+
if (left.id < right.id) return -1;
|
|
123
|
+
if (left.id > right.id) return 1;
|
|
124
|
+
return 0;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function buildBeadsView(
|
|
128
|
+
issues: readonly BeadSummary[],
|
|
129
|
+
options: BeadsViewOptions = {},
|
|
130
|
+
): BeadsView {
|
|
131
|
+
const lanes: BeadLanes = {
|
|
132
|
+
ready: [],
|
|
133
|
+
in_progress: [],
|
|
134
|
+
blocked: [],
|
|
135
|
+
other: [],
|
|
136
|
+
};
|
|
137
|
+
const counts: BeadLaneCounts = {
|
|
138
|
+
ready: 0,
|
|
139
|
+
in_progress: 0,
|
|
140
|
+
blocked: 0,
|
|
141
|
+
other: 0,
|
|
142
|
+
};
|
|
143
|
+
const query = options.query?.trim().toLowerCase() ?? "";
|
|
144
|
+
const filter = options.filter ?? "all";
|
|
145
|
+
|
|
146
|
+
for (const issue of issues) {
|
|
147
|
+
const lane = beadLaneFor(issue);
|
|
148
|
+
counts[lane] += 1;
|
|
149
|
+
if (matchesFilter(issue, filter) && matchesSearch(issue, query)) lanes[lane].push(issue);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
for (const lane of BEAD_LANES) lanes[lane].sort(compareBeads);
|
|
153
|
+
|
|
154
|
+
return { lanes, counts };
|
|
155
|
+
}
|