beads-ui 0.3.0 → 0.4.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 +26 -0
- package/README.md +15 -6
- package/app/main.bundle.js +617 -0
- package/app/main.bundle.js.map +7 -0
- package/bin/bdui.js +2 -1
- package/package.json +27 -16
- package/server/app.js +39 -35
- package/server/bd.js +6 -2
- package/server/cli/commands.js +12 -8
- package/server/cli/daemon.js +20 -5
- package/server/cli/index.js +19 -31
- package/server/cli/open.js +3 -0
- package/server/cli/usage.js +4 -2
- package/server/config.js +3 -2
- package/server/db.js +9 -6
- package/server/index.js +10 -4
- package/server/list-adapters.js +9 -3
- package/server/logging.js +23 -0
- package/server/subscriptions.js +12 -0
- package/server/validators.js +2 -0
- package/server/watcher.js +10 -5
- package/server/ws.js +31 -10
- package/app/data/list-selectors.js +0 -98
- package/app/data/providers.js +0 -76
- package/app/data/sort.js +0 -45
- package/app/data/subscription-issue-store.js +0 -161
- package/app/data/subscription-issue-stores.js +0 -102
- package/app/data/subscriptions-store.js +0 -219
- package/app/main.js +0 -702
- package/app/protocol.js +0 -196
- package/app/protocol.md +0 -66
- package/app/router.js +0 -114
- package/app/state.js +0 -103
- package/app/utils/issue-id-renderer.js +0 -71
- package/app/utils/issue-id.js +0 -10
- package/app/utils/issue-type.js +0 -27
- package/app/utils/issue-url.js +0 -9
- package/app/utils/markdown.js +0 -22
- package/app/utils/priority-badge.js +0 -47
- package/app/utils/priority.js +0 -1
- package/app/utils/status-badge.js +0 -32
- package/app/utils/status.js +0 -23
- package/app/utils/toast.js +0 -34
- package/app/utils/type-badge.js +0 -33
- package/app/views/board.js +0 -535
- package/app/views/detail.js +0 -1249
- package/app/views/epics.js +0 -280
- package/app/views/issue-dialog.js +0 -163
- package/app/views/issue-row.js +0 -190
- package/app/views/list.js +0 -464
- package/app/views/nav.js +0 -67
- package/app/views/new-issue-dialog.js +0 -345
- package/app/ws.js +0 -279
- package/docs/adr/001-push-only-lists.md +0 -134
- package/docs/adr/002-per-subscription-stores-and-full-issue-push.md +0 -200
- package/docs/architecture.md +0 -194
- package/docs/data-exchange-subscription-plan.md +0 -198
- package/docs/db-watching.md +0 -30
- package/docs/migration-v2.md +0 -54
- package/docs/protocol/issues-push-v2.md +0 -179
- package/docs/subscription-issue-store.md +0 -112
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
# ADR 001 — Push‑Only Lists (v2)
|
|
2
|
-
|
|
3
|
-
```
|
|
4
|
-
Date: 2025-10-26
|
|
5
|
-
Status: Accepted (data‑flow details superseded by ADR 002)
|
|
6
|
-
Owner: agent
|
|
7
|
-
```
|
|
8
|
-
|
|
9
|
-
## Context
|
|
10
|
-
|
|
11
|
-
The UI currently mixes push updates with read RPCs like `list-issues` and
|
|
12
|
-
`epic-status`. This ADR establishes the push‑only direction for list data and
|
|
13
|
-
removing read RPCs in list views. It predated ADR 002 which later simplified the
|
|
14
|
-
data flow further (per‑subscription stores + full‑issue payloads).
|
|
15
|
-
|
|
16
|
-
- Push streams provide everything lists need to render. See
|
|
17
|
-
`docs/protocol/issues-push-v2.md` and ADR 002. Earlier iterations used a
|
|
18
|
-
central `issues` entity cache plus `list-delta` membership; this has been
|
|
19
|
-
replaced by per‑subscription stores receiving full issue payloads.
|
|
20
|
-
|
|
21
|
-
We want every list‑shaped view (Issues, Board, Epics → children) to render
|
|
22
|
-
exclusively from local push data. Reads remain only for mutations that return a
|
|
23
|
-
single updated entity (e.g. detail view refresh).
|
|
24
|
-
|
|
25
|
-
Related docs:
|
|
26
|
-
|
|
27
|
-
- Protocol: `docs/protocol/issues-push-v2.md`
|
|
28
|
-
- Server plan: `docs/data-exchange-subscription-plan.md`
|
|
29
|
-
|
|
30
|
-
## Decision
|
|
31
|
-
|
|
32
|
-
- One active subscription per visible list. Examples (client ids):
|
|
33
|
-
- Issues tab: `tab:issues` with spec from filters via `computeIssuesSpec()`
|
|
34
|
-
- Board: `tab:board:ready|in-progress|closed|blocked`
|
|
35
|
-
- Epics list: `tab:epics` (for epic entities); children subscribe on expand as
|
|
36
|
-
`detail:{id}` with `{ type: 'issue-detail', params: { id } }`
|
|
37
|
-
- Rendering reads from two local stores only:
|
|
38
|
-
- `per‑subscription stores`: one store per active client subscription id.
|
|
39
|
-
Stores receive versioned `snapshot`/`upsert`/`delete` push envelopes with
|
|
40
|
-
full issue payloads and expose deterministic, sorted snapshots for the
|
|
41
|
-
owning view.
|
|
42
|
-
- `subscriptions`: manages subscription lifecycle and keys. Rendering reads
|
|
43
|
-
from per‑subscription stores, not from membership ids.
|
|
44
|
-
- Introduce a small selectors utility to apply view‑specific sort rules on store
|
|
45
|
-
snapshots (no composition from a central cache).
|
|
46
|
-
- Remove read RPCs used for lists: `list-issues`, `epic-status`. Keep mutation
|
|
47
|
-
RPCs and `show-issue` until detail view also reads from push cache.
|
|
48
|
-
- Tests drive views with push envelopes and `list-delta`; no RPC stubs for
|
|
49
|
-
reads.
|
|
50
|
-
|
|
51
|
-
## API Shape (Client)
|
|
52
|
-
|
|
53
|
-
Subscriptions store (already implemented):
|
|
54
|
-
|
|
55
|
-
```js
|
|
56
|
-
// app/data/subscriptions-store.js
|
|
57
|
-
createSubscriptionStore(send) -> {
|
|
58
|
-
wireEvents(on), subscribeList(client_id, spec) -> unsubscribe,
|
|
59
|
-
selectors: { getIds(client_id), has(client_id), count(client_id) }
|
|
60
|
-
}
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
Selectors utility (implemented):
|
|
64
|
-
|
|
65
|
-
```js
|
|
66
|
-
// app/data/list-selectors.js
|
|
67
|
-
/** Compose from per‑subscription store snapshots and apply stable sort. */
|
|
68
|
-
export function createListSelectors(issueStores) {
|
|
69
|
-
return {
|
|
70
|
-
selectIssuesFor(client_id) {},
|
|
71
|
-
selectBoardColumn(client_id, mode) {},
|
|
72
|
-
selectEpicChildren(epic_id) {},
|
|
73
|
-
subscribe(fn) {}
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
Sorting rules:
|
|
79
|
-
|
|
80
|
-
- Issues list: priority asc (0..4), then `created_at` desc, then id asc.
|
|
81
|
-
- Board columns: preserve existing view rules (ready → priority asc, then
|
|
82
|
-
`updated_at` desc; in‑progress → `updated_at` desc; closed → `closed_at`
|
|
83
|
-
desc).
|
|
84
|
-
- Epics children: same as Issues list unless view specifies otherwise.
|
|
85
|
-
|
|
86
|
-
## Consequences
|
|
87
|
-
|
|
88
|
-
Pros:
|
|
89
|
-
|
|
90
|
-
- Consistent, snappy UI with minimal fetch logic; views are pure derives.
|
|
91
|
-
- Server can batch and coalesce; client renders at most once per envelope.
|
|
92
|
-
- Clear separation: mutations via RPC, reads via push caches.
|
|
93
|
-
|
|
94
|
-
Cons / Risks:
|
|
95
|
-
|
|
96
|
-
- Initial implementation work in views and tests.
|
|
97
|
-
- Need disciplined subscription lifecycle on route/tab changes.
|
|
98
|
-
- Requires follow‑up to migrate detail view fully to the push cache.
|
|
99
|
-
|
|
100
|
-
## Migration Checklist
|
|
101
|
-
|
|
102
|
-
Views
|
|
103
|
-
|
|
104
|
-
- [x] Issues view renders from per‑subscription stores; no `list-issues`.
|
|
105
|
-
- [x] Board renders from per‑subscription stores; no `get*` list reads.
|
|
106
|
-
- [x] Epics list/children render from per‑subscription stores; children use
|
|
107
|
-
`issue-detail` for the epic id; children come from `dependents`.
|
|
108
|
-
|
|
109
|
-
Client Data Layer
|
|
110
|
-
|
|
111
|
-
- [x] Add `app/data/list-selectors.js` with helpers listed above (UI-156).
|
|
112
|
-
- [x] Remove list read functions from `app/data/providers.js` (UI-159).
|
|
113
|
-
- [ ] Keep `getIssue` and all mutation helpers until detail view push migration
|
|
114
|
-
happens (follow‑up).
|
|
115
|
-
|
|
116
|
-
Tests
|
|
117
|
-
|
|
118
|
-
- [x] Update list/board/epics tests to use per‑subscription push envelopes
|
|
119
|
-
(UI-158).
|
|
120
|
-
- [x] Remove RPC read stubs from tests.
|
|
121
|
-
|
|
122
|
-
Docs
|
|
123
|
-
|
|
124
|
-
- [x] This ADR committed (UI-152).
|
|
125
|
-
- [x] Update protocol and architecture docs for push‑only model (UI-160).
|
|
126
|
-
|
|
127
|
-
## Notes
|
|
128
|
-
|
|
129
|
-
- Client ids used in this repo today:
|
|
130
|
-
- `tab:issues` for the Issues view
|
|
131
|
-
- `tab:board:ready|in-progress|closed|blocked` for Board columns
|
|
132
|
-
- `tab:epics` for the Epics tab; `epic:${id}` for expanded children
|
|
133
|
-
- See `app/main.js` for current subscription wiring, filter → spec mapping, and
|
|
134
|
-
per‑subscription push routing.
|
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
# ADR 002 — Per‑Subscription Stores and Full‑Issue Push (Breaking)
|
|
2
|
-
|
|
3
|
-
```
|
|
4
|
-
Date: 2025-10-26
|
|
5
|
-
Status: Proposed (ready for owner approval)
|
|
6
|
-
Owner: agent
|
|
7
|
-
```
|
|
8
|
-
|
|
9
|
-
## Context
|
|
10
|
-
|
|
11
|
-
The UI currently maintains a central `issues` cache and a separate list
|
|
12
|
-
membership model. Push events update the central cache and lists fan out from
|
|
13
|
-
it. This split increases cognitive load (two caches, two sets of selectors),
|
|
14
|
-
creates subtle ordering/dedup bugs, and complicates tests and routing.
|
|
15
|
-
|
|
16
|
-
We want a simpler, local model per visible list: one subscription → one store →
|
|
17
|
-
one push update stream → one rendered list. Push events must contain complete
|
|
18
|
-
issue objects for correctness and to avoid fan‑out to a central cache.
|
|
19
|
-
|
|
20
|
-
## Decision
|
|
21
|
-
|
|
22
|
-
- Adopt a per‑subscription issue store (`SubscriptionIssueStore`) keyed by the
|
|
23
|
-
client’s subscription id.
|
|
24
|
-
- Server sends per‑subscription full‑issue payloads only; no id‑only deltas.
|
|
25
|
-
Messages are serialized per subscription and revisioned.
|
|
26
|
-
- Lists render exclusively from their own store snapshots; the central issue
|
|
27
|
-
cache is removed from the list render path.
|
|
28
|
-
- Breaking change: remove legacy id‑only list deltas and any compatibility
|
|
29
|
-
paths/flags. No phased rollout and no telemetry collection.
|
|
30
|
-
|
|
31
|
-
## Protocol (Server → Client)
|
|
32
|
-
|
|
33
|
-
Message shapes are defined in `types/subscriptions.ts` and documented in
|
|
34
|
-
`docs/data-exchange-subscription-plan.md`.
|
|
35
|
-
|
|
36
|
-
All envelopes include a version tag and a per‑subscription, strictly monotonic
|
|
37
|
-
`revision` used for ordering and replay protection (see UI‑144).
|
|
38
|
-
|
|
39
|
-
- `subscribed` `{ id: string }`
|
|
40
|
-
- `snapshot` `{ id: string, revision: number, issues: Issue[] }`
|
|
41
|
-
- `upsert` `{ id: string, revision: number, issue: Issue }`
|
|
42
|
-
- `delete` `{ id: string, revision: number, issue_id: string }`
|
|
43
|
-
- `error` `{ id?: string, code: string, message: string, details?: object }`
|
|
44
|
-
|
|
45
|
-
Notes
|
|
46
|
-
|
|
47
|
-
- Per‑subscription ordering is guaranteed by the server and signaled via
|
|
48
|
-
`revision`. Clients MUST apply envelopes in `revision` order and ignore any
|
|
49
|
-
envelope whose `revision` is ≤ the last applied.
|
|
50
|
-
- Clients MUST treat updates as idempotent and MAY additionally guard on an
|
|
51
|
-
`issue.updated_at` timestamp to ignore stale `upsert`s that race with local
|
|
52
|
-
state. Timestamps are advisory; `revision` is canonical for ordering.
|
|
53
|
-
- Initial state arrives as a `snapshot` with a complete list of issues for the
|
|
54
|
-
subscription key.
|
|
55
|
-
|
|
56
|
-
## Client Store API
|
|
57
|
-
|
|
58
|
-
The UI manages one store per active subscription. Minimal API surface:
|
|
59
|
-
|
|
60
|
-
```ts
|
|
61
|
-
// types only — see types/subscription-issue-store.ts
|
|
62
|
-
export interface SubscriptionIssueStore {
|
|
63
|
-
/** Client subscription id this store belongs to. */
|
|
64
|
-
readonly id: string;
|
|
65
|
-
|
|
66
|
-
/** Attach a listener that is called after each applied message. */
|
|
67
|
-
subscribe(listener: () => void): () => void;
|
|
68
|
-
|
|
69
|
-
/** Apply a push message: snapshot, upsert, or delete. */
|
|
70
|
-
applyPush(msg: SnapshotMsg | UpsertMsg | DeleteMsg): void;
|
|
71
|
-
|
|
72
|
-
/** Read-only, stable snapshot for rendering (deterministic sort). */
|
|
73
|
-
snapshot(): readonly Issue[];
|
|
74
|
-
|
|
75
|
-
/** Lookup helpers used by views/tests. */
|
|
76
|
-
size(): number;
|
|
77
|
-
getById(id: string): Issue | undefined;
|
|
78
|
-
|
|
79
|
-
/** Release references and listeners when the view unmounts. */
|
|
80
|
-
dispose(): void;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export type SnapshotMsg = {
|
|
84
|
-
type: 'snapshot';
|
|
85
|
-
id: string;
|
|
86
|
-
revision: number;
|
|
87
|
-
issues: Issue[];
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
export type UpsertMsg = {
|
|
91
|
-
type: 'upsert';
|
|
92
|
-
id: string;
|
|
93
|
-
revision: number;
|
|
94
|
-
issue: Issue;
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
export type DeleteMsg = {
|
|
98
|
-
type: 'delete';
|
|
99
|
-
id: string;
|
|
100
|
-
revision: number;
|
|
101
|
-
issue_id: string;
|
|
102
|
-
};
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
### Sorting and identity
|
|
106
|
-
|
|
107
|
-
- Stores maintain stable item identity across updates (same object ref for the
|
|
108
|
-
same `id` when only fields change) and expose a deterministic sort order
|
|
109
|
-
suitable for the owning view (e.g., Issues: priority asc, then `created_at`
|
|
110
|
-
desc, then id asc).
|
|
111
|
-
|
|
112
|
-
### Error handling and reconnect
|
|
113
|
-
|
|
114
|
-
- On disconnect/reconnect, the client creates a fresh store and re‑subscribes.
|
|
115
|
-
The server sends a fresh snapshot; no attempt is made to diff across sessions.
|
|
116
|
-
|
|
117
|
-
### Reconcile algorithm (pseudo‑code)
|
|
118
|
-
|
|
119
|
-
```
|
|
120
|
-
state: Map<string, Issue> = new Map()
|
|
121
|
-
lastRevision: number = 0
|
|
122
|
-
|
|
123
|
-
function applyPush(msg) {
|
|
124
|
-
if (msg.revision <= lastRevision) return // stale or duplicate
|
|
125
|
-
lastRevision = msg.revision
|
|
126
|
-
|
|
127
|
-
switch (msg.type) {
|
|
128
|
-
case 'snapshot':
|
|
129
|
-
state.clear()
|
|
130
|
-
for (const it of deterministicallySort(msg.issues)) {
|
|
131
|
-
state.set(it.id, it)
|
|
132
|
-
}
|
|
133
|
-
break
|
|
134
|
-
case 'upsert':
|
|
135
|
-
const existing = state.get(msg.issue.id)
|
|
136
|
-
if (!existing || existing.updated_at <= msg.issue.updated_at) {
|
|
137
|
-
state.set(msg.issue.id, msg.issue)
|
|
138
|
-
}
|
|
139
|
-
break
|
|
140
|
-
case 'delete':
|
|
141
|
-
state.delete(msg.issue_id)
|
|
142
|
-
break
|
|
143
|
-
}
|
|
144
|
-
notifyListeners()
|
|
145
|
-
}
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
The sort function must be deterministic and view‑specific (e.g., priority asc,
|
|
149
|
-
then `created_at` desc, then `id` asc). Stores keep object identity stable for
|
|
150
|
-
the same `id` whenever fields change.
|
|
151
|
-
|
|
152
|
-
## Migration
|
|
153
|
-
|
|
154
|
-
- Delete list render paths that read via the central issues cache.
|
|
155
|
-
- Introduce a factory `createSubscriptionIssueStore(id)` at view mount; wire the
|
|
156
|
-
push client to route `snapshot`/`upsert`/`delete` messages by `id` to the
|
|
157
|
-
corresponding store via `applyPush`.
|
|
158
|
-
- Update list components to render from `store.snapshot()` and subscribe to
|
|
159
|
-
re‑render on changes.
|
|
160
|
-
- Remove legacy central‑store fan‑out and dead selectors.
|
|
161
|
-
|
|
162
|
-
## Consequences
|
|
163
|
-
|
|
164
|
-
Pros
|
|
165
|
-
|
|
166
|
-
- One‑to‑one mapping of subscription → store → view simplifies reasoning and
|
|
167
|
-
testing.
|
|
168
|
-
- No cache fan‑out; updates apply once per subscription and render once.
|
|
169
|
-
- Clearer ownership boundaries; easier disposal on route/tab changes.
|
|
170
|
-
|
|
171
|
-
Cons / Risks
|
|
172
|
-
|
|
173
|
-
- Larger `updated` payloads vs id‑only membership deltas. Mitigated by
|
|
174
|
-
per‑subscription scoping and batching.
|
|
175
|
-
- Requires coordinated server/client cutover due to the breaking change.
|
|
176
|
-
|
|
177
|
-
## Alternatives Considered
|
|
178
|
-
|
|
179
|
-
- Keep the central cache and fan‑out membership to lists. Rejected: duplicates
|
|
180
|
-
ownership, increases complexity and test surface, caused known ordering bugs.
|
|
181
|
-
- Maintain id‑only list deltas with separate issue fetches. Rejected: adds
|
|
182
|
-
round‑trips and cross‑store coordination; does not meet simplicity goal.
|
|
183
|
-
- Dual protocol/feature flag with gradual cutover. Rejected per epic scope: no
|
|
184
|
-
flags, no compatibility layer, and no telemetry collection.
|
|
185
|
-
|
|
186
|
-
## Related
|
|
187
|
-
|
|
188
|
-
- ADR 001 — Push‑Only Lists (v2): establishes push‑only direction and server
|
|
189
|
-
batching; this ADR replaces the central‑store + list‑membership split with a
|
|
190
|
-
per‑subscription store model.
|
|
191
|
-
- `docs/protocol/issues-push-v2.md` and
|
|
192
|
-
`docs/data-exchange-subscription-plan.md` for normative protocol and server
|
|
193
|
-
behavior.
|
|
194
|
-
|
|
195
|
-
## Status & Follow‑ups
|
|
196
|
-
|
|
197
|
-
- This ADR is a breaking change with no flags/compat/telemetry. It becomes
|
|
198
|
-
Accepted once UI‑166 is approved by frontend and backend owners and the
|
|
199
|
-
server/client work (UI‑167, UI‑168, UI‑169) lands.
|
|
200
|
-
- Cleanup (UI-174) removes the central store and delta fan‑out code.
|
package/docs/architecture.md
DELETED
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
# beads-ui Architecture (v2)
|
|
2
|
-
|
|
3
|
-
Note (2025-10-26)
|
|
4
|
-
|
|
5
|
-
- beads-ui has migrated to a push‑only protocol for lists and details. The
|
|
6
|
-
server no longer implements legacy read RPCs `list-issues` and `epic-status`.
|
|
7
|
-
For the normative protocol reference, see `docs/protocol/issues-push-v2.md`.
|
|
8
|
-
|
|
9
|
-
This document describes the high‑level architecture of beads‑ui and the v2
|
|
10
|
-
push‑only data flow used between the browser SPA and the local Node.js server.
|
|
11
|
-
|
|
12
|
-
## Overview
|
|
13
|
-
|
|
14
|
-
- Local‑first single‑page app served by a localhost HTTP server
|
|
15
|
-
- WebSocket for data (request/response + server push events)
|
|
16
|
-
- Server bridges UI intents to the `bd` CLI and watches the active beads
|
|
17
|
-
database for changes
|
|
18
|
-
|
|
19
|
-
```
|
|
20
|
-
+--------------+ ws://127.0.0.1:PORT/ws +--------------------+
|
|
21
|
-
| Browser SPA | <--------------------------------------> | HTTP + WS Server |
|
|
22
|
-
| (ESM, DOM) | requests (JSON) / replies + events | (Node.js, ESM) |
|
|
23
|
-
+--------------+ +---------+----------+
|
|
24
|
-
^ |
|
|
25
|
-
| v
|
|
26
|
-
| +----+-----+
|
|
27
|
-
| | bd |
|
|
28
|
-
| | (CLI) |
|
|
29
|
-
| +----+-----+
|
|
30
|
-
| |
|
|
31
|
-
| watches v
|
|
32
|
-
|------------------------------------ changes --------> [ SQLite DB ]
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## Components and Responsibilities
|
|
36
|
-
|
|
37
|
-
- UI (app/)
|
|
38
|
-
- `app/main.js`: bootstraps shell, creates store/router, wires WS client,
|
|
39
|
-
refreshes on push
|
|
40
|
-
- Views: `app/views/list.js`, `app/views/detail.js` render issues and allow
|
|
41
|
-
edits
|
|
42
|
-
- Transport: `app/ws.js` persistent client with reconnect, correlation, and
|
|
43
|
-
event dispatcher
|
|
44
|
-
- Protocol: `app/protocol.js` shared message shapes, version, helpers, and
|
|
45
|
-
type guards
|
|
46
|
-
|
|
47
|
-
- Server (server/)
|
|
48
|
-
- Web: `server/app.js` (Express app), `server/index.js` (startup and wiring)
|
|
49
|
-
- WebSocket: `server/ws.js` (attach server, parse, validate, dispatch
|
|
50
|
-
handlers, broadcast events)
|
|
51
|
-
- bd bridge: `server/bd.js` (spawn `bd`, inject `--db` consistently, JSON
|
|
52
|
-
helpers)
|
|
53
|
-
- DB resolution/watch: `server/db.js` (resolve active DB path),
|
|
54
|
-
`server/watcher.js` (schedule list refresh)
|
|
55
|
-
- Config: `server/config.js` (bind to `127.0.0.1`, default port 3000)
|
|
56
|
-
|
|
57
|
-
## Data Flow
|
|
58
|
-
|
|
59
|
-
1. User action in the UI creates a request `{ id, type, payload }` via
|
|
60
|
-
`app/ws.js`.
|
|
61
|
-
2. Server validates and maps the request to a `bd` command (no shell; args array
|
|
62
|
-
only).
|
|
63
|
-
3. Server replies with `{ id, ok, type, payload }` or `{ id, ok:false, error }`.
|
|
64
|
-
4. Independent of requests, the DB watcher schedules a refresh for active list
|
|
65
|
-
subscriptions; clients receive `snapshot`/`upsert`/`delete` envelopes.
|
|
66
|
-
|
|
67
|
-
## Protocol (v2.0.0)
|
|
68
|
-
|
|
69
|
-
Envelope shapes (see `app/protocol.md` and `docs/protocol/issues-push-v2.md`):
|
|
70
|
-
|
|
71
|
-
- Request: `{ id: string, type: string, payload?: any }`
|
|
72
|
-
- Reply:
|
|
73
|
-
`{ id: string, ok: boolean, type: string, payload?: any, error?: { code: string, message: string, details?: any } }`
|
|
74
|
-
|
|
75
|
-
Push‑only subscriptions for lists and details:
|
|
76
|
-
|
|
77
|
-
- Client subscribes via `subscribe-list` with `{ id, type, params? }`.
|
|
78
|
-
- Server acks the subscription and immediately publishes a `snapshot` envelope
|
|
79
|
-
with the full list for that subscription id followed by `upsert`/`delete`
|
|
80
|
-
envelopes as data changes. Clients render from local per‑subscription stores.
|
|
81
|
-
|
|
82
|
-
Common message types (mutations only; list reads removed):
|
|
83
|
-
|
|
84
|
-
- `update-status` payload:
|
|
85
|
-
`{ id: string, status: 'open'|'in_progress'|'closed' }`
|
|
86
|
-
- `edit-text` payload:
|
|
87
|
-
`{ id: string, field: 'title'|'description'|'acceptance'|'notes'|'design', value: string }`
|
|
88
|
-
- `update-priority` payload: `{ id: string, priority: 0|1|2|3|4 }`
|
|
89
|
-
- `dep-add` payload: `{ a: string, b: string, view_id?: string }`
|
|
90
|
-
- `dep-remove` payload: `{ a: string, b: string, view_id?: string }`
|
|
91
|
-
- `create-issue` payload:
|
|
92
|
-
`{ title: string, type?: 'bug'|'feature'|'task'|'epic'|'chore', priority?: 0|1|2|3|4, description?: string }`
|
|
93
|
-
|
|
94
|
-
Removed in v2:
|
|
95
|
-
|
|
96
|
-
- `list-issues`, `epic-status`, `subscribe-updates` and the legacy
|
|
97
|
-
`issues-changed` event.
|
|
98
|
-
|
|
99
|
-
## UI → bd Command Mapping
|
|
100
|
-
|
|
101
|
-
- Lists and details: push‑only via `subscribe-list` (no list reads)
|
|
102
|
-
- Update status: `bd update <id> --status <open|in_progress|closed>`
|
|
103
|
-
- Update priority: `bd update <id> --priority <0..4>`
|
|
104
|
-
- Edit title: `bd update <id> --title <text>`
|
|
105
|
-
- Edit description: `bd update <id> --description <text>`
|
|
106
|
-
- Edit acceptance: `bd update <id> --acceptance-criteria <text>`
|
|
107
|
-
- Link dependency: `bd dep add <a> <b>` (a depends on b)
|
|
108
|
-
- Unlink dependency: `bd dep remove <a> <b>`
|
|
109
|
-
- Create issue: `bd create "title" -t <type> -p <prio> -d "desc"`
|
|
110
|
-
|
|
111
|
-
Rationale
|
|
112
|
-
|
|
113
|
-
- Use `--json` for read commands to ensure typed payloads.
|
|
114
|
-
- Avoid shell invocation; pass args array to `spawn` to prevent injection.
|
|
115
|
-
- Always inject a resolved `--db <path>` so watcher and CLI operate on the same
|
|
116
|
-
database.
|
|
117
|
-
|
|
118
|
-
## Issue Data Model (wire)
|
|
119
|
-
|
|
120
|
-
```ts
|
|
121
|
-
interface Issue {
|
|
122
|
-
id: string;
|
|
123
|
-
title?: string;
|
|
124
|
-
description?: string;
|
|
125
|
-
acceptance?: string;
|
|
126
|
-
status?: 'open' | 'in_progress' | 'closed';
|
|
127
|
-
priority?: 0 | 1 | 2 | 3 | 4;
|
|
128
|
-
dependencies?: Array<{
|
|
129
|
-
id: string;
|
|
130
|
-
title?: string;
|
|
131
|
-
status?: string;
|
|
132
|
-
priority?: number;
|
|
133
|
-
issue_type?: string;
|
|
134
|
-
}>;
|
|
135
|
-
dependents?: Array<{
|
|
136
|
-
id: string;
|
|
137
|
-
title?: string;
|
|
138
|
-
status?: string;
|
|
139
|
-
priority?: number;
|
|
140
|
-
issue_type?: string;
|
|
141
|
-
}>;
|
|
142
|
-
}
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
Notes
|
|
146
|
-
|
|
147
|
-
- Fields are optional to allow partial views and forward compatibility.
|
|
148
|
-
- Additional properties may appear; clients should ignore unknown keys.
|
|
149
|
-
|
|
150
|
-
## Error Model and Versioning
|
|
151
|
-
|
|
152
|
-
- Error object: `{ code: string, message: string, details?: any }`
|
|
153
|
-
- Common codes: `bad_request`, `not_found`, `bd_error`, `unknown_type`,
|
|
154
|
-
`bad_json`
|
|
155
|
-
|
|
156
|
-
## Security and Local Boundaries
|
|
157
|
-
|
|
158
|
-
- Server binds to `127.0.0.1` by default to keep traffic local.
|
|
159
|
-
- Basic input validation at the WS boundary; unknown or malformed messages
|
|
160
|
-
produce structured errors.
|
|
161
|
-
- No shell usage; `spawn` with args only; environment opt‑in via `BD_BIN`.
|
|
162
|
-
|
|
163
|
-
## Watcher Design
|
|
164
|
-
|
|
165
|
-
- The server resolves the active beads SQLite DB path (see
|
|
166
|
-
`docs/db-watching.md`).
|
|
167
|
-
- File watcher schedules list refresh; the server publishes subscription
|
|
168
|
-
envelopes. UI re-renders from local per-subscription stores.
|
|
169
|
-
|
|
170
|
-
## Risks & Open Questions
|
|
171
|
-
|
|
172
|
-
- Create flow not implemented in server handlers
|
|
173
|
-
- Owner: Server
|
|
174
|
-
- Next: Add `create-issue` handler and tests; wire minimal UI affordance
|
|
175
|
-
- Ready list support missing end‑to‑end
|
|
176
|
-
- Owner: Server + UI
|
|
177
|
-
- Next: Add `list-ready` handler and a list filter in UI
|
|
178
|
-
- Backpressure when many updates arrive
|
|
179
|
-
- Owner: Server
|
|
180
|
-
- Next: Coalesce broadcast events; consider debounce window
|
|
181
|
-
- Large databases and payload size
|
|
182
|
-
- Owner: UI
|
|
183
|
-
- Next: Add incremental refresh (fetch issue by id on hints)
|
|
184
|
-
- Cross‑platform DB path resolution nuances
|
|
185
|
-
- Owner: Server
|
|
186
|
-
- Next: Expand tests for Windows/macOS/Linux; document overrides
|
|
187
|
-
- Acceptance text editing
|
|
188
|
-
- Owner: UI + Server
|
|
189
|
-
- Status: Implemented via `edit-text` + `--acceptance-criteria`
|
|
190
|
-
|
|
191
|
-
---
|
|
192
|
-
|
|
193
|
-
For the normative protocol reference and unit tests, see `app/protocol.md` and
|
|
194
|
-
`app/protocol.test.js`.
|