@nanobpm/nano-workforce 0.48.2 → 0.49.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/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ # [0.49.0](https://github.com/nanobpm/nano-workforce/compare/v0.48.2...v0.49.0) (2026-08-12)
2
+
3
+
4
+ ### Features
5
+
6
+ * **ui:** per-epic operator workspace with live wave progress ([#138](https://github.com/nanobpm/nano-workforce/issues/138)) ([127888d](https://github.com/nanobpm/nano-workforce/commit/127888d5d2b66e1e9d1c85d015cbd65f3696216f)), closes [#137](https://github.com/nanobpm/nano-workforce/issues/137) [nanobpm/nano-workforce#137](https://github.com/nanobpm/nano-workforce/issues/137) [nanobpm/nano-ide#213](https://github.com/nanobpm/nano-ide/issues/213) [nanobpm/nano-ide#214](https://github.com/nanobpm/nano-ide/issues/214)
7
+
1
8
  ## [0.48.2](https://github.com/nanobpm/nano-workforce/compare/v0.48.1...v0.48.2) (2026-08-12)
2
9
 
3
10
 
package/app/plan.ts CHANGED
@@ -55,6 +55,18 @@ export interface Plan {
55
55
  // Wave-merge barrier (007_wave_gate.sql): the wave index whose PRs the plan is currently
56
56
  // waiting to see MERGED before dispatching the next wave, or null when not parked at the barrier.
57
57
  gate_wave: number | null;
58
+ // Operator-visibility wave progress (022_plan_wave_progress.sql, #137): denormalised so the
59
+ // epics-index can show wave X/N at a glance. `wave_count` is the total waves (N); `current_wave`
60
+ // is the 0-based index of the wave the fleet is actively implementing (advanced by select-wave,
61
+ // pinned to wave_count-1 on completion). Display-only — never gates control flow. NULL until the
62
+ // plan is dispatched with tasks.
63
+ wave_count: number | null;
64
+ current_wave: number | null;
65
+ // Pre-formatted 1-based "X/N" progress string for the epics-index at-a-glance column
66
+ // (022_plan_wave_progress.sql, #137). The dataGrid has no per-cell templating (nano-ide#214),
67
+ // so this is projected alongside the numeric columns by the same worker writes. NULL until
68
+ // dispatched with tasks.
69
+ wave_label: string | null;
58
70
  // Per-plan capability token for the coordination blackboard (009_plan_blackboard.sql, #51).
59
71
  // Minted at plan start; baked into the blackboard URL handed to implementer agents. NULL for
60
72
  // plans created before the blackboard shipped.
@@ -0,0 +1,24 @@
1
+ -- Operator visibility: at-a-glance wave X/N on the Epic Coordination view (issue #137).
2
+ --
3
+ -- `plan_tasks.wave` (005_plan_deps.sql) holds each task's 0-based wave, but the `plans`
4
+ -- row itself carries no progress summary — so the epics-index grid had no way to show how
5
+ -- far through its waves an epic is without joining/aggregating per row. Urban's datasource
6
+ -- cannot read a SQL VIEW (gateway.ts schema() whitelists only type='table'), so following
7
+ -- the codebase's existing convention (denormalised, worker-maintained pointers on `plans`
8
+ -- like open_task_id / open_plan_round / gate_wave) we project two flat columns:
9
+ --
10
+ -- • wave_count — total number of waves (N). Written by `record-plan` alongside
11
+ -- task_count, from the `waveCount` it already computes (app/waves.ts).
12
+ -- • current_wave — the 0-based index of the wave the fleet is ACTIVELY implementing
13
+ -- (live). Initialised to 0 by `record-plan` at dispatch, advanced by
14
+ -- `select-wave` at the start of each wave, and pinned to wave_count-1 on
15
+ -- completion so a finished epic reads N/N. Display-only: it must never
16
+ -- gate control flow (that stays driven by the currentWave/waveCount/
17
+ -- gate_wave process state). NULL until the plan is dispatched with tasks.
18
+ -- • wave_label — the human "X/N" progress string (1-based), e.g. "2/3". The dataGrid
19
+ -- renders raw field values with no per-cell templating, so this pre-formats
20
+ -- (current_wave+1)/wave_count for the epics-index at-a-glance column. Kept in
21
+ -- lockstep with the two numeric columns by the same worker writes.
22
+ ALTER TABLE plans ADD COLUMN wave_count INTEGER;
23
+ ALTER TABLE plans ADD COLUMN current_wave INTEGER;
24
+ ALTER TABLE plans ADD COLUMN wave_label TEXT;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nanobpm/nano-workforce",
3
- "version": "0.48.2",
3
+ "version": "0.49.0",
4
4
  "description": "Nano Workforce — an Agent Graph Orchestration application for Agentic SDLC: durable BPMN processes that coordinate a graph of AI agents across the software delivery lifecycle.",
5
5
  "type": "module",
6
6
  "main": "main.ts",
@@ -46,7 +46,7 @@
46
46
  "lint:fix": "biome check --write app operations workers pages components scripts e2e main.ts"
47
47
  },
48
48
  "dependencies": {
49
- "@nanobpm/urban": "^0.43.0"
49
+ "@nanobpm/urban": "^0.44.0"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@biomejs/biome": "^2.4.11",
@@ -0,0 +1,314 @@
1
+ {
2
+ "schemaVersion": "1.0",
3
+ "title": "Epic",
4
+ "nodes": [
5
+ {
6
+ "type": "nav",
7
+ "id": "nav",
8
+ "props": {
9
+ "variant": "bar",
10
+ "sticky": true,
11
+ "title": "Nano Workforce",
12
+ "items": [
13
+ { "label": "Convergence", "page": "home" },
14
+ { "label": "Epics", "page": "epic" }
15
+ ]
16
+ }
17
+ },
18
+ {
19
+ "type": "text",
20
+ "id": "title",
21
+ "props": { "text": "Epic {{param}}", "variant": "heading" }
22
+ },
23
+ {
24
+ "type": "text",
25
+ "id": "subtitle",
26
+ "props": {
27
+ "text": "This epic's plan, review trace, wave state, conflict graph, coordination notes, and trial-merge results. Sections start collapsed and remember your choice.",
28
+ "variant": "sub"
29
+ }
30
+ },
31
+ {
32
+ "type": "dataGrid",
33
+ "id": "epic-plan",
34
+ "props": {
35
+ "title": "Plan",
36
+ "rowKey": "plan_key",
37
+ "refreshMs": 5000,
38
+ "data": {
39
+ "kind": "datasource",
40
+ "source": "app",
41
+ "table": "plans",
42
+ "orderBy": { "field": "updated_at", "dir": "desc" },
43
+ "filter": [{ "field": "plan_key", "eqParam": true }]
44
+ },
45
+ "columns": [
46
+ { "field": "plan_key", "header": "Issue", "linkField": "issue_url" },
47
+ { "field": "status", "header": "Status", "link": { "kind": "processExplorer", "keyField": "process_key" } },
48
+ { "field": "wave_label", "header": "Wave" },
49
+ { "field": "task_count", "header": "Tasks" },
50
+ { "field": "open_task_id", "header": "Open escalation" },
51
+ { "field": "open_plan_round", "header": "Plan escalation round" },
52
+ { "field": "updated_at", "header": "Updated" }
53
+ ],
54
+ "rowActions": [
55
+ {
56
+ "label": "Proceed with plan",
57
+ "confirm": "Dispatch the current unapproved plan as a human override?",
58
+ "showWhenField": "open_plan_escalation_id",
59
+ "action": {
60
+ "path": "/app/api/actions/message",
61
+ "body": {
62
+ "name": "plan-escalation-answered",
63
+ "correlationKey": "{{row.plan_key}}",
64
+ "variables": { "directive": "proceed", "note": "Proceed override from the epic page." }
65
+ }
66
+ }
67
+ }
68
+ ],
69
+ "detail": {
70
+ "linkField": "issue_url",
71
+ "fields": [
72
+ { "field": "repo", "label": "Repository" },
73
+ { "field": "issue_number", "label": "Issue number" },
74
+ { "field": "base_branch", "label": "Base branch (blank = repo default)" },
75
+ { "field": "outcome", "label": "Outcome" },
76
+ { "field": "open_plan_findings", "label": "Open plan-review findings" },
77
+ { "field": "open_task_question", "label": "Open escalation question" }
78
+ ],
79
+ "form": {
80
+ "showWhenField": "open_plan_escalation_id",
81
+ "title": "Answer the open plan-review escalation",
82
+ "promptField": "open_plan_findings",
83
+ "inputKey": "note",
84
+ "inputLabel": "Revision directive note for the planner",
85
+ "submitLabel": "Revise plan",
86
+ "action": {
87
+ "path": "/app/api/actions/message",
88
+ "body": {
89
+ "name": "plan-escalation-answered",
90
+ "correlationKey": "{{row.plan_key}}",
91
+ "variables": { "directive": "revise", "note": "{{form.note}}" }
92
+ }
93
+ }
94
+ }
95
+ }
96
+ }
97
+ },
98
+ {
99
+ "type": "dataGrid",
100
+ "id": "wave-state",
101
+ "props": {
102
+ "title": "Wave state",
103
+ "rowKey": "id",
104
+ "refreshMs": 5000,
105
+ "collapsible": true,
106
+ "defaultCollapsed": true,
107
+ "groupBy": "wave",
108
+ "groupLabel": "Wave",
109
+ "data": {
110
+ "kind": "datasource",
111
+ "source": "app",
112
+ "table": "plan_tasks",
113
+ "orderBy": { "field": "wave", "dir": "asc" },
114
+ "filter": [
115
+ { "field": "plan_key", "eqParam": true },
116
+ { "field": "status", "in": ["pending", "opened", "escalated", "waiting-for-lane", "blocked"] }
117
+ ]
118
+ },
119
+ "tabs": [
120
+ {
121
+ "label": "Active",
122
+ "filter": [
123
+ { "field": "plan_key", "eqParam": true },
124
+ { "field": "status", "in": ["pending", "opened", "escalated", "waiting-for-lane", "blocked"] }
125
+ ]
126
+ },
127
+ {
128
+ "label": "Skipped",
129
+ "filter": [
130
+ { "field": "plan_key", "eqParam": true },
131
+ { "field": "status", "in": ["skipped"] }
132
+ ]
133
+ },
134
+ {
135
+ "label": "All",
136
+ "filter": [{ "field": "plan_key", "eqParam": true }]
137
+ }
138
+ ],
139
+ "columns": [
140
+ { "field": "wave", "header": "Wave" },
141
+ { "field": "task_id", "header": "Task" },
142
+ { "field": "title", "header": "Title" },
143
+ { "field": "status", "header": "Status" },
144
+ { "field": "pr_key", "header": "PR" },
145
+ { "field": "summary", "header": "Summary" },
146
+ { "field": "updated_at", "header": "Updated" }
147
+ ],
148
+ "detail": {
149
+ "fields": [
150
+ { "field": "open_question", "label": "Open question" },
151
+ { "field": "answer", "label": "Answer" },
152
+ { "field": "draft_pr_key", "label": "Draft PR" },
153
+ { "field": "prompt", "label": "Prompt" }
154
+ ]
155
+ }
156
+ }
157
+ },
158
+ {
159
+ "type": "dataGrid",
160
+ "id": "plan-reviews",
161
+ "props": {
162
+ "title": "Plan review trace",
163
+ "refreshMs": 5000,
164
+ "collapsible": true,
165
+ "defaultCollapsed": true,
166
+ "data": {
167
+ "kind": "datasource",
168
+ "source": "app",
169
+ "table": "plan_reviews",
170
+ "orderBy": { "field": "round", "dir": "asc" },
171
+ "filter": [{ "field": "plan_key", "eqParam": true }]
172
+ },
173
+ "columns": [
174
+ { "field": "round", "header": "Round" },
175
+ { "field": "epoch", "header": "Epoch" },
176
+ { "field": "approved", "header": "Approved? (1/0)" },
177
+ { "field": "findings", "header": "Reviewer findings" },
178
+ { "field": "created_at", "header": "Recorded" }
179
+ ]
180
+ }
181
+ },
182
+ {
183
+ "type": "dataGrid",
184
+ "id": "plan-review-escalations",
185
+ "props": {
186
+ "title": "Plan-review escalations",
187
+ "rowKey": "id",
188
+ "refreshMs": 5000,
189
+ "collapsible": true,
190
+ "defaultCollapsed": true,
191
+ "data": {
192
+ "kind": "datasource",
193
+ "source": "app",
194
+ "table": "plan_review_escalations",
195
+ "orderBy": { "field": "id", "dir": "desc" },
196
+ "filter": [{ "field": "plan_key", "eqParam": true }]
197
+ },
198
+ "columns": [
199
+ { "field": "epoch", "header": "Epoch" },
200
+ { "field": "round", "header": "Round" },
201
+ { "field": "findings", "header": "Findings" },
202
+ { "field": "status", "header": "Status" },
203
+ { "field": "directive", "header": "Directive" },
204
+ { "field": "note", "header": "Human note" },
205
+ { "field": "asked_at", "header": "Asked" },
206
+ { "field": "answered_at", "header": "Answered" }
207
+ ]
208
+ }
209
+ },
210
+ {
211
+ "type": "dataGrid",
212
+ "id": "merge-exclusions",
213
+ "props": {
214
+ "title": "Conflict graph / who blocks whom",
215
+ "rowKey": "id",
216
+ "refreshMs": 5000,
217
+ "collapsible": true,
218
+ "defaultCollapsed": true,
219
+ "data": {
220
+ "kind": "datasource",
221
+ "source": "app",
222
+ "table": "plan_merge_exclusions",
223
+ "orderBy": { "field": "id", "dir": "asc" },
224
+ "filter": [{ "field": "plan_key", "eqParam": true }]
225
+ },
226
+ "columns": [
227
+ { "field": "task_a", "header": "Task A" },
228
+ { "field": "task_b", "header": "Task B" },
229
+ { "field": "files", "header": "Shared files" },
230
+ { "field": "source", "header": "Source" },
231
+ { "field": "updated_at", "header": "Updated" }
232
+ ]
233
+ }
234
+ },
235
+ {
236
+ "type": "dataGrid",
237
+ "id": "coordination-notes",
238
+ "props": {
239
+ "title": "Coordination notes",
240
+ "rowKey": "id",
241
+ "refreshMs": 5000,
242
+ "collapsible": true,
243
+ "defaultCollapsed": true,
244
+ "data": {
245
+ "kind": "datasource",
246
+ "source": "app",
247
+ "table": "plan_blackboard",
248
+ "orderBy": { "field": "id", "dir": "asc" },
249
+ "filter": [{ "field": "plan_key", "eqParam": true }]
250
+ },
251
+ "columns": [
252
+ { "field": "wave", "header": "Wave" },
253
+ { "field": "author_task", "header": "Agent" },
254
+ { "field": "kind", "header": "Kind" },
255
+ { "field": "files", "header": "Files" },
256
+ { "field": "body", "header": "Note" },
257
+ { "field": "created_at", "header": "Posted" }
258
+ ]
259
+ }
260
+ },
261
+ {
262
+ "type": "dataGrid",
263
+ "id": "trial-merge-results",
264
+ "props": {
265
+ "title": "Trial-merge results",
266
+ "rowKey": "id",
267
+ "refreshMs": 5000,
268
+ "collapsible": true,
269
+ "defaultCollapsed": true,
270
+ "data": {
271
+ "kind": "datasource",
272
+ "source": "app",
273
+ "table": "plan_trial_merges",
274
+ "orderBy": { "field": "created_at", "dir": "desc" },
275
+ "filter": [
276
+ { "field": "plan_key", "eqParam": true },
277
+ { "field": "result", "in": ["merge-conflict", "suite-failed"] },
278
+ { "field": "resolved", "in": [0] }
279
+ ]
280
+ },
281
+ "tabs": [
282
+ {
283
+ "label": "Needs attention",
284
+ "filter": [
285
+ { "field": "plan_key", "eqParam": true },
286
+ { "field": "result", "in": ["merge-conflict", "suite-failed"] },
287
+ { "field": "resolved", "in": [0] }
288
+ ]
289
+ },
290
+ {
291
+ "label": "Clean",
292
+ "filter": [
293
+ { "field": "plan_key", "eqParam": true },
294
+ { "field": "result", "in": ["clean"] }
295
+ ]
296
+ },
297
+ {
298
+ "label": "All",
299
+ "filter": [{ "field": "plan_key", "eqParam": true }]
300
+ }
301
+ ],
302
+ "columns": [
303
+ { "field": "wave", "header": "Wave" },
304
+ { "field": "result", "header": "Result" },
305
+ { "field": "heads", "header": "Heads" },
306
+ { "field": "conflicts", "header": "Conflicts" },
307
+ { "field": "failing", "header": "Failing" },
308
+ { "field": "summary", "header": "Summary" },
309
+ { "field": "created_at", "header": "Recorded" }
310
+ ]
311
+ }
312
+ }
313
+ ]
314
+ }
@@ -7,10 +7,11 @@
7
7
  "id": "nav",
8
8
  "props": {
9
9
  "variant": "bar",
10
+ "sticky": true,
10
11
  "title": "Nano Workforce",
11
12
  "items": [
12
13
  { "label": "Convergence", "page": "home" },
13
- { "label": "Epic", "page": "epic" }
14
+ { "label": "Epics", "page": "epic" }
14
15
  ]
15
16
  }
16
17
  },
@@ -23,7 +24,7 @@
23
24
  "type": "text",
24
25
  "id": "subtitle",
25
26
  "props": {
26
- "text": "Hand an issue to the fleet to plan and implement it, then track each plan's review trace, wave state, merge-exclusion graph, coordination notes, and trial-merge gate results.",
27
+ "text": "Hand an issue to the fleet to plan and implement it, then open an epic to track its review trace, wave state, merge-exclusion graph, coordination notes, and trial-merge gate results.",
27
28
  "variant": "sub"
28
29
  }
29
30
  },
@@ -44,7 +45,7 @@
44
45
  "type": "dataGrid",
45
46
  "id": "epic-plans",
46
47
  "props": {
47
- "title": "Plans",
48
+ "title": "Epics",
48
49
  "rowKey": "plan_key",
49
50
  "refreshMs": 5000,
50
51
  "data": {
@@ -66,241 +67,13 @@
66
67
  { "label": "All", "filter": [] }
67
68
  ],
68
69
  "columns": [
69
- { "field": "plan_key", "header": "Issue", "linkField": "issue_url" },
70
+ { "field": "plan_key", "header": "Epic", "link": { "kind": "page", "page": "epic-detail", "keyField": "plan_key" } },
70
71
  { "field": "status", "header": "Status", "link": { "kind": "processExplorer", "keyField": "process_key" } },
72
+ { "field": "wave_label", "header": "Wave" },
71
73
  { "field": "task_count", "header": "Tasks" },
72
- { "field": "open_task_id", "header": "Open escalation" },
73
- { "field": "open_plan_round", "header": "Plan escalation round" },
74
+ { "field": "open_plan_findings", "header": "Attention", "badge": { "tone": "danger", "label": "!" } },
75
+ { "field": "issue_number", "header": "Issue", "linkField": "issue_url" },
74
76
  { "field": "updated_at", "header": "Updated" }
75
- ],
76
- "rowActions": [
77
- {
78
- "label": "Proceed with plan",
79
- "confirm": "Dispatch the current unapproved plan as a human override?",
80
- "showWhenField": "open_plan_escalation_id",
81
- "action": {
82
- "path": "/app/api/actions/message",
83
- "body": {
84
- "name": "plan-escalation-answered",
85
- "correlationKey": "{{row.plan_key}}",
86
- "variables": { "directive": "proceed", "note": "Proceed override from the plans page." }
87
- }
88
- }
89
- }
90
- ],
91
- "detail": {
92
- "linkField": "issue_url",
93
- "fields": [
94
- { "field": "repo", "label": "Repository" },
95
- { "field": "issue_number", "label": "Issue number" },
96
- { "field": "base_branch", "label": "Base branch (blank = repo default)" },
97
- { "field": "outcome", "label": "Outcome" },
98
- { "field": "open_plan_findings", "label": "Open plan-review findings" },
99
- { "field": "open_task_question", "label": "Open escalation question" }
100
- ],
101
- "form": {
102
- "showWhenField": "open_plan_escalation_id",
103
- "title": "Answer the open plan-review escalation",
104
- "promptField": "open_plan_findings",
105
- "inputKey": "note",
106
- "inputLabel": "Revision directive note for the planner",
107
- "submitLabel": "Revise plan",
108
- "action": {
109
- "path": "/app/api/actions/message",
110
- "body": {
111
- "name": "plan-escalation-answered",
112
- "correlationKey": "{{row.plan_key}}",
113
- "variables": { "directive": "revise", "note": "{{form.note}}" }
114
- }
115
- }
116
- }
117
- }
118
- }
119
- },
120
- {
121
- "type": "dataGrid",
122
- "id": "plan-reviews",
123
- "props": {
124
- "title": "Plan review trace",
125
- "refreshMs": 5000,
126
- "data": {
127
- "kind": "datasource",
128
- "source": "app",
129
- "table": "plan_reviews",
130
- "orderBy": { "field": "round", "dir": "asc" }
131
- },
132
- "columns": [
133
- { "field": "plan_key", "header": "Plan" },
134
- { "field": "round", "header": "Round" },
135
- { "field": "epoch", "header": "Epoch" },
136
- { "field": "approved", "header": "Approved? (1/0)" },
137
- { "field": "findings", "header": "Reviewer findings" },
138
- { "field": "created_at", "header": "Recorded" }
139
- ]
140
- }
141
- },
142
- {
143
- "type": "dataGrid",
144
- "id": "plan-review-escalations",
145
- "props": {
146
- "title": "Plan-review escalations",
147
- "rowKey": "id",
148
- "refreshMs": 5000,
149
- "data": {
150
- "kind": "datasource",
151
- "source": "app",
152
- "table": "plan_review_escalations",
153
- "orderBy": { "field": "id", "dir": "desc" }
154
- },
155
- "columns": [
156
- { "field": "plan_key", "header": "Plan" },
157
- { "field": "epoch", "header": "Epoch" },
158
- { "field": "round", "header": "Round" },
159
- { "field": "findings", "header": "Findings" },
160
- { "field": "status", "header": "Status" },
161
- { "field": "directive", "header": "Directive" },
162
- { "field": "note", "header": "Human note" },
163
- { "field": "asked_at", "header": "Asked" },
164
- { "field": "answered_at", "header": "Answered" }
165
- ]
166
- }
167
- },
168
- {
169
- "type": "dataGrid",
170
- "id": "wave-state",
171
- "props": {
172
- "title": "Wave state",
173
- "rowKey": "id",
174
- "refreshMs": 5000,
175
- "data": {
176
- "kind": "datasource",
177
- "source": "app",
178
- "table": "plan_tasks",
179
- "orderBy": { "field": "wave", "dir": "asc" },
180
- "filter": [
181
- { "field": "status", "in": ["pending", "opened", "escalated", "waiting-for-lane", "blocked"] }
182
- ]
183
- },
184
- "tabs": [
185
- {
186
- "label": "Active",
187
- "filter": [
188
- { "field": "status", "in": ["pending", "opened", "escalated", "waiting-for-lane", "blocked"] }
189
- ]
190
- },
191
- {
192
- "label": "Skipped",
193
- "filter": [{ "field": "status", "in": ["skipped"] }]
194
- },
195
- { "label": "All", "filter": [] }
196
- ],
197
- "columns": [
198
- { "field": "plan_key", "header": "Plan" },
199
- { "field": "wave", "header": "Wave" },
200
- { "field": "task_id", "header": "Task" },
201
- { "field": "title", "header": "Title" },
202
- { "field": "status", "header": "Status" },
203
- { "field": "pr_key", "header": "PR" },
204
- { "field": "summary", "header": "Summary" },
205
- { "field": "updated_at", "header": "Updated" }
206
- ],
207
- "detail": {
208
- "fields": [
209
- { "field": "open_question", "label": "Open question" },
210
- { "field": "answer", "label": "Answer" },
211
- { "field": "draft_pr_key", "label": "Draft PR" },
212
- { "field": "prompt", "label": "Prompt" }
213
- ]
214
- }
215
- }
216
- },
217
- {
218
- "type": "dataGrid",
219
- "id": "merge-exclusions",
220
- "props": {
221
- "title": "Conflict graph / who blocks whom",
222
- "rowKey": "id",
223
- "refreshMs": 5000,
224
- "data": {
225
- "kind": "datasource",
226
- "source": "app",
227
- "table": "plan_merge_exclusions",
228
- "orderBy": { "field": "id", "dir": "asc" }
229
- },
230
- "columns": [
231
- { "field": "plan_key", "header": "Plan" },
232
- { "field": "task_a", "header": "Task A" },
233
- { "field": "task_b", "header": "Task B" },
234
- { "field": "files", "header": "Shared files" },
235
- { "field": "source", "header": "Source" },
236
- { "field": "updated_at", "header": "Updated" }
237
- ]
238
- }
239
- },
240
- {
241
- "type": "dataGrid",
242
- "id": "coordination-notes",
243
- "props": {
244
- "title": "Coordination notes",
245
- "rowKey": "id",
246
- "refreshMs": 5000,
247
- "data": {
248
- "kind": "datasource",
249
- "source": "app",
250
- "table": "plan_blackboard",
251
- "orderBy": { "field": "id", "dir": "asc" }
252
- },
253
- "columns": [
254
- { "field": "plan_key", "header": "Plan" },
255
- { "field": "wave", "header": "Wave" },
256
- { "field": "author_task", "header": "Agent" },
257
- { "field": "kind", "header": "Kind" },
258
- { "field": "files", "header": "Files" },
259
- { "field": "body", "header": "Note" },
260
- { "field": "created_at", "header": "Posted" }
261
- ]
262
- }
263
- },
264
- {
265
- "type": "dataGrid",
266
- "id": "trial-merge-results",
267
- "props": {
268
- "title": "Trial-merge results",
269
- "rowKey": "id",
270
- "refreshMs": 5000,
271
- "data": {
272
- "kind": "datasource",
273
- "source": "app",
274
- "table": "plan_trial_merges",
275
- "orderBy": { "field": "created_at", "dir": "desc" },
276
- "filter": [
277
- { "field": "result", "in": ["merge-conflict", "suite-failed"] },
278
- { "field": "resolved", "in": [0] }
279
- ]
280
- },
281
- "tabs": [
282
- {
283
- "label": "Needs attention",
284
- "filter": [
285
- { "field": "result", "in": ["merge-conflict", "suite-failed"] },
286
- { "field": "resolved", "in": [0] }
287
- ]
288
- },
289
- {
290
- "label": "Clean",
291
- "filter": [{ "field": "result", "in": ["clean"] }]
292
- },
293
- { "label": "All", "filter": [] }
294
- ],
295
- "columns": [
296
- { "field": "plan_key", "header": "Plan" },
297
- { "field": "wave", "header": "Wave" },
298
- { "field": "result", "header": "Result" },
299
- { "field": "heads", "header": "Heads" },
300
- { "field": "conflicts", "header": "Conflicts" },
301
- { "field": "failing", "header": "Failing" },
302
- { "field": "summary", "header": "Summary" },
303
- { "field": "created_at", "header": "Recorded" }
304
77
  ]
305
78
  }
306
79
  }