beads-ui 0.10.1 → 0.11.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/CHANGES.md +9 -0
- package/package.json +1 -1
- package/server/bd.js +22 -1
- package/server/cli/commands.js +83 -16
package/CHANGES.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changes
|
|
2
2
|
|
|
3
|
+
## 0.11.0
|
|
4
|
+
|
|
5
|
+
- [`fc00b87`](https://github.com/mantoni/beads-ui/commit/fc00b87cfd1b6600a9b9088a9f62c2f6e8fc919e)
|
|
6
|
+
fix(ui): harden daemon restart workspace registration (Leon Letto)
|
|
7
|
+
- [`2ea0dd0`](https://github.com/mantoni/beads-ui/commit/2ea0dd08eb71625fa3ae51e64ea6501b4d058154)
|
|
8
|
+
perf(ui): reduce list latency by default sandbox bd calls (Leon Letto)
|
|
9
|
+
|
|
10
|
+
_Released by [Maximilian Antoni](https://github.com/mantoni) on 2026-03-05._
|
|
11
|
+
|
|
3
12
|
## 0.10.1
|
|
4
13
|
|
|
5
14
|
- [`62017f7`](https://github.com/mantoni/beads-ui/commit/62017f74fadb439c7270160ac03866d3554f36a3)
|
package/package.json
CHANGED
package/server/bd.js
CHANGED
|
@@ -94,7 +94,7 @@ function runBdUnlocked(args, options = {}) {
|
|
|
94
94
|
};
|
|
95
95
|
|
|
96
96
|
/** @type {string[]} */
|
|
97
|
-
const final_args = args
|
|
97
|
+
const final_args = buildBdArgs(args);
|
|
98
98
|
|
|
99
99
|
return new Promise((resolve) => {
|
|
100
100
|
const child = spawn(bin, final_args, spawn_opts);
|
|
@@ -151,6 +151,27 @@ function runBdUnlocked(args, options = {}) {
|
|
|
151
151
|
});
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Build final bd CLI arguments.
|
|
156
|
+
* bdui defaults to sandbox mode to avoid sync/autopush overhead on interactive
|
|
157
|
+
* UI requests. Set `BDUI_BD_SANDBOX=0` (or "false") to opt out.
|
|
158
|
+
*
|
|
159
|
+
* @param {string[]} args
|
|
160
|
+
* @returns {string[]}
|
|
161
|
+
*/
|
|
162
|
+
function buildBdArgs(args) {
|
|
163
|
+
const arg_set = new Set(args);
|
|
164
|
+
const raw_sandbox = String(process.env.BDUI_BD_SANDBOX || '').toLowerCase();
|
|
165
|
+
const sandbox_disabled = raw_sandbox === '0' || raw_sandbox === 'false';
|
|
166
|
+
const should_prepend_sandbox = !sandbox_disabled && !arg_set.has('--sandbox');
|
|
167
|
+
|
|
168
|
+
if (!should_prepend_sandbox) {
|
|
169
|
+
return args.slice();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return ['--sandbox', ...args];
|
|
173
|
+
}
|
|
174
|
+
|
|
154
175
|
/**
|
|
155
176
|
* Serialize `bd` invocations.
|
|
156
177
|
* Dolt embedded mode can crash when multiple `bd` processes run concurrently
|
package/server/cli/commands.js
CHANGED
|
@@ -10,6 +10,10 @@ import {
|
|
|
10
10
|
} from './daemon.js';
|
|
11
11
|
import { openUrl, registerWorkspaceWithServer, waitForServer } from './open.js';
|
|
12
12
|
|
|
13
|
+
const STARTUP_SETTLE_MS = 200;
|
|
14
|
+
const REGISTER_RETRY_ATTEMPTS = 5;
|
|
15
|
+
const REGISTER_RETRY_DELAY_MS = 150;
|
|
16
|
+
|
|
13
17
|
/**
|
|
14
18
|
* Handle `start` command. Idempotent when already running.
|
|
15
19
|
* - Spawns a detached server process, writes PID file, returns 0.
|
|
@@ -21,27 +25,17 @@ import { openUrl, registerWorkspaceWithServer, waitForServer } from './open.js';
|
|
|
21
25
|
export async function handleStart(options) {
|
|
22
26
|
// Default: do not open a browser unless explicitly requested via `open: true`.
|
|
23
27
|
const should_open = options?.open === true;
|
|
28
|
+
const cwd = process.cwd();
|
|
24
29
|
const existing_pid = readPidFile();
|
|
25
30
|
if (existing_pid && isProcessRunning(existing_pid)) {
|
|
26
31
|
// Server is already running - register this workspace dynamically
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
if (
|
|
30
|
-
|
|
31
|
-
workspace_database.exists
|
|
32
|
-
) {
|
|
33
|
-
const { url } = getConfig();
|
|
34
|
-
const registered = await registerWorkspaceWithServer(url, {
|
|
35
|
-
path: cwd,
|
|
36
|
-
database: workspace_database.path
|
|
37
|
-
});
|
|
38
|
-
if (registered) {
|
|
39
|
-
console.log('Workspace registered: %s', cwd);
|
|
40
|
-
}
|
|
32
|
+
const { url } = getConfig();
|
|
33
|
+
const registered = await registerCurrentWorkspace(url, cwd);
|
|
34
|
+
if (registered) {
|
|
35
|
+
console.log('Workspace registered: %s', cwd);
|
|
41
36
|
}
|
|
42
37
|
console.warn('Server is already running.');
|
|
43
38
|
if (should_open) {
|
|
44
|
-
const { url } = getConfig();
|
|
45
39
|
await openUrl(url);
|
|
46
40
|
}
|
|
47
41
|
return 0;
|
|
@@ -58,6 +52,7 @@ export async function handleStart(options) {
|
|
|
58
52
|
if (options?.port) {
|
|
59
53
|
process.env.PORT = String(options.port);
|
|
60
54
|
}
|
|
55
|
+
const { url } = getConfig();
|
|
61
56
|
|
|
62
57
|
const started = startDaemon({
|
|
63
58
|
is_debug: options?.is_debug,
|
|
@@ -65,10 +60,32 @@ export async function handleStart(options) {
|
|
|
65
60
|
port: options?.port
|
|
66
61
|
});
|
|
67
62
|
if (started && started.pid > 0) {
|
|
63
|
+
// Give the spawned daemon a brief moment to fail fast (for example EADDRINUSE).
|
|
64
|
+
await sleep(STARTUP_SETTLE_MS);
|
|
65
|
+
|
|
66
|
+
if (!isProcessRunning(started.pid)) {
|
|
67
|
+
removePidFile();
|
|
68
|
+
|
|
69
|
+
// If another server is already running at the configured URL, register this
|
|
70
|
+
// workspace there so it appears in the picker instead of silently missing.
|
|
71
|
+
const registered = await registerCurrentWorkspaceWithRetry(url, cwd);
|
|
72
|
+
if (registered) {
|
|
73
|
+
console.warn(
|
|
74
|
+
'Daemon exited early; registered workspace with existing server: %s',
|
|
75
|
+
cwd
|
|
76
|
+
);
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
79
|
+
return 1;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Register against the currently reachable server to ensure this workspace
|
|
83
|
+
// appears in the picker even when startup races with other daemons.
|
|
84
|
+
void registerCurrentWorkspaceWithRetry(url, cwd);
|
|
85
|
+
|
|
68
86
|
printServerUrl();
|
|
69
87
|
// Auto-open the browser once for a fresh daemon start
|
|
70
88
|
if (should_open) {
|
|
71
|
-
const { url } = getConfig();
|
|
72
89
|
// Wait briefly for the server to accept connections (single retry window)
|
|
73
90
|
await waitForServer(url, 600);
|
|
74
91
|
// Best-effort open; ignore result
|
|
@@ -80,6 +97,56 @@ export async function handleStart(options) {
|
|
|
80
97
|
return 1;
|
|
81
98
|
}
|
|
82
99
|
|
|
100
|
+
/**
|
|
101
|
+
* @param {number} ms
|
|
102
|
+
* @returns {Promise<void>}
|
|
103
|
+
*/
|
|
104
|
+
function sleep(ms) {
|
|
105
|
+
return new Promise((resolve) => {
|
|
106
|
+
setTimeout(() => {
|
|
107
|
+
resolve();
|
|
108
|
+
}, ms);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @param {string} url
|
|
114
|
+
* @param {string} cwd
|
|
115
|
+
* @returns {Promise<boolean>}
|
|
116
|
+
*/
|
|
117
|
+
async function registerCurrentWorkspace(url, cwd) {
|
|
118
|
+
const workspace_database = resolveWorkspaceDatabase({ cwd });
|
|
119
|
+
if (
|
|
120
|
+
workspace_database.source === 'home-default' ||
|
|
121
|
+
!workspace_database.exists
|
|
122
|
+
) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return registerWorkspaceWithServer(url, {
|
|
127
|
+
path: cwd,
|
|
128
|
+
database: workspace_database.path
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* @param {string} url
|
|
134
|
+
* @param {string} cwd
|
|
135
|
+
* @returns {Promise<boolean>}
|
|
136
|
+
*/
|
|
137
|
+
async function registerCurrentWorkspaceWithRetry(url, cwd) {
|
|
138
|
+
for (let i = 0; i < REGISTER_RETRY_ATTEMPTS; i++) {
|
|
139
|
+
const registered = await registerCurrentWorkspace(url, cwd);
|
|
140
|
+
if (registered) {
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
if (i < REGISTER_RETRY_ATTEMPTS - 1) {
|
|
144
|
+
await sleep(REGISTER_RETRY_DELAY_MS);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
|
|
83
150
|
/**
|
|
84
151
|
* Handle `stop` command.
|
|
85
152
|
* - Sends SIGTERM and waits for exit (with SIGKILL fallback), removes PID file.
|