bdtui 0.3.0 → 0.3.2

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 CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.3.2] - 2026-05-24
6
+
7
+ ### Changed
8
+
9
+ - Detail-pane dependency rows redesigned: status-colored icons (○ open, ◐ in_progress, ● blocked, ✓ closed, ❄ deferred) with the whole row tinted by effective status. Open beads whose blockers aren't all closed now render as blocked, mirroring how `bd blocked` derives the Blocked tab.
10
+
11
+ ## [0.3.1] - 2026-05-16
12
+
13
+ ### Added
14
+
15
+ - Auto-start Dolt on launch: `bdtui` runs `bd dolt test` and falls back to `bd dolt start` if the project's Dolt server is down, so the TUI no longer opens against a dead backend. Skipped for `-v`/`--version`.
16
+
5
17
  ## [0.3.0] - 2026-05-15
6
18
 
7
19
  ### Added
package/bin/bdtui.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import path from 'path';
3
+ import { spawnSync } from 'child_process';
3
4
  import { createRequire } from 'module';
4
5
 
5
6
  const require = createRequire(import.meta.url);
@@ -15,5 +16,21 @@ if (arg === '-v' || arg === '--version') {
15
16
  const cwd = arg ? path.resolve(arg) : process.cwd();
16
17
  process.chdir(cwd);
17
18
 
19
+ ensureDoltRunning(cwd);
20
+
18
21
  const { run } = await import('../src/app.js');
19
22
  await run(cwd);
23
+
24
+ function ensureDoltRunning(cwd) {
25
+ const test = spawnSync('bd', ['dolt', 'test'], { cwd, stdio: 'ignore' });
26
+ if (test.status === 0) return;
27
+ // `bd` missing or otherwise unusable — let the TUI surface the real error.
28
+ if (test.error && test.error.code === 'ENOENT') return;
29
+
30
+ process.stderr.write("Dolt server not running — starting via 'bd dolt start'…\n");
31
+ const start = spawnSync('bd', ['dolt', 'start'], { cwd, stdio: 'inherit' });
32
+ if (start.status !== 0) {
33
+ process.stderr.write("Failed to start Dolt server. Run 'bd dolt start' manually.\n");
34
+ process.exit(start.status ?? 1);
35
+ }
36
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bdtui",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Terminal UI for the bd beads issue tracker",
5
5
  "type": "module",
6
6
  "bin": {
package/src/state.js CHANGED
@@ -9,6 +9,7 @@ export const state = {
9
9
  filter: 'ready',
10
10
  typeFilter: 'all', // 'all' | 'epic' | 'task'
11
11
  epicFilter: null, // null or epic bead id — when set, list is scoped to that epic + its children
12
+ blockedIds: new Set(), // ids that appear in `bd blocked` — derived state, not raw status
12
13
  cwd: process.cwd(),
13
14
  };
14
15
 
@@ -76,7 +77,11 @@ function buildTreeOrder(beads, parentOf) {
76
77
  }
77
78
 
78
79
  export async function loadList() {
79
- const beads = await bdList(state.filter, state.cwd);
80
+ const [beads, blocked] = await Promise.all([
81
+ bdList(state.filter, state.cwd),
82
+ bdList('blocked', state.cwd).catch(() => []),
83
+ ]);
84
+ state.blockedIds = new Set((blocked || []).map((b) => b.id));
80
85
  const byId = new Map(beads.map((b) => [b.id, b]));
81
86
 
82
87
  // Build parent-child map from real bd deps (dependency_type === 'parent-child').
@@ -5,6 +5,22 @@ const STATUS_COLOR = {
5
5
  open: 'white', in_progress: 'cyan', blocked: 'red',
6
6
  deferred: 'yellow', closed: 'gray',
7
7
  };
8
+
9
+ // Dep-row palette: tuned for at-a-glance scanning in the dependency list.
10
+ // open / ready → green (go)
11
+ // in_progress → yellow (active)
12
+ // blocked → red (stop)
13
+ // deferred → gray (parked)
14
+ // closed → gray (done, recede)
15
+ const DEP_STATUS_COLOR = {
16
+ open: 'green', in_progress: 'yellow', blocked: 'red',
17
+ deferred: 'gray', closed: 'gray',
18
+ };
19
+
20
+ const STATUS_ICON = {
21
+ open: '○', in_progress: '◐', blocked: '●',
22
+ closed: '✓', deferred: '❄',
23
+ };
8
24
  const PRIORITY_COLOR = ['red', 'yellow', 'green', 'cyan', 'gray'];
9
25
  const PRIORITY_LABEL = ['Critical', 'High', 'Normal', 'Low', 'Trivial'];
10
26
 
@@ -30,11 +46,26 @@ function sectionHeader(title) {
30
46
  return `\n${bold(t('cyan', title))}\n${t('gray', '─'.repeat(44))}`;
31
47
  }
32
48
 
33
- function depLine(dep, arrow, color) {
34
- const id = esc(dep.id || String(dep));
35
- const type = esc(dep.dep_type || dep.type || 'blocks');
36
- const title = dep.title ? ` ${t('gray', '— ' + esc(dep.title))}` : '';
37
- return ` ${t(color, arrow)} {bold}${id}{/bold} ${t('gray', `[${type}]`)}${title}`;
49
+ function depLine(dep) {
50
+ const rawId = esc(dep.id || String(dep));
51
+ const rawTitle = dep.title ? esc(dep.title) : '';
52
+ const issueType = dep.issue_type ? esc(dep.issue_type) : null;
53
+
54
+ // Effective status mirrors how `bd blocked` derives the Blocked tab:
55
+ // an open bead whose blockers aren't all closed reads as "blocked",
56
+ // not "open". `bd dep list` only returns the raw stored status.
57
+ const effectiveStatus = (dep.status === 'open' && state.blockedIds.has(dep.id))
58
+ ? 'blocked'
59
+ : dep.status;
60
+
61
+ const statusColor = DEP_STATUS_COLOR[effectiveStatus] || 'white';
62
+ const icon = STATUS_ICON[effectiveStatus] || ' ';
63
+ const idText = `{bold}${rawId}{/bold}`;
64
+ const typeTag = issueType ? ` [${issueType}]` : '';
65
+ const titleText = rawTitle ? ` — ${rawTitle}` : '';
66
+
67
+ const row = `${icon} ${idText}${typeTag}${titleText}`;
68
+ return ` {${statusColor}-fg}${row}{/${statusColor}-fg}`;
38
69
  }
39
70
 
40
71
  // Apply inline markdown styling to already-escaped text. Order matters:
@@ -153,11 +184,11 @@ export function renderDetail(box) {
153
184
  // ── Dependencies ───────────────────────────────────────────────────────────
154
185
  if (b.depsDown?.length > 0) {
155
186
  lines.push(sectionHeader(`Depends on (${b.depsDown.length})`));
156
- for (const d of b.depsDown) lines.push(depLine(d, '→', 'yellow'));
187
+ for (const d of b.depsDown) lines.push(depLine(d));
157
188
  }
158
189
  if (b.depsUp?.length > 0) {
159
190
  lines.push(sectionHeader(`Needed by (${b.depsUp.length})`));
160
- for (const d of b.depsUp) lines.push(depLine(d, '←', 'green'));
191
+ for (const d of b.depsUp) lines.push(depLine(d));
161
192
  }
162
193
 
163
194
  // ── Description ────────────────────────────────────────────────────────────