@formicoidea/labre-framework-bpmn 0.32.0 → 0.33.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/dist/actions.d.ts +202 -6
- package/dist/actions.js +421 -43
- package/dist/background.d.ts +2 -0
- package/dist/background.js +158 -0
- package/dist/commands.js +496 -5
- package/dist/consts.d.ts +157 -3
- package/dist/consts.js +192 -3
- package/dist/element-renderer.d.ts +10 -4
- package/dist/element-renderer.js +14 -55
- package/dist/element-view.d.ts +100 -8
- package/dist/element-view.js +249 -30
- package/dist/export.d.ts +277 -0
- package/dist/export.js +1802 -0
- package/dist/facts.d.ts +48 -0
- package/dist/facts.js +127 -0
- package/dist/import.d.ts +44 -0
- package/dist/import.js +1440 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +44 -0
- package/dist/interchange.d.ts +109 -0
- package/dist/interchange.js +191 -0
- package/dist/morph.d.ts +61 -0
- package/dist/morph.js +118 -0
- package/dist/node/node-renderer.d.ts +0 -9
- package/dist/node/node-renderer.js +294 -17
- package/dist/pool-hit.d.ts +98 -0
- package/dist/pool-hit.js +130 -0
- package/dist/presets.d.ts +114 -0
- package/dist/presets.js +232 -0
- package/dist/profiles.d.ts +2 -0
- package/dist/profiles.js +189 -0
- package/dist/roles.d.ts +96 -0
- package/dist/roles.js +410 -0
- package/dist/rules.d.ts +199 -0
- package/dist/rules.js +1539 -0
- package/dist/templates/index.js +116 -9
- package/dist/toolbar/bpmn-senior-button.js +8 -2
- package/dist/toolbar/config.d.ts +27 -2
- package/dist/toolbar/config.js +86 -2
- package/dist/toolbar/icons.d.ts +67 -0
- package/dist/toolbar/icons.js +141 -0
- package/dist/toolbar/senior-tool.js +1 -0
- package/dist/translations.d.ts +3 -1
- package/dist/translations.js +8 -3
- package/dist/view.d.ts +6 -2
- package/dist/view.js +68 -5
- package/package.json +2 -2
package/dist/commands.js
CHANGED
|
@@ -1,11 +1,44 @@
|
|
|
1
|
-
import { activateBpmnSequenceFlow, createBpmnNode, createBpmnPool, } from './actions.js';
|
|
2
|
-
import { bpmnEndIcon, bpmnGatewayIcon, bpmnPoolIcon, bpmnSequenceIcon, bpmnStartIcon, bpmnTaskIcon, } from './toolbar/icons.js';
|
|
1
|
+
import { activateBpmnAssociation, activateBpmnMessageFlow, activateBpmnSequenceFlow, addBpmnLane, bpmnLanesOf, bpmnPoolsForLaneEdit, bpmnPoolsSelected, createBpmnNode, createBpmnPool, exportBpmnXmlFile, importBpmnSvgFile, importBpmnXmlFile, removeBpmnLane, } from './actions.js';
|
|
2
|
+
import { bpmnAssociationIcon, bpmnCallActivityIcon, bpmnDataObjectIcon, bpmnDataStoreIcon, bpmnEndIcon, bpmnEndMessageIcon, bpmnEndTerminateIcon, bpmnExportXmlIcon, bpmnGatewayIcon, bpmnGatewayParallelIcon, bpmnGroupIcon, bpmnImportSvgIcon, bpmnImportXmlIcon, bpmnLaneAddIcon, bpmnLaneRemoveIcon, bpmnMessageIcon, bpmnPoolIcon, bpmnSequenceIcon, bpmnStartIcon, bpmnStartMessageIcon, bpmnStartTimerIcon, bpmnSubProcessIcon, bpmnTaskIcon, bpmnTaskServiceIcon, bpmnTaskUserIcon, bpmnTextAnnotationIcon, } from './toolbar/icons.js';
|
|
3
|
+
/**
|
|
4
|
+
* Declaration order is DISPLAY order, and past the cap it is also the COLD
|
|
5
|
+
* START — which is why it leads with the canonical core rather than with the
|
|
6
|
+
* events.
|
|
7
|
+
*
|
|
8
|
+
* `selectSeniorMenuCommands` falls back to the authored head of the NOMINATED
|
|
9
|
+
* list for a user who has invoked nothing yet. The row held seven when the
|
|
10
|
+
* recette caught this, and an order grouped strictly by family spent all seven
|
|
11
|
+
* on events: start, three of its variants, two ends, task. A first contact with
|
|
12
|
+
* BPMN with no gateway, no sequence flow and no pool — every button drawing a
|
|
13
|
+
* circle, and nothing to connect them with. Caught in a live recette, and it is
|
|
14
|
+
* pure data.
|
|
15
|
+
*
|
|
16
|
+
* The first seven are therefore the seven artefacts a process cannot be drawn
|
|
17
|
+
* without: start, end, task, exclusive gateway, sequence flow, pool, message
|
|
18
|
+
* flow. Everything else follows in family blocks. The row seats thirteen since
|
|
19
|
+
* 2026-08-28, which makes the head wider but not less load-bearing: a framework
|
|
20
|
+
* whose declarations opened on six variants of one family would still meet a
|
|
21
|
+
* user with a lopsided first contact. Nothing about position STABILITY changes
|
|
22
|
+
* — author order is still the position law, and usage only ever changes
|
|
23
|
+
* membership, never where a button sits (`docs/adr/0008`, amendments of
|
|
24
|
+
* 2026-08-26 and 2026-08-28).
|
|
25
|
+
*
|
|
26
|
+
* The catalogue reads off the same order, so its headers now appear in
|
|
27
|
+
* first-encounter order — events, activities, gateways, flows, swimlanes, data,
|
|
28
|
+
* annotations — and the entries inside each keep author order. Both are better
|
|
29
|
+
* reading than the strict grouping was: swimlanes climbs to where a pool
|
|
30
|
+
* belongs, and the events section opens on the plain start and end rather than
|
|
31
|
+
* burying them under their own variants.
|
|
32
|
+
*/
|
|
3
33
|
const SPECS = [
|
|
34
|
+
/* ── The core: a drawable process, from the first click ─────────────── */
|
|
4
35
|
{
|
|
5
36
|
id: 'addStartEvent',
|
|
6
37
|
label: 'Start event',
|
|
7
38
|
iconKey: 'bpmn.start',
|
|
8
39
|
kind: 'artefact',
|
|
40
|
+
category: 'events',
|
|
41
|
+
senior: true,
|
|
9
42
|
element: 'node:startEvent',
|
|
10
43
|
run: std => createBpmnNode(std, 'startEvent'),
|
|
11
44
|
},
|
|
@@ -14,6 +47,8 @@ const SPECS = [
|
|
|
14
47
|
label: 'End event',
|
|
15
48
|
iconKey: 'bpmn.end',
|
|
16
49
|
kind: 'artefact',
|
|
50
|
+
category: 'events',
|
|
51
|
+
senior: true,
|
|
17
52
|
element: 'node:endEvent',
|
|
18
53
|
run: std => createBpmnNode(std, 'endEvent'),
|
|
19
54
|
},
|
|
@@ -22,6 +57,8 @@ const SPECS = [
|
|
|
22
57
|
label: 'Task',
|
|
23
58
|
iconKey: 'bpmn.task',
|
|
24
59
|
kind: 'artefact',
|
|
60
|
+
category: 'activities',
|
|
61
|
+
senior: true,
|
|
25
62
|
element: 'node:task',
|
|
26
63
|
run: std => createBpmnNode(std, 'task'),
|
|
27
64
|
},
|
|
@@ -30,6 +67,8 @@ const SPECS = [
|
|
|
30
67
|
label: 'Exclusive gateway',
|
|
31
68
|
iconKey: 'bpmn.gateway',
|
|
32
69
|
kind: 'artefact',
|
|
70
|
+
category: 'gateways',
|
|
71
|
+
senior: true,
|
|
33
72
|
element: 'node:gatewayExclusive',
|
|
34
73
|
run: std => createBpmnNode(std, 'gatewayExclusive'),
|
|
35
74
|
},
|
|
@@ -38,6 +77,8 @@ const SPECS = [
|
|
|
38
77
|
label: 'Sequence flow',
|
|
39
78
|
iconKey: 'bpmn.sequence',
|
|
40
79
|
kind: 'tool',
|
|
80
|
+
category: 'flows',
|
|
81
|
+
senior: true,
|
|
41
82
|
element: 'connector:sequence',
|
|
42
83
|
run: activateBpmnSequenceFlow,
|
|
43
84
|
},
|
|
@@ -46,19 +87,195 @@ const SPECS = [
|
|
|
46
87
|
label: 'Pool',
|
|
47
88
|
iconKey: 'bpmn.pool',
|
|
48
89
|
kind: 'artefact',
|
|
90
|
+
category: 'swimlanes',
|
|
91
|
+
senior: true,
|
|
49
92
|
element: 'pool',
|
|
50
93
|
run: createBpmnPool,
|
|
51
94
|
},
|
|
95
|
+
{
|
|
96
|
+
// Seventh, and the last of the cold start: the moment there are two
|
|
97
|
+
// participants there is a message between them, and it is the one arrow a
|
|
98
|
+
// sequence flow may never stand in for.
|
|
99
|
+
id: 'messageFlowTool',
|
|
100
|
+
label: 'Message flow',
|
|
101
|
+
iconKey: 'bpmn.message',
|
|
102
|
+
kind: 'tool',
|
|
103
|
+
category: 'flows',
|
|
104
|
+
senior: true,
|
|
105
|
+
element: 'connector:message',
|
|
106
|
+
run: activateBpmnMessageFlow,
|
|
107
|
+
},
|
|
108
|
+
/* ── Activities: the typed tasks and the two that stand for a process ─ */
|
|
109
|
+
{
|
|
110
|
+
id: 'addUserTask',
|
|
111
|
+
label: 'User task',
|
|
112
|
+
iconKey: 'bpmn.task.user',
|
|
113
|
+
kind: 'artefact',
|
|
114
|
+
category: 'activities',
|
|
115
|
+
senior: true,
|
|
116
|
+
element: 'node:taskUser',
|
|
117
|
+
run: std => createBpmnNode(std, 'taskUser'),
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: 'addServiceTask',
|
|
121
|
+
label: 'Service task',
|
|
122
|
+
iconKey: 'bpmn.task.service',
|
|
123
|
+
kind: 'artefact',
|
|
124
|
+
category: 'activities',
|
|
125
|
+
senior: true,
|
|
126
|
+
element: 'node:taskService',
|
|
127
|
+
run: std => createBpmnNode(std, 'taskService'),
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
id: 'addSubProcess',
|
|
131
|
+
label: 'Sub-process',
|
|
132
|
+
iconKey: 'bpmn.sub-process',
|
|
133
|
+
kind: 'artefact',
|
|
134
|
+
category: 'activities',
|
|
135
|
+
senior: true,
|
|
136
|
+
element: 'node:subProcess',
|
|
137
|
+
run: std => createBpmnNode(std, 'subProcess'),
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
id: 'addCallActivity',
|
|
141
|
+
label: 'Call activity',
|
|
142
|
+
iconKey: 'bpmn.call-activity',
|
|
143
|
+
kind: 'artefact',
|
|
144
|
+
category: 'activities',
|
|
145
|
+
senior: true,
|
|
146
|
+
element: 'node:callActivity',
|
|
147
|
+
run: std => createBpmnNode(std, 'callActivity'),
|
|
148
|
+
},
|
|
149
|
+
/* ── The other gateway ──────────────────────────────────────────────── */
|
|
150
|
+
{
|
|
151
|
+
// `addParallelGateway`, matching the word order of the sibling that already
|
|
152
|
+
// shipped (`addExclusiveGateway`) — a command id is a value a host override
|
|
153
|
+
// table points at, and the pair reads as a pair in every list that sorts.
|
|
154
|
+
id: 'addParallelGateway',
|
|
155
|
+
label: 'Parallel gateway',
|
|
156
|
+
iconKey: 'bpmn.gateway.parallel',
|
|
157
|
+
kind: 'artefact',
|
|
158
|
+
category: 'gateways',
|
|
159
|
+
senior: true,
|
|
160
|
+
element: 'node:gatewayParallel',
|
|
161
|
+
run: std => createBpmnNode(std, 'gatewayParallel'),
|
|
162
|
+
},
|
|
163
|
+
/* ── Event variants: what TRIGGERS a start, what an end does on the way
|
|
164
|
+
out. Refinements of the two plain events above, so they follow them. ─ */
|
|
165
|
+
{
|
|
166
|
+
id: 'addMessageStartEvent',
|
|
167
|
+
label: 'Message start event',
|
|
168
|
+
iconKey: 'bpmn.start.message',
|
|
169
|
+
kind: 'artefact',
|
|
170
|
+
category: 'events',
|
|
171
|
+
senior: false,
|
|
172
|
+
element: 'node:startEventMessage',
|
|
173
|
+
run: std => createBpmnNode(std, 'startEventMessage'),
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
id: 'addTimerStartEvent',
|
|
177
|
+
label: 'Timer start event',
|
|
178
|
+
iconKey: 'bpmn.start.timer',
|
|
179
|
+
kind: 'artefact',
|
|
180
|
+
category: 'events',
|
|
181
|
+
senior: false,
|
|
182
|
+
element: 'node:startEventTimer',
|
|
183
|
+
run: std => createBpmnNode(std, 'startEventTimer'),
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
id: 'addMessageEndEvent',
|
|
187
|
+
label: 'Message end event',
|
|
188
|
+
iconKey: 'bpmn.end.message',
|
|
189
|
+
kind: 'artefact',
|
|
190
|
+
category: 'events',
|
|
191
|
+
senior: false,
|
|
192
|
+
element: 'node:endEventMessage',
|
|
193
|
+
run: std => createBpmnNode(std, 'endEventMessage'),
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
id: 'addTerminateEndEvent',
|
|
197
|
+
label: 'Terminate end event',
|
|
198
|
+
iconKey: 'bpmn.end.terminate',
|
|
199
|
+
kind: 'artefact',
|
|
200
|
+
category: 'events',
|
|
201
|
+
senior: false,
|
|
202
|
+
element: 'node:endEventTerminate',
|
|
203
|
+
run: std => createBpmnNode(std, 'endEventTerminate'),
|
|
204
|
+
},
|
|
205
|
+
/* ── The last connecting object ─────────────────────────────────────── */
|
|
206
|
+
{
|
|
207
|
+
id: 'associationTool',
|
|
208
|
+
label: 'Association',
|
|
209
|
+
iconKey: 'bpmn.association',
|
|
210
|
+
kind: 'tool',
|
|
211
|
+
category: 'flows',
|
|
212
|
+
senior: false,
|
|
213
|
+
element: 'connector:association',
|
|
214
|
+
run: activateBpmnAssociation,
|
|
215
|
+
},
|
|
216
|
+
/* ── Data ───────────────────────────────────────────────────────────── */
|
|
217
|
+
{
|
|
218
|
+
id: 'addDataObject',
|
|
219
|
+
label: 'Data object',
|
|
220
|
+
iconKey: 'bpmn.data-object',
|
|
221
|
+
kind: 'artefact',
|
|
222
|
+
category: 'data',
|
|
223
|
+
senior: true,
|
|
224
|
+
element: 'node:dataObject',
|
|
225
|
+
run: std => createBpmnNode(std, 'dataObject'),
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
id: 'addDataStore',
|
|
229
|
+
label: 'Data store',
|
|
230
|
+
iconKey: 'bpmn.data-store',
|
|
231
|
+
kind: 'artefact',
|
|
232
|
+
category: 'data',
|
|
233
|
+
senior: false,
|
|
234
|
+
element: 'node:dataStore',
|
|
235
|
+
run: std => createBpmnNode(std, 'dataStore'),
|
|
236
|
+
},
|
|
237
|
+
/* ── Artifacts: what an author writes ON the picture ────────────────── */
|
|
238
|
+
{
|
|
239
|
+
id: 'addTextAnnotation',
|
|
240
|
+
label: 'Text annotation',
|
|
241
|
+
iconKey: 'bpmn.text-annotation',
|
|
242
|
+
kind: 'artefact',
|
|
243
|
+
category: 'annotations',
|
|
244
|
+
senior: true,
|
|
245
|
+
element: 'node:textAnnotation',
|
|
246
|
+
run: std => createBpmnNode(std, 'textAnnotation'),
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
// Filed with the annotation and not in a section of its own: both are
|
|
250
|
+
// things an author writes ON the picture rather than parts of the process,
|
|
251
|
+
// and BPMN 2.0.2 §10.4 exempts both from every rule for the same reason.
|
|
252
|
+
id: 'addGroup',
|
|
253
|
+
label: 'Group',
|
|
254
|
+
iconKey: 'bpmn.group',
|
|
255
|
+
kind: 'artefact',
|
|
256
|
+
category: 'annotations',
|
|
257
|
+
// Out of the fourteen, and not because it is unimportant: a lasso is drawn
|
|
258
|
+
// round a part of the process that is already there, so it is never the
|
|
259
|
+
// first thing reached for on a blank board — which is precisely what the
|
|
260
|
+
// senior row is a shortcut to.
|
|
261
|
+
senior: false,
|
|
262
|
+
element: 'node:group',
|
|
263
|
+
run: std => createBpmnNode(std, 'group'),
|
|
264
|
+
},
|
|
52
265
|
];
|
|
53
|
-
|
|
266
|
+
const toolboxCommands = SPECS.map((spec, order) => ({
|
|
54
267
|
id: `bpmn.${spec.id}`,
|
|
55
268
|
owner: 'bpmn',
|
|
56
269
|
kind: spec.kind,
|
|
57
270
|
labelKey: `com.labre.commands.bpmn.${spec.id}`,
|
|
58
271
|
labelFallback: spec.label,
|
|
59
|
-
category:
|
|
272
|
+
category: spec.category,
|
|
60
273
|
iconKey: spec.iconKey,
|
|
61
|
-
|
|
274
|
+
// The catalogue holds all of them; `senior` decides which fourteen the
|
|
275
|
+
// sub-menu opens on before this user has reached for anything.
|
|
276
|
+
surfaces: spec.senior
|
|
277
|
+
? ['senior-menu', 'catalogue', 'palette', 'agent']
|
|
278
|
+
: ['catalogue', 'palette', 'agent'],
|
|
62
279
|
order,
|
|
63
280
|
scope: 'edgeless',
|
|
64
281
|
defaultKeys: { mac: [], other: [] },
|
|
@@ -66,11 +283,285 @@ export const bpmnCommands = SPECS.map((spec, order) => ({
|
|
|
66
283
|
run: spec.run,
|
|
67
284
|
telemetry: { framework: 'bpmn', element: spec.element },
|
|
68
285
|
}));
|
|
286
|
+
/**
|
|
287
|
+
* The LANE commands (B4) — the first BPMN entries that are not a toolbox slot.
|
|
288
|
+
*
|
|
289
|
+
* A lane is a subdivision of a pool that already exists, so there is nothing to
|
|
290
|
+
* pick up off a palette and drop: these act on a SELECTION. That is why they
|
|
291
|
+
* decline `'senior-menu'` — a permanently greyed entry in the sub-menu of a
|
|
292
|
+
* framework you have not drawn anything with yet is furniture, not an
|
|
293
|
+
* affordance. They keep `'catalogue'`, which every framework command carries,
|
|
294
|
+
* so a host catalogue and Settings › Shortcuts still list them, and they join
|
|
295
|
+
* `'contextual-toolbar'`, whose entry is declared by the pool's own
|
|
296
|
+
* `ToolbarModuleConfig` and INVOKES these — one behaviour, one availability
|
|
297
|
+
* rule, one telemetry emission (`docs/adr/0008`, `docs/adr/0010` M3).
|
|
298
|
+
*/
|
|
299
|
+
const LANE_COMMON = {
|
|
300
|
+
owner: 'bpmn',
|
|
301
|
+
kind: 'action',
|
|
302
|
+
// The same section as the pool they divide: the catalogue reads "here is the
|
|
303
|
+
// frame, and here is what you do to it" in one place, which is where a reader
|
|
304
|
+
// looking for lanes actually looks.
|
|
305
|
+
category: 'swimlanes',
|
|
306
|
+
surfaces: ['catalogue', 'contextual-toolbar', 'palette', 'agent'],
|
|
307
|
+
scope: 'edgeless',
|
|
308
|
+
// Keyless by intent — still bindable from Settings › Shortcuts, which is what
|
|
309
|
+
// `toShortcutDescriptor` being total buys.
|
|
310
|
+
defaultKeys: { mac: [], other: [] },
|
|
311
|
+
availability: 'selection',
|
|
312
|
+
};
|
|
313
|
+
const laneCommands = [
|
|
314
|
+
{
|
|
315
|
+
...LANE_COMMON,
|
|
316
|
+
id: 'bpmn.addLane',
|
|
317
|
+
labelKey: 'com.labre.commands.bpmn.addLane',
|
|
318
|
+
labelFallback: 'Add lane',
|
|
319
|
+
descriptionKey: 'com.labre.commands.bpmn.addLane.description',
|
|
320
|
+
descriptionFallback: 'Divide the selected pool into lanes; the new one takes an equal share.',
|
|
321
|
+
iconKey: 'bpmn.lane-add',
|
|
322
|
+
// Ranked after every toolbox entry, so the catalogue reads "here is what
|
|
323
|
+
// BPMN draws" before "here is what you do to it". They still land in the
|
|
324
|
+
// `swimlanes` section beside the pool — a section is where a command is
|
|
325
|
+
// FILED, and `order` only decides where it sits inside it.
|
|
326
|
+
order: SPECS.length,
|
|
327
|
+
run: addBpmnLane,
|
|
328
|
+
telemetry: { framework: 'bpmn', element: 'pool:lane-add' },
|
|
329
|
+
// Narrows `'selection'`, never contradicts it: a selection holding no
|
|
330
|
+
// unlocked pool has nothing to divide. Read-only rides in
|
|
331
|
+
// `bpmnPoolsForLaneEdit` for the reason the union documents — it holds ONE
|
|
332
|
+
// value, and `'selection'` is the precondition a catalogue has to show.
|
|
333
|
+
when: std => bpmnPoolsForLaneEdit(std).length > 0,
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
...LANE_COMMON,
|
|
337
|
+
id: 'bpmn.removeLane',
|
|
338
|
+
labelKey: 'com.labre.commands.bpmn.removeLane',
|
|
339
|
+
labelFallback: 'Remove lane',
|
|
340
|
+
descriptionKey: 'com.labre.commands.bpmn.removeLane.description',
|
|
341
|
+
descriptionFallback: 'Remove the last lane of the selected pool. Nothing drawn in it moves.',
|
|
342
|
+
iconKey: 'bpmn.lane-remove',
|
|
343
|
+
order: SPECS.length + 1,
|
|
344
|
+
run: removeBpmnLane,
|
|
345
|
+
telemetry: { framework: 'bpmn', element: 'pool:lane-remove' },
|
|
346
|
+
// …and additionally: a pool with no lane has none to remove.
|
|
347
|
+
when: std => bpmnPoolsForLaneEdit(std).some(model => bpmnLanesOf(model).length > 0),
|
|
348
|
+
},
|
|
349
|
+
];
|
|
350
|
+
/**
|
|
351
|
+
* The EXPORT — the first BPMN command whose subject is the whole board.
|
|
352
|
+
*
|
|
353
|
+
* ## Why it hangs off the pool's toolbar and still exports everything
|
|
354
|
+
*
|
|
355
|
+
* A pool is the only thing on the canvas that is unambiguously "this drawing is
|
|
356
|
+
* a BPMN process", so it is where a reader looks for what to do with one. What
|
|
357
|
+
* it is NOT is the scope: a BPMN document is a process, half a process is not a
|
|
358
|
+
* smaller process, and a file holding one participant of a two-participant
|
|
359
|
+
* collaboration would be a picture of a conversation with one side deleted. The
|
|
360
|
+
* selected pool decides the FILENAME and nothing else — see
|
|
361
|
+
* {@link exportBpmnXmlFile}.
|
|
362
|
+
*
|
|
363
|
+
* ## Surfaces
|
|
364
|
+
*
|
|
365
|
+
* It declines `'senior-menu'` for the same reason the lane gestures do: the
|
|
366
|
+
* sub-menu is what you reach for to DRAW something, and this draws nothing. It
|
|
367
|
+
* keeps `'catalogue'`, which is not a category claim but the registry's own
|
|
368
|
+
* invariant — the catalogue is the TOTAL surface, and a command missing from it
|
|
369
|
+
* is unreachable the moment its framework overflows the fourteen slots (pinned
|
|
370
|
+
* by `registry.unit.spec.ts`). On the row itself it sits in the "⋮" menu rather
|
|
371
|
+
* than as a button: it is the rarest thing anybody does to a pool, and the row
|
|
372
|
+
* is already three entries wide.
|
|
373
|
+
*/
|
|
374
|
+
const exportCommand = {
|
|
375
|
+
id: 'bpmn.exportXml',
|
|
376
|
+
owner: 'bpmn',
|
|
377
|
+
kind: 'action',
|
|
378
|
+
labelKey: 'com.labre.commands.bpmn.exportXml',
|
|
379
|
+
labelFallback: 'Export BPMN XML',
|
|
380
|
+
descriptionKey: 'com.labre.commands.bpmn.exportXml.description',
|
|
381
|
+
descriptionFallback: 'Download the whole board as a BPMN 2.0 XML file, ready to open in any BPMN tool.',
|
|
382
|
+
// Filed with the import it is the other half of — see {@link INTERCHANGE}.
|
|
383
|
+
// It shipped filed under `swimlanes`, because the pool's "⋮" is where it is
|
|
384
|
+
// REACHED from and there was no better section for a command with no sibling;
|
|
385
|
+
// a category is where a command is FILED, and it has one now.
|
|
386
|
+
category: 'interchange',
|
|
387
|
+
iconKey: 'bpmn.export-xml',
|
|
388
|
+
surfaces: ['catalogue', 'contextual-toolbar', 'palette', 'agent'],
|
|
389
|
+
order: SPECS.length + 2,
|
|
390
|
+
scope: 'edgeless',
|
|
391
|
+
defaultKeys: { mac: [], other: [] },
|
|
392
|
+
availability: 'selection',
|
|
393
|
+
run: exportBpmnXmlFile,
|
|
394
|
+
telemetry: { framework: 'bpmn', element: 'pool:export-xml' },
|
|
395
|
+
// A pool in the selection, and no more than that: an export READS, so unlike
|
|
396
|
+
// the lane gestures it is offered on a locked pool and on a read-only
|
|
397
|
+
// document — which is precisely the board somebody wants to take away.
|
|
398
|
+
when: std => bpmnPoolsSelected(std).length > 0,
|
|
399
|
+
};
|
|
400
|
+
/**
|
|
401
|
+
* The IMPORT — the other direction of the same format, and the first BPMN
|
|
402
|
+
* command that needs nothing on the board at all.
|
|
403
|
+
*
|
|
404
|
+
* ## `interchange`, and why the export moved into it
|
|
405
|
+
*
|
|
406
|
+
* These two are one subject: this board as a `.bpmn` file, out and in. The
|
|
407
|
+
* export was filed under `swimlanes` because that is the toolbar it is reached
|
|
408
|
+
* from and because a section of one is not a section; a category is where a
|
|
409
|
+
* command is FILED and not where it is reached from, and filing the pair apart
|
|
410
|
+
* would make the catalogue say that taking a process away and bringing one back
|
|
411
|
+
* are different kinds of thing. `order` is untouched, so nothing MOVES inside a
|
|
412
|
+
* section — the position law of `docs/adr/0008` is about rank, and this is
|
|
413
|
+
* membership.
|
|
414
|
+
*
|
|
415
|
+
* ## Surfaces: four, and the one it declines
|
|
416
|
+
*
|
|
417
|
+
* `'senior-menu'` since the PO decision of 2026-08-28, which REVERSES the
|
|
418
|
+
* ruling this comment used to carry ("the sub-menu is a row of things you DRAW,
|
|
419
|
+
* and this draws nothing you chose"). The distinction survives for the EXPORT,
|
|
420
|
+
* which is still catalogue-and-toolbar only: an export is what you do to a
|
|
421
|
+
* board you already have, and it is reached from the pool it is about. An
|
|
422
|
+
* import is where a board COMES FROM. On an empty canvas the sub-menu is the
|
|
423
|
+
* first thing a user opens, and "start from a file somebody sent me" belongs in
|
|
424
|
+
* that row beside "start from a start event" — asking them to find the
|
|
425
|
+
* catalogue sidepanel first was the friction the decision names.
|
|
426
|
+
*
|
|
427
|
+
* The row itself is not at risk: BPMN's catalogue has outgrown the cap since
|
|
428
|
+
* #157, so `selectSeniorMenuCommands` already ranks the nomination list down to
|
|
429
|
+
* thirteen buttons plus "More artefacts…", and this entry takes a slot only
|
|
430
|
+
* when the user actually reaches for it. It is the fifteenth nomination, and
|
|
431
|
+
* nothing about the arbitration changes.
|
|
432
|
+
*
|
|
433
|
+
* No `'contextual-toolbar'`, and that is the difference from the export — a
|
|
434
|
+
* contextual toolbar is a statement about a SELECTION, and the moment this
|
|
435
|
+
* command is most wanted is on an empty board with nothing selected at all. It
|
|
436
|
+
* keeps `'catalogue'` (the registry's total surface — a command missing from it
|
|
437
|
+
* is unreachable), `'palette'` and `'agent'`.
|
|
438
|
+
*
|
|
439
|
+
* ## `'editable'`, which is the first use of it in the repo
|
|
440
|
+
*
|
|
441
|
+
* An import needs no selection — but it WRITES, so a read-only document is a
|
|
442
|
+
* document it cannot run on, and that is a precondition a catalogue has to be
|
|
443
|
+
* able to show. `'editable'` is exactly that value and it has been in the union
|
|
444
|
+
* since `docs/adr/0008` (`Availability`, `command-registry.ts`); nothing had
|
|
445
|
+
* reached for it before. `'always'` would light the entry on a read-only
|
|
446
|
+
* document, do nothing when clicked, and put the same untruth into the
|
|
447
|
+
* serializable manifest a host reads — which is the one thing `availability`
|
|
448
|
+
* exists to prevent.
|
|
449
|
+
*
|
|
450
|
+
* The guard inside {@link importBpmnXmlFile} stays: a declaration is what a
|
|
451
|
+
* surface renders from, and the action is what actually touches the store.
|
|
452
|
+
*
|
|
453
|
+
* The mirror image of the export, which READS and is therefore `'selection'` on
|
|
454
|
+
* a pool and offered on a read-only document precisely because that is the
|
|
455
|
+
* board somebody wants to take away.
|
|
456
|
+
*/
|
|
457
|
+
const importCommand = {
|
|
458
|
+
id: 'bpmn.importXml',
|
|
459
|
+
owner: 'bpmn',
|
|
460
|
+
kind: 'action',
|
|
461
|
+
labelKey: 'com.labre.commands.bpmn.importXml',
|
|
462
|
+
labelFallback: 'Import BPMN XML',
|
|
463
|
+
descriptionKey: 'com.labre.commands.bpmn.importXml.description',
|
|
464
|
+
descriptionFallback: 'Open a BPMN 2.0 XML file as a board. What Labre cannot draw is kept in the document, and the import says what it was.',
|
|
465
|
+
category: 'interchange',
|
|
466
|
+
iconKey: 'bpmn.import-xml',
|
|
467
|
+
surfaces: ['senior-menu', 'catalogue', 'palette', 'agent'],
|
|
468
|
+
order: SPECS.length + 3,
|
|
469
|
+
scope: 'edgeless',
|
|
470
|
+
// Keyless by intent, like every other BPMN entry: past fourteen a framework
|
|
471
|
+
// binds by host override rather than by shipping a default chord. Still
|
|
472
|
+
// bindable from Settings › Shortcuts, which is what `toShortcutDescriptor`
|
|
473
|
+
// being total buys.
|
|
474
|
+
defaultKeys: { mac: [], other: [] },
|
|
475
|
+
availability: 'editable',
|
|
476
|
+
run: importBpmnXmlFile,
|
|
477
|
+
// `board:` and not `pool:`: the export names the pool whose toolbar launched
|
|
478
|
+
// it, and this one is launched with no pool anywhere.
|
|
479
|
+
telemetry: { framework: 'bpmn', element: 'board:import-xml' },
|
|
480
|
+
};
|
|
481
|
+
/**
|
|
482
|
+
* The SVG FALLBACK import — the visual tier, named as such before the picker
|
|
483
|
+
* opens.
|
|
484
|
+
*
|
|
485
|
+
* ## Why it is not in the senior sub-menu, and that is an arbitration
|
|
486
|
+
*
|
|
487
|
+
* `bpmn.importXml` took the fifteenth nomination on the PO's ruling of
|
|
488
|
+
* 2026-08-28, and that ruling was about where a BPMN board comes FROM: a file
|
|
489
|
+
* somebody sent you, in the format the framework speaks. This is the other
|
|
490
|
+
* kind of file — a picture of a process, from a tool that does not export
|
|
491
|
+
* `.bpmn` at all — and it lands one click away, in the artefact catalogue
|
|
492
|
+
* behind "More artefacts…", rather than taking a sixteenth slot in a row that
|
|
493
|
+
* seats fourteen. The registry spec's budget assertion allows exactly ONE
|
|
494
|
+
* over-nomination per owner and BPMN has spent it; this entry deliberately does
|
|
495
|
+
* not contest it.
|
|
496
|
+
*
|
|
497
|
+
* **Flagged for the PO**: if the fallback turns out to be what people actually
|
|
498
|
+
* reach for — a `.svg` is what most drawing tools export — this is a one-line
|
|
499
|
+
* change (`surfaces` gains `'senior-menu'`), and it is a curation decision
|
|
500
|
+
* rather than a merge.
|
|
501
|
+
*
|
|
502
|
+
* ## The label says the tier, because P2 requires it BEFORE the file is read
|
|
503
|
+
*
|
|
504
|
+
* "Import SVG sketch", and a description that spends its whole sentence on what
|
|
505
|
+
* this is not: best effort, shapes and text, no round-trip. ADR 0012 is blunt
|
|
506
|
+
* about the cost of getting this wrong — "a single 'Import…' entry that hides
|
|
507
|
+
* the difference would earn a support ticket per user" — and the surface is the
|
|
508
|
+
* only place the difference can be stated, because the report comes AFTER the
|
|
509
|
+
* decision to open the file.
|
|
510
|
+
*/
|
|
511
|
+
const importSvgCommand = {
|
|
512
|
+
id: 'bpmn.importSvg',
|
|
513
|
+
owner: 'bpmn',
|
|
514
|
+
kind: 'action',
|
|
515
|
+
labelKey: 'com.labre.commands.bpmn.importSvg',
|
|
516
|
+
labelFallback: 'Import SVG sketch',
|
|
517
|
+
descriptionKey: 'com.labre.commands.bpmn.importSvg.description',
|
|
518
|
+
descriptionFallback: 'Best effort: recognises shapes and text, no round-trip. What arrives is a sketch you then promote into BPMN artefacts.',
|
|
519
|
+
// Filed with the two `.bpmn` directions: the subject is the same one — this
|
|
520
|
+
// board, and a file it came from or goes to.
|
|
521
|
+
category: 'interchange',
|
|
522
|
+
iconKey: 'bpmn.import-svg',
|
|
523
|
+
surfaces: ['catalogue', 'palette', 'agent'],
|
|
524
|
+
order: SPECS.length + 4,
|
|
525
|
+
scope: 'edgeless',
|
|
526
|
+
defaultKeys: { mac: [], other: [] },
|
|
527
|
+
// It WRITES, so a read-only document is one it cannot run on — the same
|
|
528
|
+
// reasoning as `bpmn.importXml`, and the value the catalogue renders from.
|
|
529
|
+
availability: 'editable',
|
|
530
|
+
run: importBpmnSvgFile,
|
|
531
|
+
telemetry: { framework: 'bpmn', element: 'board:import-svg' },
|
|
532
|
+
};
|
|
533
|
+
export const bpmnCommands = [
|
|
534
|
+
...toolboxCommands,
|
|
535
|
+
...laneCommands,
|
|
536
|
+
exportCommand,
|
|
537
|
+
importCommand,
|
|
538
|
+
importSvgCommand,
|
|
539
|
+
];
|
|
69
540
|
export const bpmnCommandIcons = {
|
|
70
541
|
'bpmn.start': bpmnStartIcon,
|
|
542
|
+
'bpmn.start.message': bpmnStartMessageIcon,
|
|
543
|
+
'bpmn.start.timer': bpmnStartTimerIcon,
|
|
71
544
|
'bpmn.end': bpmnEndIcon,
|
|
545
|
+
'bpmn.end.message': bpmnEndMessageIcon,
|
|
546
|
+
'bpmn.end.terminate': bpmnEndTerminateIcon,
|
|
72
547
|
'bpmn.task': bpmnTaskIcon,
|
|
548
|
+
'bpmn.task.user': bpmnTaskUserIcon,
|
|
549
|
+
'bpmn.task.service': bpmnTaskServiceIcon,
|
|
550
|
+
'bpmn.sub-process': bpmnSubProcessIcon,
|
|
551
|
+
'bpmn.call-activity': bpmnCallActivityIcon,
|
|
73
552
|
'bpmn.gateway': bpmnGatewayIcon,
|
|
553
|
+
'bpmn.gateway.parallel': bpmnGatewayParallelIcon,
|
|
74
554
|
'bpmn.sequence': bpmnSequenceIcon,
|
|
555
|
+
'bpmn.message': bpmnMessageIcon,
|
|
556
|
+
'bpmn.association': bpmnAssociationIcon,
|
|
557
|
+
'bpmn.data-object': bpmnDataObjectIcon,
|
|
558
|
+
'bpmn.data-store': bpmnDataStoreIcon,
|
|
559
|
+
'bpmn.text-annotation': bpmnTextAnnotationIcon,
|
|
560
|
+
'bpmn.group': bpmnGroupIcon,
|
|
75
561
|
'bpmn.pool': bpmnPoolIcon,
|
|
562
|
+
'bpmn.lane-add': bpmnLaneAddIcon,
|
|
563
|
+
'bpmn.lane-remove': bpmnLaneRemoveIcon,
|
|
564
|
+
'bpmn.export-xml': bpmnExportXmlIcon,
|
|
565
|
+
'bpmn.import-xml': bpmnImportXmlIcon,
|
|
566
|
+
'bpmn.import-svg': bpmnImportSvgIcon,
|
|
76
567
|
};
|