dsh-workflow-map 0.0.0-stage → 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 +98 -0
- package/LICENSE +21 -0
- package/README.en.md +447 -0
- package/README.md +423 -2
- package/client.js +9673 -0
- package/cordis.patch.yml +11 -0
- package/docs/OPEN-SOURCE-GUIDE.md +256 -0
- package/docs/screenshot-detail.png +0 -0
- package/docs/screenshot.png +0 -0
- package/icon.svg +6 -0
- package/index.js +12 -0
- package/lib/index.js +974 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +65 -4
- package/src/client/Branches.js +241 -0
- package/src/client/FlowColumn.js +572 -0
- package/src/client/FlowEffects.js +677 -0
- package/src/client/HotInject.js +628 -0
- package/src/client/MainFlow.js +359 -0
- package/src/client/NodeCard.js +545 -0
- package/src/client/NodeDetail.js +569 -0
- package/src/client/ProgressUnder.js +216 -0
- package/src/client/RightPage.js +285 -0
- package/src/client/Settings.js +212 -0
- package/src/client/StepColumn.js +479 -0
- package/src/client/StepDetail.js +250 -0
- package/src/client/export.js +280 -0
- package/src/client/panel.js +1911 -0
- package/src/client/pulse.js +126 -0
- package/src/client/store.js +1090 -0
- package/src/client/theme.js +1164 -0
- package/src/fold.mjs +2633 -0
- package/src/orchestrator.mjs +426 -0
- package/src/route.js +621 -0
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MainFlow.js — the single page: ONE serial main flow, satellites on both sides.
|
|
3
|
+
*
|
|
4
|
+
* The layout the user asked for, in one sentence: the steps run serially down the middle, each
|
|
5
|
+
* step's core unit carries a few parallel "interfaces" off its left and right edges, every
|
|
6
|
+
* interface ends in a round icon naming the TOOL FILE TYPE it stands for, and every connector is
|
|
7
|
+
* a STRAIGHT line. The main chain only ever runs one way — down.
|
|
8
|
+
*
|
|
9
|
+
* Why the satellites exist at all: a step's flow is not a flat list of tool calls, it is "this
|
|
10
|
+
* step touched .md files here, ran pwsh there". The circle is that fact turned into a click
|
|
11
|
+
* target; clicking it opens the right-hand page with the calls of exactly that type (RightPage.js).
|
|
12
|
+
*
|
|
13
|
+
* Division of labour, so nothing is computed twice:
|
|
14
|
+
* store.js wfTypeBuckets pure derivation of the buckets from raw host nodes (ONE place)
|
|
15
|
+
* this file only draws what the buckets already say
|
|
16
|
+
* RightPage.js the same buckets, with their calls, for the opened page
|
|
17
|
+
*
|
|
18
|
+
* Pure: `h` and `say` are injected, no React import, no DOM. tools/selftest-panel.mjs drives the
|
|
19
|
+
* exact tree the frame mounts.
|
|
20
|
+
*
|
|
21
|
+
* Inlined into client.js by tools/build-client.mjs (single-file browser half).
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/* ------------------------------------------------------------------------- */
|
|
25
|
+
/* geometry helpers */
|
|
26
|
+
/* ------------------------------------------------------------------------- */
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The scrollTop that centres one unit in the page viewport. Pure, so the browser check
|
|
30
|
+
* (tools/measure-main.ps1) and the self-test can assert the same arithmetic.
|
|
31
|
+
*
|
|
32
|
+
* The result is clamped to [0, scrollHeight − viewport], because "centred" must never mean
|
|
33
|
+
* "scrolled past the end" — the last unit cannot be centred and forcing it would look like a
|
|
34
|
+
* jump to nowhere.
|
|
35
|
+
*/
|
|
36
|
+
function wfCenterOffset(unitTop, unitHeight, viewportHeight, scrollHeight) {
|
|
37
|
+
var top = wfFinite(unitTop);
|
|
38
|
+
var height = wfFinite(unitHeight);
|
|
39
|
+
var view = wfFinite(viewportHeight);
|
|
40
|
+
var total = wfFinite(scrollHeight);
|
|
41
|
+
if (top === null || height === null || view === null || total === null) return null;
|
|
42
|
+
if (height <= 0 || view <= 0 || total <= 0) return null;
|
|
43
|
+
var wanted = top + height / 2 - view / 2;
|
|
44
|
+
var max = Math.max(0, total - view);
|
|
45
|
+
if (wanted < 0) return 0;
|
|
46
|
+
if (wanted > max) return max;
|
|
47
|
+
return wanted;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** A satellite's accessible name: what it is, how often, on which side. */
|
|
51
|
+
function wfSatelliteLabel(say, sat) {
|
|
52
|
+
var bucket = sat || {};
|
|
53
|
+
var kind = bucket.kind === "tool" ? "panel.page.kind.tool" : "panel.page.kind.file";
|
|
54
|
+
return say("panel.page.sat.label", {
|
|
55
|
+
label: bucket.label || "—",
|
|
56
|
+
kind: say(kind),
|
|
57
|
+
count: wfFinite(bucket.count) === null ? 0 : bucket.count,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* ------------------------------------------------------------------------- */
|
|
62
|
+
/* rendering */
|
|
63
|
+
/* ------------------------------------------------------------------------- */
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* One round icon at the end of one straight interface. `side` decides which end the line is on:
|
|
67
|
+
* a left satellite is `[circle][line → card]`, a right one is `[card ← line][circle]`.
|
|
68
|
+
*
|
|
69
|
+
* The circle is a real <button>: it is a top-level action (open the type page), Tab-reachable,
|
|
70
|
+
* and it carries `aria-pressed` so the opened one is announced as open. The count badge is a
|
|
71
|
+
* sibling <span> — a button inside a button is invalid HTML.
|
|
72
|
+
*/
|
|
73
|
+
function wfRenderSatellite(h, sat, side, say, actions, ctx) {
|
|
74
|
+
var fire = actions || {};
|
|
75
|
+
var context = ctx || {};
|
|
76
|
+
var bucket = sat && typeof sat === "object" ? sat : {};
|
|
77
|
+
var key = typeof bucket.key === "string" ? bucket.key : "";
|
|
78
|
+
var open = context.openType === key && context.openStep === context.stepIndex;
|
|
79
|
+
var count = wfFinite(bucket.count) === null ? 0 : Math.max(0, Math.round(bucket.count));
|
|
80
|
+
var running = bucket.running === true;
|
|
81
|
+
var label = wfSatelliteLabel(say, bucket);
|
|
82
|
+
|
|
83
|
+
var circle = h(
|
|
84
|
+
"button",
|
|
85
|
+
{
|
|
86
|
+
type: "button",
|
|
87
|
+
className: "wf-sat",
|
|
88
|
+
key: "sat",
|
|
89
|
+
"data-dsh-workflow-map": "sat",
|
|
90
|
+
"data-type": key,
|
|
91
|
+
"data-side": side,
|
|
92
|
+
"data-tone": typeof bucket.tone === "string" ? bucket.tone : "read",
|
|
93
|
+
"data-count": String(count),
|
|
94
|
+
"data-live": running ? "true" : "false",
|
|
95
|
+
"data-open": open ? "true" : "false",
|
|
96
|
+
"aria-pressed": open ? "true" : "false",
|
|
97
|
+
"aria-label": label,
|
|
98
|
+
title: label,
|
|
99
|
+
onClick: function () {
|
|
100
|
+
if (typeof fire.onOpenType === "function") fire.onOpenType(context.stepIndex, key);
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
h("span", { className: "wf-sat-glyph", key: "glyph", "aria-hidden": "true" }, bucket.label || "—"),
|
|
104
|
+
count > 1
|
|
105
|
+
? h("span", { className: "wf-sat-count", key: "count", "aria-hidden": "true" }, String(count))
|
|
106
|
+
: null
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
return h(
|
|
110
|
+
"div",
|
|
111
|
+
{
|
|
112
|
+
className: "wf-sat-row",
|
|
113
|
+
key: "sat-" + key,
|
|
114
|
+
"data-side": side,
|
|
115
|
+
"data-live": running ? "true" : "false",
|
|
116
|
+
},
|
|
117
|
+
side === "left" ? circle : h("span", { className: "wf-sat-line", key: "line", "aria-hidden": "true" }),
|
|
118
|
+
side === "left" ? h("span", { className: "wf-sat-line", key: "line", "aria-hidden": "true" }) : circle
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** One side's stack of satellites (empty when the step touched nothing on that side). */
|
|
123
|
+
function wfRenderSatSide(h, list, side, say, actions, ctx) {
|
|
124
|
+
var items = Array.isArray(list) ? list : [];
|
|
125
|
+
if (items.length === 0) return null;
|
|
126
|
+
return h(
|
|
127
|
+
"div",
|
|
128
|
+
{
|
|
129
|
+
className: "wf-sat-side",
|
|
130
|
+
key: "side-" + side,
|
|
131
|
+
"data-side": side,
|
|
132
|
+
role: "group",
|
|
133
|
+
"aria-label": side === "left" ? "读取与查询" : "写入与工具",
|
|
134
|
+
},
|
|
135
|
+
items.map(function (sat) {
|
|
136
|
+
return wfRenderSatellite(h, sat, side, say, actions, ctx);
|
|
137
|
+
})
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* One main-flow unit: the step card in the middle, its satellites on both sides, and — except on
|
|
143
|
+
* the last unit — the straight connector down to the next one.
|
|
144
|
+
*
|
|
145
|
+
* The card is a button. Clicking it opens the step's page on the right; it does NOT expand
|
|
146
|
+
* anything in place (user decision), so the serial line keeps its rhythm no matter what is open.
|
|
147
|
+
*/
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
// NEEDS-CONFIRM - the union of the plan's own flag and the waiting-on-human state
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* The pending-confirm rows of ONE unit, bounded and repaired. Each row is
|
|
154
|
+
* `{ kind, label, at }` as the host folded it; a malformed row is dropped rather than
|
|
155
|
+
* rendered as "undefined".
|
|
156
|
+
*/
|
|
157
|
+
function wfConfirmRows(row) {
|
|
158
|
+
var out = [];
|
|
159
|
+
var list = row && Array.isArray(row.pendingConfirm) ? row.pendingConfirm : [];
|
|
160
|
+
for (var i = 0; i < list.length && out.length < 8; i += 1) {
|
|
161
|
+
var one = list[i];
|
|
162
|
+
if (!one || typeof one !== "object") continue;
|
|
163
|
+
var kind = typeof one.kind === "string" ? one.kind : "";
|
|
164
|
+
if (kind === "") continue;
|
|
165
|
+
out.push({ kind: kind, label: typeof one.label === "string" ? one.label : "" });
|
|
166
|
+
}
|
|
167
|
+
return out;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* The flag's tooltip. A row WITH a reason says what the session is waiting for; a row without
|
|
172
|
+
* one (or none at all — only the plan's marker) says the plan asked for a confirmation here.
|
|
173
|
+
* Never empty: a tooltip of "" reads as a broken control.
|
|
174
|
+
*/
|
|
175
|
+
function wfConfirmTitle(row, say) {
|
|
176
|
+
var rows = wfConfirmRows(row);
|
|
177
|
+
if (rows.length === 0) return say("panel.step.confirm");
|
|
178
|
+
var parts = [];
|
|
179
|
+
for (var i = 0; i < rows.length; i += 1) {
|
|
180
|
+
var one = rows[i];
|
|
181
|
+
var kind = say(one.kind === "approval" ? "panel.kind.approval" : "panel.kind.question");
|
|
182
|
+
parts.push(one.label === "" ? kind : kind + ":" + one.label);
|
|
183
|
+
}
|
|
184
|
+
return say("panel.step.confirm") + " · " + parts.join(";");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function wfRenderUnit(h, unit, position, total, say, actions, ctx) {
|
|
188
|
+
var fire = actions || {};
|
|
189
|
+
var context = ctx || {};
|
|
190
|
+
var row = unit && typeof unit === "object" ? unit : {};
|
|
191
|
+
var index = wfFinite(row.index) === null ? position : Math.round(row.index);
|
|
192
|
+
var status = typeof row.status === "string" ? row.status : "pending";
|
|
193
|
+
// Two different facts, deliberately kept apart:
|
|
194
|
+
// selected the step the panel remembers (the last one opened) — the chain's highlight
|
|
195
|
+
// open the step whose page is on the right right now — the card's aria-pressed
|
|
196
|
+
var selected = context.selectedStep === index;
|
|
197
|
+
var open = context.openStep === index;
|
|
198
|
+
// The satellites' context: the SAME facts, plus THIS unit's index — a circle's click reports
|
|
199
|
+
// (stepIndex, typeKey), and without the index every circle of every step would open step null.
|
|
200
|
+
var unitContext = Object.assign({}, context, { stepIndex: index });
|
|
201
|
+
var dropped = row.dropped && typeof row.dropped === "object" ? row.dropped : {};
|
|
202
|
+
var leftDropped = wfFinite(dropped.left) === null ? 0 : dropped.left;
|
|
203
|
+
var rightDropped = wfFinite(dropped.right) === null ? 0 : dropped.right;
|
|
204
|
+
var metaBits = [];
|
|
205
|
+
if (wfFinite(row.toolCount) !== null) metaBits.push(say("panel.step.tools", { count: row.toolCount }));
|
|
206
|
+
if (wfFinite(row.fileCount) !== null) metaBits.push(say("panel.step.files", { count: row.fileCount }));
|
|
207
|
+
|
|
208
|
+
var card = h(
|
|
209
|
+
"button",
|
|
210
|
+
{
|
|
211
|
+
type: "button",
|
|
212
|
+
className: "wf-unit-card",
|
|
213
|
+
key: "card",
|
|
214
|
+
"data-dsh-workflow-map": "step",
|
|
215
|
+
"data-step-index": index,
|
|
216
|
+
"data-plan-index": row.planIndex === null ? undefined : row.planIndex,
|
|
217
|
+
"data-status": status,
|
|
218
|
+
"data-status-source": row.statusSource,
|
|
219
|
+
"data-selected": selected ? "true" : "false",
|
|
220
|
+
"data-active": row.active ? "true" : "false",
|
|
221
|
+
"aria-pressed": open ? "true" : "false",
|
|
222
|
+
title: row.title,
|
|
223
|
+
onClick: function () {
|
|
224
|
+
if (typeof fire.onOpenStep === "function") fire.onOpenStep(index);
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
h("span", { className: "wf-unit-seq", key: "seq" }, wfPad2(position + 1) + "/" + wfPad2(total)),
|
|
228
|
+
h("span", { className: "wf-unit-ring", key: "ring", "aria-hidden": "true" }, wfStatusGlyph(status)),
|
|
229
|
+
h(
|
|
230
|
+
"span",
|
|
231
|
+
{ className: "wf-unit-main", key: "main" },
|
|
232
|
+
h("span", { className: "wf-unit-title", key: "title" }, row.title || "—"),
|
|
233
|
+
h(
|
|
234
|
+
"span",
|
|
235
|
+
{ className: "wf-unit-meta", key: "meta" },
|
|
236
|
+
h("span", { className: "wf-badge", key: "badge" }, say(row.statusKey || "panel.status.pending")),
|
|
237
|
+
metaBits.map(function (bit, at) {
|
|
238
|
+
return h("span", { key: "bit-" + at }, bit);
|
|
239
|
+
}),
|
|
240
|
+
// "needs confirm" used to live in StepColumn's meta row; the unit keeps it (and the badge order
|
|
241
|
+
// the self-test asserts) so the flag is not lost with that file.
|
|
242
|
+
// "needs confirm" is now the UNION of two sources, both derived:
|
|
243
|
+
// · the plan's own `requiresConfirmation` (the step was marked as a confirm point),
|
|
244
|
+
// · the step being WAITING on a human right now — the host folds an open approval
|
|
245
|
+
// or an unanswered question into `row.pendingConfirm`.
|
|
246
|
+
// The flag names WHY when the row carries a reason, so the user can tell "this step was
|
|
247
|
+
// marked" versus "this step is asking you something right now".
|
|
248
|
+
wfConfirmRows(row).length > 0 || row.requiresConfirmation
|
|
249
|
+
? h(
|
|
250
|
+
"span",
|
|
251
|
+
{ className: "wf-step-flag", key: "flag", title: wfConfirmTitle(row, say) },
|
|
252
|
+
say("panel.step.confirm")
|
|
253
|
+
)
|
|
254
|
+
: null
|
|
255
|
+
),
|
|
256
|
+
status === "running" ? wfRenderLive(h, say, "step") : null
|
|
257
|
+
)
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
var droppedNote =
|
|
261
|
+
leftDropped + rightDropped > 0
|
|
262
|
+
? h(
|
|
263
|
+
"span",
|
|
264
|
+
{ className: "wf-unit-more", key: "more", title: say("panel.page.moreTypes", { count: leftDropped + rightDropped }) },
|
|
265
|
+
say("panel.page.moreTypes", { count: leftDropped + rightDropped })
|
|
266
|
+
)
|
|
267
|
+
: null;
|
|
268
|
+
|
|
269
|
+
return h(
|
|
270
|
+
"li",
|
|
271
|
+
{
|
|
272
|
+
className: "wf-unit",
|
|
273
|
+
key: "unit-" + index,
|
|
274
|
+
"data-dsh-workflow-map": "unit",
|
|
275
|
+
"data-step-index": index,
|
|
276
|
+
"data-status": status,
|
|
277
|
+
"data-selected": selected ? "true" : "false",
|
|
278
|
+
// "needs confirm" on the row itself, so the card's left edge and the ring can be styled
|
|
279
|
+
// without the badge row having to be in view.
|
|
280
|
+
"data-confirm": wfConfirmRows(row).length > 0 || row.requiresConfirmation === true ? "true" : "false",
|
|
281
|
+
},
|
|
282
|
+
wfRenderSatSide(h, row.left, "left", say, actions, unitContext),
|
|
283
|
+
h(
|
|
284
|
+
"div",
|
|
285
|
+
{
|
|
286
|
+
className: "wf-unit-center",
|
|
287
|
+
key: "center",
|
|
288
|
+
},
|
|
289
|
+
card,
|
|
290
|
+
// "Title only" still means ONE line under the card: the rail keeps its number and drops
|
|
291
|
+
// the second (timing) line, exactly as it did in the two-column layout.
|
|
292
|
+
wfRenderProgressDock(h, context.compact ? Object.assign({}, row, { compact: true }) : row, say, context.asOf),
|
|
293
|
+
droppedNote
|
|
294
|
+
),
|
|
295
|
+
wfRenderSatSide(h, row.right, "right", say, actions, unitContext),
|
|
296
|
+
position + 1 < total
|
|
297
|
+
? h("span", {
|
|
298
|
+
className: "wf-unit-link",
|
|
299
|
+
key: "link",
|
|
300
|
+
"aria-hidden": "true",
|
|
301
|
+
"data-done": status === "done" ? "true" : "false",
|
|
302
|
+
// Only the one connector that points directly at the single running step carries the flowing gradient.
|
|
303
|
+
// row.live is derived by the store from the host's runningIndex, so at most one row qualifies.
|
|
304
|
+
"data-live": row.live === true ? "true" : "false",
|
|
305
|
+
})
|
|
306
|
+
: null
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* The page itself. `extras` carries {openStep, openType, asOf}; its scroller is the ONE scroll
|
|
312
|
+
* container of the main flow (the right page scrolls on its own).
|
|
313
|
+
*/
|
|
314
|
+
function wfRenderMainFlow(h, model, say, actions, extras) {
|
|
315
|
+
var ui = extras || {};
|
|
316
|
+
var rows = model && Array.isArray(model.steps) ? model.steps : [];
|
|
317
|
+
var units = model && model.page && Array.isArray(model.page.units) ? model.page.units : [];
|
|
318
|
+
var context = {
|
|
319
|
+
// `openStep` is the step whose page is on the right; `selectedStep` is the panel's remembered
|
|
320
|
+
// selection (they are usually the same, but the header can open a page without re-selecting).
|
|
321
|
+
openStep: wfFinite(ui.openStep) === null ? null : Math.round(ui.openStep),
|
|
322
|
+
selectedStep: wfFinite(model && model.selected) === null ? null : Math.round(model.selected),
|
|
323
|
+
openType: typeof ui.openType === "string" && ui.openType !== "" ? ui.openType : null,
|
|
324
|
+
compact: !!(ui.compact === true),
|
|
325
|
+
asOf: wfFinite(ui.asOf) === null ? (model ? model.now : null) : ui.asOf,
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
var header = h(
|
|
329
|
+
"div",
|
|
330
|
+
{ className: "wf-page-head", key: "head", "data-dsh-workflow-map": "page-head" },
|
|
331
|
+
h("span", { key: "label" }, say("panel.page.main")),
|
|
332
|
+
h("span", { className: "wf-count", key: "count" }, model ? model.progress.done + "/" + model.progress.total : "")
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
var body =
|
|
336
|
+
units.length === 0
|
|
337
|
+
? h("div", { className: "wf-empty", key: "empty" }, say("panel.flow.empty"))
|
|
338
|
+
: h(
|
|
339
|
+
"ul",
|
|
340
|
+
{
|
|
341
|
+
className: "wf-units",
|
|
342
|
+
key: "units",
|
|
343
|
+
role: "list",
|
|
344
|
+
"data-dsh-workflow-map": "page",
|
|
345
|
+
"data-units": units.length,
|
|
346
|
+
"data-running": wfFinite(model && model.page ? model.page.runningIndex : null) === null ? "" : String(model.page.runningIndex),
|
|
347
|
+
},
|
|
348
|
+
units.map(function (unit, position) {
|
|
349
|
+
return wfRenderUnit(h, unit, position, units.length, say, actions, context);
|
|
350
|
+
})
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
return h(
|
|
354
|
+
"div",
|
|
355
|
+
{ className: "wf-page", key: "page", "data-dsh-workflow-map": "main" },
|
|
356
|
+
header,
|
|
357
|
+
h("div", { className: "wf-page-scroll", key: "scroll", "data-dsh-workflow-map": "page-scroll" }, body)
|
|
358
|
+
);
|
|
359
|
+
}
|