@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 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
+ ![Paseo Beads wide work queue](docs/images/paseo-beads-wide.png)
17
+
18
+ ### Compact
19
+
20
+ ![Paseo Beads compact work queue](docs/images/paseo-beads-compact.png)
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
+ }