@worca/ui 0.19.0 → 0.20.0
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/app/main.bundle.js +408 -413
- package/app/main.bundle.js.map +4 -4
- package/app/styles.css +16 -23
- package/app/utils/stage-order.js +2 -0
- package/package.json +1 -1
- package/server/bd-daemon.js +43 -0
- package/server/beads-reader.js +37 -23
- package/server/watcher-set.js +2 -0
- package/server/ws-beads-watcher.js +6 -2
package/app/styles.css
CHANGED
|
@@ -3436,29 +3436,27 @@ sl-details.learnings-panel::part(content) {
|
|
|
3436
3436
|
align-items: center;
|
|
3437
3437
|
}
|
|
3438
3438
|
|
|
3439
|
-
.pr-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
.pr-details-table {
|
|
3444
|
-
border-collapse: collapse;
|
|
3439
|
+
.pr-info-strip {
|
|
3440
|
+
display: flex;
|
|
3441
|
+
flex-wrap: wrap;
|
|
3442
|
+
gap: 4px 20px;
|
|
3445
3443
|
font-size: 13px;
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
vertical-align: middle;
|
|
3444
|
+
color: var(--muted);
|
|
3445
|
+
margin-top: 8px;
|
|
3446
|
+
padding-top: 8px;
|
|
3447
|
+
border-top: 1px solid var(--border-subtle);
|
|
3448
|
+
align-items: center;
|
|
3452
3449
|
}
|
|
3453
3450
|
|
|
3454
|
-
.pr-
|
|
3451
|
+
.pr-info-item {
|
|
3452
|
+
display: inline-flex;
|
|
3453
|
+
align-items: center;
|
|
3454
|
+
gap: 4px;
|
|
3455
3455
|
white-space: nowrap;
|
|
3456
|
-
width: 1%;
|
|
3457
|
-
padding-right: 10px;
|
|
3458
3456
|
}
|
|
3459
3457
|
|
|
3460
|
-
.pr-
|
|
3461
|
-
display: flex;
|
|
3458
|
+
.pr-info-strip .run-pr-link {
|
|
3459
|
+
display: inline-flex;
|
|
3462
3460
|
align-items: center;
|
|
3463
3461
|
gap: 4px;
|
|
3464
3462
|
}
|
|
@@ -3472,13 +3470,8 @@ sl-details.learnings-panel::part(content) {
|
|
|
3472
3470
|
border: 1px solid var(--border-subtle);
|
|
3473
3471
|
}
|
|
3474
3472
|
|
|
3475
|
-
.pr-branch-flow {
|
|
3476
|
-
font-family: monospace;
|
|
3477
|
-
font-size: 12px;
|
|
3478
|
-
}
|
|
3479
|
-
|
|
3480
3473
|
.pr-title-badge {
|
|
3481
|
-
|
|
3474
|
+
/* spacing handled by parent .pipeline-stage-header gap */
|
|
3482
3475
|
}
|
|
3483
3476
|
|
|
3484
3477
|
.classification-strip {
|
package/app/utils/stage-order.js
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { dirname, join, resolve } from 'node:path';
|
|
4
|
+
import { promisify } from 'node:util';
|
|
5
|
+
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Ensure the bd daemon is running for the project at worcaDir.
|
|
10
|
+
* Best-effort — all errors swallowed.
|
|
11
|
+
*
|
|
12
|
+
* Probes `bd daemon status` first. The `daemon.stopped` sentinel only blocks
|
|
13
|
+
* auto-start; if the daemon is already running (e.g. started manually outside
|
|
14
|
+
* worca), we report it as up regardless of the sentinel.
|
|
15
|
+
*/
|
|
16
|
+
export async function ensureBdDaemon(worcaDir) {
|
|
17
|
+
const beadsDir = resolve(join(worcaDir, '..', '.beads'));
|
|
18
|
+
if (!existsSync(beadsDir)) return false;
|
|
19
|
+
|
|
20
|
+
const workspaceDir = dirname(beadsDir);
|
|
21
|
+
const opts = {
|
|
22
|
+
encoding: 'utf8',
|
|
23
|
+
timeout: 5000,
|
|
24
|
+
env: { ...process.env, BEADS_DIR: beadsDir },
|
|
25
|
+
cwd: workspaceDir,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
await execFileAsync('bd', ['daemon', 'status'], opts);
|
|
30
|
+
return true;
|
|
31
|
+
} catch {
|
|
32
|
+
// not running — sentinel may block auto-start below
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (existsSync(join(beadsDir, 'daemon.stopped'))) return false;
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
await execFileAsync('bd', ['daemon', 'start'], opts);
|
|
39
|
+
return true;
|
|
40
|
+
} catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
package/server/beads-reader.js
CHANGED
|
@@ -49,7 +49,10 @@ export function dbExists(beadsDb) {
|
|
|
49
49
|
export async function listIssues(beadsDb) {
|
|
50
50
|
try {
|
|
51
51
|
const issues = await runBd(['list', '--limit', '0'], beadsDb);
|
|
52
|
-
|
|
52
|
+
// Must await here — without it, an enrichWithDeps rejection (e.g. bd show
|
|
53
|
+
// SIGTERM under daemon contention) escapes the try/catch and propagates
|
|
54
|
+
// to the WS handler as an unhandled rejection, crashing Node.
|
|
55
|
+
return await enrichWithDeps(issues, beadsDb);
|
|
53
56
|
} catch {
|
|
54
57
|
return [];
|
|
55
58
|
}
|
|
@@ -61,7 +64,7 @@ export async function listIssuesByLabel(beadsDb, label) {
|
|
|
61
64
|
['list', '--label-any', label, '--all', '--limit', '0'],
|
|
62
65
|
beadsDb,
|
|
63
66
|
);
|
|
64
|
-
return enrichWithDeps(issues, beadsDb);
|
|
67
|
+
return await enrichWithDeps(issues, beadsDb);
|
|
65
68
|
} catch {
|
|
66
69
|
return [];
|
|
67
70
|
}
|
|
@@ -92,37 +95,48 @@ export async function listUnlinkedIssues(beadsDb) {
|
|
|
92
95
|
/**
|
|
93
96
|
* Returns { runId: { total, done } } for every run:<id> label in the beads db.
|
|
94
97
|
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
98
|
+
* Single-pass: bd label list-all (totals) + bd list --all (ids) +
|
|
99
|
+
* bd show <all-ids> (labels + statuses), then group by run labels in JS.
|
|
100
|
+
* Always 3 bd calls regardless of run-label count.
|
|
101
|
+
*
|
|
102
|
+
* Called by the beads watcher on every db change (counts are included in the
|
|
103
|
+
* broadcast payload) and by the list-beads-counts endpoint for initial load
|
|
104
|
+
* and project switch.
|
|
100
105
|
*/
|
|
101
106
|
export async function countIssuesByRunLabel(beadsDb) {
|
|
102
107
|
try {
|
|
103
108
|
const rows = await runBd(['label', 'list-all'], beadsDb);
|
|
104
109
|
const counts = {};
|
|
105
110
|
const runLabels = rows.filter((r) => r.label.startsWith('run:'));
|
|
111
|
+
if (runLabels.length === 0) return counts;
|
|
106
112
|
for (const row of runLabels) {
|
|
107
113
|
counts[row.label.replace('run:', '')] = { total: row.count, done: 0 };
|
|
108
114
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
115
|
+
try {
|
|
116
|
+
const issues = await runBd(['list', '--all', '--limit', '0'], beadsDb);
|
|
117
|
+
if (issues.length === 0) return counts;
|
|
118
|
+
const detailed = await runBd(
|
|
119
|
+
['show', ...issues.map((i) => i.id)],
|
|
120
|
+
beadsDb,
|
|
121
|
+
);
|
|
122
|
+
for (const issue of detailed) {
|
|
123
|
+
if (issue.status !== 'closed') continue;
|
|
124
|
+
for (const label of issue.labels || []) {
|
|
125
|
+
if (label.startsWith('run:')) {
|
|
126
|
+
const runId = label.replace('run:', '');
|
|
127
|
+
if (counts[runId]) counts[runId].done++;
|
|
128
|
+
}
|
|
123
129
|
}
|
|
124
|
-
}
|
|
125
|
-
)
|
|
130
|
+
}
|
|
131
|
+
} catch (err) {
|
|
132
|
+
// Leave done=0 for all runs on list/show failure. Logged so a stale
|
|
133
|
+
// "0/N" badge can be traced back to bd subprocess timeout (typically
|
|
134
|
+
// daemon contention) rather than mistaken for "no closed issues."
|
|
135
|
+
// The next watcher tick recomputes and corrects.
|
|
136
|
+
console.warn(
|
|
137
|
+
`[countIssuesByRunLabel] bd list/show failed; counts.done left at 0: ${err?.message || err}`,
|
|
138
|
+
);
|
|
139
|
+
}
|
|
126
140
|
return counts;
|
|
127
141
|
} catch {
|
|
128
142
|
return {};
|
package/server/watcher-set.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import { existsSync } from 'node:fs';
|
|
12
12
|
import { join } from 'node:path';
|
|
13
|
+
import { ensureBdDaemon } from './bd-daemon.js';
|
|
13
14
|
import { resolveRunDir } from './run-dir-resolver.js';
|
|
14
15
|
import { createBeadsWatcher } from './ws-beads-watcher.js';
|
|
15
16
|
import { createEventWatcher } from './ws-event-watcher.js';
|
|
@@ -146,6 +147,7 @@ export class WatcherSet {
|
|
|
146
147
|
|
|
147
148
|
// Beads watcher
|
|
148
149
|
if (!this.beadsWatcher) {
|
|
150
|
+
ensureBdDaemon(worcaDir).catch(() => {});
|
|
149
151
|
try {
|
|
150
152
|
this.beadsWatcher = this._factories.createBeadsWatcher({
|
|
151
153
|
worcaDir,
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { existsSync, unwatchFile, watch, watchFile } from 'node:fs';
|
|
8
8
|
import { join, resolve } from 'node:path';
|
|
9
|
-
import { listIssues } from './beads-reader.js';
|
|
9
|
+
import { countIssuesByRunLabel, listIssues } from './beads-reader.js';
|
|
10
10
|
|
|
11
11
|
const BEADS_DEBOUNCE_MS = 500;
|
|
12
12
|
const BEADS_POLL_MS = 2000;
|
|
@@ -26,11 +26,15 @@ export function createBeadsWatcher({ worcaDir, broadcaster, projectId }) {
|
|
|
26
26
|
BEADS_REFRESH_TIMER = setTimeout(async () => {
|
|
27
27
|
BEADS_REFRESH_TIMER = null;
|
|
28
28
|
try {
|
|
29
|
-
const issues = await
|
|
29
|
+
const [issues, counts] = await Promise.all([
|
|
30
|
+
listIssues(beadsDbPath),
|
|
31
|
+
countIssuesByRunLabel(beadsDbPath).catch(() => ({})),
|
|
32
|
+
]);
|
|
30
33
|
broadcaster.broadcast(
|
|
31
34
|
'beads-update',
|
|
32
35
|
{
|
|
33
36
|
issues,
|
|
37
|
+
counts,
|
|
34
38
|
dbExists: true,
|
|
35
39
|
dbPath: beadsDbPath,
|
|
36
40
|
},
|