@formicoidea/labre-framework-bpmn 0.41.0 → 0.43.1

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.
@@ -48,6 +48,40 @@ export declare const BPMN_XML_FORMAT: InterchangeFormat;
48
48
  * gives a centre inside two overlapping pools to the FIRST one, and the audit's
49
49
  * `attribute()` does the same. Sorting here would make the export disagree with
50
50
  * the badge the user can see.
51
+ *
52
+ * ## The filter is the ROLE, not the class
53
+ *
54
+ * A `BpmnNodeElementModel` is a shape with a `kind`; the ROLE is the author's
55
+ * statement that this circle IS the start of the process (`docs/adr/0010`, and
56
+ * C4's `isC4Node` before it). The distinction became load-bearing the day the
57
+ * pool grew a legend: a legend swatch is a real `bpmnNode` with a real `kind`
58
+ * and deliberately NO role, so a class-based filter would have written eighteen
59
+ * ghost `<task>` / `<startEvent>` / `<dataObject>` elements into the
60
+ * participant's process and broken the bpmn.io round trip the pack is for. The
61
+ * connectors have always been filtered this way (`EDGE_ELEMENT[connector.role]`
62
+ * in `export.ts`); this is the nodes and the pools catching up.
63
+ *
64
+ * ## What it costs, and the PO took that cost
65
+ *
66
+ * The pack shipped on 2026-06-13 and roles on 2026-08-26, with nothing
67
+ * backfilled (`roles.ts` says so in as many words). A process drawn between
68
+ * those two dates carries no role on anything and therefore no longer exports.
69
+ * Arbitrated on 2026-09-17: the alternative is an interchange file that cannot
70
+ * be trusted, which is worse than one that is not written.
71
+ *
72
+ * ## …and what it now SAYS
73
+ *
74
+ * The same ruling of 2026-09-17 added the other half: everything inside a
75
+ * pool's perimeter leaves in the board's generic SVG export (ADR 0025, R34),
76
+ * and only the roled artefacts leave in the `.bpmn`. That asymmetry is wanted;
77
+ * being silent about it was not. So the role-less things drawn INSIDE a pool
78
+ * are counted here — this is the only place that sees what was not picked —
79
+ * and `exportBpmnXmlWithWarnings` turns the count into one warning.
80
+ *
81
+ * Connectors are deliberately NOT counted: a neutral arrow states nothing
82
+ * (`docs/adr/0010`), so there is nothing to lose, which is the reason
83
+ * `export.ts` has always dropped one in silence. Neither is a generated legend
84
+ * — see {@link legendMemberIds}.
51
85
  */
52
86
  export declare function bpmnBoardFrom(elements: readonly GfxPrimitiveElementModel[]): BpmnExportBoard;
53
87
  /**
@@ -1,8 +1,10 @@
1
- import { interchangeCapabilityId, parseSvgSketch, SVG_SKETCH_EXTENSION, SVG_SKETCH_FORMAT_ID, SVG_SKETCH_MIME, } from '@formicoidea/labre-core/blocks/surface';
1
+ import { interchangeCapabilityId, LEGEND_ROLE, parseSvgSketch, SVG_SKETCH_EXTENSION, SVG_SKETCH_FORMAT_ID, SVG_SKETCH_MIME, } from '@formicoidea/labre-core/blocks/surface';
2
2
  import { BpmnNodeElementModel, BpmnPoolElementModel, ConnectorElementModel, } from '@formicoidea/labre-core/model';
3
3
  import { fillPlaceholders } from '@formicoidea/labre-core/shared/services';
4
4
  import { BPMN_FORMAT_ID, exportBpmnXmlWithWarnings, } from './export.js';
5
+ import { bpmnPoolOf } from './facts.js';
5
6
  import { importBpmnXml } from './import.js';
7
+ import { BPMN_ROLE, BPMN_ROLE_OF_KIND } from './roles.js';
6
8
  /**
7
9
  * BPMN's entries in the interchange registry (`docs/adr/0012`, P1).
8
10
  *
@@ -49,6 +51,41 @@ export const BPMN_XML_FORMAT = {
49
51
  mime: BPMN_XML_MIME,
50
52
  };
51
53
  /* ── Pure board helpers ───────────────────────────────────────────────── */
54
+ /**
55
+ * Everything a generated legend is made of: the group the legend gesture stamps
56
+ * with {@link LEGEND_ROLE}, and everything under it.
57
+ *
58
+ * A legend is drawn INSIDE the board it documents (`createBoardLegend`) and is
59
+ * made, on purpose, of role-less shapes, texts and swatches — so without this
60
+ * it would be reported as fourteen things the `.bpmn` left out, which is
61
+ * exactly the surprise that warning exists to prevent. The group's ROLE is what
62
+ * it is read by: nothing here matches a title, a framework name or a box.
63
+ *
64
+ * Read defensively, like every other walk over stored data in this framework:
65
+ * the children came out of a Y.Map and are whatever a peer wrote.
66
+ */
67
+ function legendMemberIds(elements) {
68
+ const byId = new Map(elements.map(element => [element.id, element]));
69
+ const members = new Set();
70
+ const walk = (element) => {
71
+ if (members.has(element.id))
72
+ return;
73
+ members.add(element.id);
74
+ const children = element.childIds;
75
+ if (!Array.isArray(children))
76
+ return;
77
+ for (const id of children) {
78
+ const child = typeof id === 'string' ? byId.get(id) : undefined;
79
+ if (child)
80
+ walk(child);
81
+ }
82
+ };
83
+ for (const element of elements) {
84
+ if (element.role === LEGEND_ROLE)
85
+ walk(element);
86
+ }
87
+ return members;
88
+ }
52
89
  /**
53
90
  * The artefacts the exporter speaks about, picked out of a surface's elements
54
91
  * and kept in the order they were given.
@@ -57,20 +94,75 @@ export const BPMN_XML_FORMAT = {
57
94
  * gives a centre inside two overlapping pools to the FIRST one, and the audit's
58
95
  * `attribute()` does the same. Sorting here would make the export disagree with
59
96
  * the badge the user can see.
97
+ *
98
+ * ## The filter is the ROLE, not the class
99
+ *
100
+ * A `BpmnNodeElementModel` is a shape with a `kind`; the ROLE is the author's
101
+ * statement that this circle IS the start of the process (`docs/adr/0010`, and
102
+ * C4's `isC4Node` before it). The distinction became load-bearing the day the
103
+ * pool grew a legend: a legend swatch is a real `bpmnNode` with a real `kind`
104
+ * and deliberately NO role, so a class-based filter would have written eighteen
105
+ * ghost `<task>` / `<startEvent>` / `<dataObject>` elements into the
106
+ * participant's process and broken the bpmn.io round trip the pack is for. The
107
+ * connectors have always been filtered this way (`EDGE_ELEMENT[connector.role]`
108
+ * in `export.ts`); this is the nodes and the pools catching up.
109
+ *
110
+ * ## What it costs, and the PO took that cost
111
+ *
112
+ * The pack shipped on 2026-06-13 and roles on 2026-08-26, with nothing
113
+ * backfilled (`roles.ts` says so in as many words). A process drawn between
114
+ * those two dates carries no role on anything and therefore no longer exports.
115
+ * Arbitrated on 2026-09-17: the alternative is an interchange file that cannot
116
+ * be trusted, which is worse than one that is not written.
117
+ *
118
+ * ## …and what it now SAYS
119
+ *
120
+ * The same ruling of 2026-09-17 added the other half: everything inside a
121
+ * pool's perimeter leaves in the board's generic SVG export (ADR 0025, R34),
122
+ * and only the roled artefacts leave in the `.bpmn`. That asymmetry is wanted;
123
+ * being silent about it was not. So the role-less things drawn INSIDE a pool
124
+ * are counted here — this is the only place that sees what was not picked —
125
+ * and `exportBpmnXmlWithWarnings` turns the count into one warning.
126
+ *
127
+ * Connectors are deliberately NOT counted: a neutral arrow states nothing
128
+ * (`docs/adr/0010`), so there is nothing to lose, which is the reason
129
+ * `export.ts` has always dropped one in silence. Neither is a generated legend
130
+ * — see {@link legendMemberIds}.
60
131
  */
61
132
  export function bpmnBoardFrom(elements) {
62
133
  const pools = [];
63
134
  const nodes = [];
64
135
  const connectors = [];
136
+ const roleless = [];
65
137
  for (const element of elements) {
66
- if (element instanceof BpmnPoolElementModel)
67
- pools.push(element);
68
- else if (element instanceof BpmnNodeElementModel)
69
- nodes.push(element);
70
- else if (element instanceof ConnectorElementModel)
138
+ if (element instanceof BpmnPoolElementModel) {
139
+ if (element.role === BPMN_ROLE.pool)
140
+ pools.push(element);
141
+ else
142
+ roleless.push(element);
143
+ }
144
+ else if (element instanceof BpmnNodeElementModel) {
145
+ if (element.role === BPMN_ROLE_OF_KIND[element.kind])
146
+ nodes.push(element);
147
+ else
148
+ roleless.push(element);
149
+ }
150
+ else if (element instanceof ConnectorElementModel) {
71
151
  connectors.push(element);
152
+ }
153
+ else {
154
+ roleless.push(element);
155
+ }
72
156
  }
73
- return { pools, nodes, connectors };
157
+ // Against the pools that are actually WRITTEN: "inside the pool" has to mean
158
+ // inside a pool the file has, or the sentence names a perimeter the reader of
159
+ // the `.bpmn` cannot see. `bpmnPoolOf` is the same whole-containment test the
160
+ // audit and the serializer use, so what is reported left out is what the rest
161
+ // of the framework agrees is in there.
162
+ const legend = legendMemberIds(elements);
163
+ const leftOut = roleless.filter(element => !legend.has(element.id) &&
164
+ bpmnPoolOf(pools, element.elementBound) !== null).length;
165
+ return { pools, nodes, connectors, ...(leftOut > 0 ? { leftOut } : {}) };
74
166
  }
75
167
  /**
76
168
  * A name a file system will accept, minus the extension.
@@ -12,7 +12,6 @@ import { DefaultTheme, } from '@formicoidea/labre-core/model';
12
12
  *
13
13
  * - events — envelope (message), clock (timer), solid disc (terminate);
14
14
  * - tasks — a person (user) or a gear (service) in the top-left corner;
15
- * - activity — the `+` box at the bottom edge (sub-process, call activity);
16
15
  * - gateway — the X (exclusive) or the `+` (parallel);
17
16
  * - data — folded page, cylinder, open bracket.
18
17
  *
@@ -34,6 +33,13 @@ import { DefaultTheme, } from '@formicoidea/labre-core/model';
34
33
  * two and the one that is legible zoomed out.
35
34
  * - The **timer** has no hour ticks and the **data store** no shelf lines: at
36
35
  * the sizes this canvas draws them, both read as noise around the shape.
36
+ * - The **collapsed `[+]`** of the sub-process and the call activity is not
37
+ * drawn at all. In bpmn.io that boxed plus is a BUTTON — it unfolds the
38
+ * process the box stands for. Labre has no such gesture: a process defined
39
+ * elsewhere is a linked document, which carries a symbol of its own. Drawn
40
+ * here the marker would promise an action nothing performs, so the two kinds
41
+ * are bare and the call activity is told apart by its thick border, a
42
+ * creation-time preset (`CALL_ACTIVITY_WIDTH` in `consts.ts`).
37
43
  *
38
44
  * Mirrors the EDGY node renderer.
39
45
  */
@@ -41,13 +47,19 @@ import { DefaultTheme, } from '@formicoidea/labre-core/model';
41
47
  * The artefacts BPMN draws BARE — a plain native shape with nothing on it.
42
48
  * Everything else in the union is decorated here.
43
49
  *
44
- * `group` is bare for a different reason from the other three. They are
45
- * undecorated because the notation puts no marker on them; the group has a
46
- * distinctive look — a dashed, rounded, unfilled rectangle — and it is here
47
- * because that look is expressible as a native shape's own properties
48
- * (`strokeStyle: dash`, `radius`, `filled: false`). Drawing it by hand would
49
- * have meant re-implementing dashes the shape renderer already does, and losing
50
- * the editability that comes free with them.
50
+ * Three of them are here because the notation puts no marker on them at all.
51
+ * The other three are here for reasons of their own:
52
+ *
53
+ * - `group` has a distinctive look — a dashed, rounded, unfilled rectangle —
54
+ * and it is bare because that look is expressible as a native shape's own
55
+ * properties (`strokeStyle: dash`, `radius`, `filled: false`). Drawing it by
56
+ * hand would have meant re-implementing dashes the shape renderer already
57
+ * does, and losing the editability that comes free with them.
58
+ * - `subProcess` and `callActivity` do carry a marker in the notation — the
59
+ * collapsed `[+]` — and Labre drops it on purpose, because here it opens
60
+ * nothing (see the note on simplifications above). What tells the call
61
+ * activity from the sub-process is its thick border, a creation-time preset
62
+ * rather than anything drawn here.
51
63
  *
52
64
  * Written as the short list rather than the long one, so that the glyph kinds
53
65
  * are DERIVED from the model's union instead of restated beside it: a kind
@@ -61,6 +73,8 @@ const UNDECORATED_KINDS = {
61
73
  startEvent: true,
62
74
  endEvent: true,
63
75
  task: true,
76
+ subProcess: true,
77
+ callActivity: true,
64
78
  group: true,
65
79
  };
66
80
  const isUndecorated = (kind) => Object.hasOwn(UNDECORATED_KINDS, kind);
@@ -204,30 +218,6 @@ export const bpmnNode = (model, ctx, matrix, renderer, rc, bound) => {
204
218
  ctx.stroke();
205
219
  return;
206
220
  }
207
- if (kind === 'subProcess' || kind === 'callActivity') {
208
- // The collapsed marker: a small boxed `+` on the bottom edge, saying
209
- // "there is a whole process folded up in here". The call activity carries
210
- // the SAME marker — what tells the two apart is its thick border, which is
211
- // a creation-time preset rather than anything drawn here.
212
- const side = unit * 0.2;
213
- const bx = cx;
214
- const by = h - unit * 0.1 - side / 2;
215
- const half = side / 2;
216
- const arm = side * 0.3;
217
- ctx.lineWidth = Math.max(1, unit * 0.028);
218
- ctx.beginPath();
219
- ctx.moveTo(bx - half, by - half);
220
- ctx.lineTo(bx + half, by - half);
221
- ctx.lineTo(bx + half, by + half);
222
- ctx.lineTo(bx - half, by + half);
223
- ctx.lineTo(bx - half, by - half);
224
- ctx.moveTo(bx - arm, by);
225
- ctx.lineTo(bx + arm, by);
226
- ctx.moveTo(bx, by - arm);
227
- ctx.lineTo(bx, by + arm);
228
- ctx.stroke();
229
- return;
230
- }
231
221
  // ── Data and artifacts: the glyph IS the body ────────────────────────
232
222
  // The native rect under these three is unfilled and unstroked, so both the
233
223
  // fill and the outline are drawn here — off the model's own colours, which
package/dist/presets.js CHANGED
@@ -29,9 +29,10 @@ export const NODE_PRESETS = {
29
29
  stroke: EVENT_END,
30
30
  width: END_WIDTH,
31
31
  },
32
- // Activities: the same rounded rectangle, and a marker tells them apart —
33
- // except the call activity, whose thick border IS the distinction (it carries
34
- // the same `+` as the sub-process).
32
+ // Activities: the same rounded rectangle, and a corner marker tells the two
33
+ // typed tasks apart. The sub-process wears nothing (its collapsed `+` opens
34
+ // nothing here, so it is not drawn) and the call activity's thick border IS
35
+ // its distinction.
35
36
  task: {
36
37
  shapeType: 'rect',
37
38
  stroke: NEUTRAL_STROKE,
@@ -4,7 +4,7 @@ import { type ChromeWording } from '@formicoidea/labre-core/shared/services';
4
4
  * The seeds the two worked scenes write — declared once so the scene builder
5
5
  * and the manifest ({@link ../translations.ts}) read the very same fallback,
6
6
  * never a restated literal. `com.labre.bpmn.example.<scene>.<slug>`, per
7
- * `docs/adr/0016`.
7
+ * `docs/adr/0023`.
8
8
  */
9
9
  export declare const SIMPLE_PROCESS_SEED: {
10
10
  readonly poolName: {
@@ -9,7 +9,7 @@ import { BPMN_ROLE } from '../roles.js';
9
9
  * The seeds the two worked scenes write — declared once so the scene builder
10
10
  * and the manifest ({@link ../translations.ts}) read the very same fallback,
11
11
  * never a restated literal. `com.labre.bpmn.example.<scene>.<slug>`, per
12
- * `docs/adr/0016`.
12
+ * `docs/adr/0023`.
13
13
  */
14
14
  export const SIMPLE_PROCESS_SEED = {
15
15
  poolName: {
@@ -211,8 +211,6 @@ const taskPreview = (glyph = '', width = 2.4) => `<svg ${A} fill="none"><rect x=
211
211
  const gatewayPreview = (marker) => `<svg ${A} fill="none"><path d="M67 16 L92 40 L67 64 L42 40 Z" stroke="${NEUTRAL_STROKE}" stroke-width="2.4" stroke-linejoin="round"/><path d="${marker}" stroke="${NEUTRAL_STROKE}" stroke-width="2.2" stroke-linecap="round"/></svg>`;
212
212
  const ENVELOPE = `<rect x="59" y="35" width="16" height="11" stroke="${NEUTRAL_STROKE}" stroke-width="1.4"/><path d="M59 35 L67 42 L75 35" stroke="${NEUTRAL_STROKE}" stroke-width="1.4" stroke-linejoin="round"/>`;
213
213
  const CLOCK = `<circle cx="67" cy="40" r="9" stroke="${NEUTRAL_STROKE}" stroke-width="1.4"/><path d="M67 40 V33 M67 40 L72 43" stroke="${NEUTRAL_STROKE}" stroke-width="1.4" stroke-linecap="round"/>`;
214
- /** The collapsed `[+]`, on the activity's bottom edge — sub-process and call alike. */
215
- const COLLAPSED = `<rect x="61" y="44" width="12" height="12" stroke="${NEUTRAL_STROKE}" stroke-width="1.4"/><path d="M67 47 V53 M64 50 H70" stroke="${NEUTRAL_STROKE}" stroke-width="1.4" stroke-linecap="round"/>`;
216
214
  const previews = {
217
215
  process: `<svg ${A} fill="none"><circle cx="16" cy="40" r="8" stroke="${EVENT_START}" stroke-width="2"/><rect x="34" y="31" width="26" height="18" rx="3" stroke="${NEUTRAL_STROKE}" stroke-width="1.6"/><path d="M78 31 L88 40 L78 49 L68 40 Z" stroke="${NEUTRAL_STROKE}" stroke-width="1.4"/><path d="M73 37 L83 43 M83 37 L73 43" stroke="${NEUTRAL_STROKE}" stroke-width="1.2"/><circle cx="118" cy="40" r="8" stroke="${EVENT_END}" stroke-width="3"/><path d="M24 40 H34 M60 40 H68 M88 40 H110" stroke="${SEQUENCE_STROKE}" stroke-width="1.2"/></svg>`,
218
216
  startEvent: eventPreview(EVENT_START, 3),
@@ -224,10 +222,13 @@ const previews = {
224
222
  task: taskPreview(),
225
223
  taskUser: taskPreview(`<circle cx="41" cy="31" r="2.6" stroke="${NEUTRAL_STROKE}" stroke-width="1.2"/><path d="M37.4 38 a3.6 3.6 0 0 1 7.2 0" stroke="${NEUTRAL_STROKE}" stroke-width="1.2"/>`),
226
224
  taskService: taskPreview(`<circle cx="41" cy="32" r="3.4" stroke="${NEUTRAL_STROKE}" stroke-width="1.2"/><circle cx="41" cy="32" r="1" fill="${NEUTRAL_STROKE}"/><path d="M41 27.4 V29 M41 35 V36.6 M36.4 32 H38 M44 32 H45.6" stroke="${NEUTRAL_STROKE}" stroke-width="1.2" stroke-linecap="round"/>`),
227
- subProcess: taskPreview(COLLAPSED),
225
+ // Neither carries the notation's collapsed `[+]`: it opens nothing in Labre
226
+ // and the board does not paint it, so a card that did would be advertising a
227
+ // drawing the command never makes.
228
+ subProcess: taskPreview(),
228
229
  // The thick border IS the distinction: this box stands for a process defined
229
230
  // elsewhere (`presets.ts`, `CALL_ACTIVITY_WIDTH`).
230
- callActivity: taskPreview(COLLAPSED, 4.5),
231
+ callActivity: taskPreview('', 4.5),
231
232
  gateway: gatewayPreview('M58 31 L76 49 M76 31 L58 49'),
232
233
  gatewayParallel: gatewayPreview('M67 30 V50 M57 40 H77'),
233
234
  dataObject: `<svg ${A} fill="none"><path d="M52 14 H76 L88 26 V66 H52 Z" stroke="${NEUTRAL_STROKE}" stroke-width="2.2" stroke-linejoin="round"/><path d="M76 14 V26 H88" stroke="${NEUTRAL_STROKE}" stroke-width="1.8" stroke-linejoin="round"/></svg>`,
@@ -250,7 +251,7 @@ const previews = {
250
251
  * `build` takes the OPTIONAL inserting editor: called with none, at module
251
252
  * load, for the English `content`; called again with the real `std` as
252
253
  * {@link Template.localize}, so the two builds read the very same layout and
253
- * differ only in the words a seed resolves to (`docs/adr/0016`).
254
+ * differ only in the words a seed resolves to (`docs/adr/0023`).
254
255
  */
255
256
  const scene = (name, preview, build, nameKey) => ({
256
257
  name,
@@ -10,6 +10,8 @@ export declare class EdgelessBpmnSeniorButton extends EdgelessBpmnSeniorButton_b
10
10
  enableActiveBackground: boolean;
11
11
  type: typeof EmptyTool;
12
12
  private _toggleMenu;
13
+ /** Lit while one of THIS framework's artefacts is armed. See the Wardley one. */
14
+ private get _armed();
13
15
  render(): import("lit-html").TemplateResult<1>;
14
16
  }
15
17
  export {};
@@ -1,7 +1,7 @@
1
1
  import { DefaultTool } from '@formicoidea/labre-core/blocks/surface';
2
2
  import { EmptyTool } from '@formicoidea/labre-core/gfx/pointer';
3
3
  import { translateKey } from '@formicoidea/labre-core/shared/services';
4
- import { EdgelessToolbarToolMixin } from '@formicoidea/labre-core/widgets/edgeless-toolbar';
4
+ import { armedArtefact, EdgelessToolbarToolMixin, } from '@formicoidea/labre-core/widgets/edgeless-toolbar';
5
5
  import { SignalWatcher } from '@formicoidea/labre-core/global/lit';
6
6
  import { css, html, LitElement } from 'lit';
7
7
  import { bpmnToolbarIcon } from './icons.js';
@@ -72,6 +72,10 @@ export class EdgelessBpmnSeniorButton extends EdgelessToolbarToolMixin(SignalWat
72
72
  const menu = this.createPopper('edgeless-bpmn-menu', this);
73
73
  menu.element.edgeless = this.edgeless;
74
74
  }
75
+ /** Lit while one of THIS framework's artefacts is armed. See the Wardley one. */
76
+ get _armed() {
77
+ return armedArtefact(this.gfx)?.owner === 'bpmn';
78
+ }
75
79
  render() {
76
80
  return html `<edgeless-toolbar-button
77
81
  class="bpmn-button"
@@ -79,7 +83,7 @@ export class EdgelessBpmnSeniorButton extends EdgelessToolbarToolMixin(SignalWat
79
83
  ? ''
80
84
  : translateKey(this.edgeless.std, 'com.labre.framework.bpmn', 'BPMN')}
81
85
  .tooltipOffset=${4}
82
- .active=${!!this.popper}
86
+ .active=${!!this.popper || this._armed}
83
87
  @click=${this._toggleMenu}
84
88
  >
85
89
  <div class="bpmn-root">
@@ -1,4 +1,4 @@
1
- import { ActionPlacement, type ChromeWording, type ToolbarContext } from '@formicoidea/labre-core/shared/services';
1
+ import { type ChromeWording, type ToolbarContext, type ToolbarModuleConfig } from '@formicoidea/labre-core/shared/services';
2
2
  import { type TemplateResult } from 'lit';
3
3
  export declare const bpmnPoolToolbarConfig: {
4
4
  readonly actions: [{
@@ -24,16 +24,27 @@ export declare const bpmnPoolToolbarConfig: {
24
24
  tooltip: string;
25
25
  run: (runCtx: ToolbarContext) => void;
26
26
  };
27
- }, {
28
- id: string;
29
- placement: ActionPlacement;
30
- when: (ctx: ToolbarContext) => boolean;
31
- generate: (ctx: ToolbarContext) => {
32
- icon: TemplateResult;
33
- label: string;
34
- run: (runCtx: ToolbarContext) => void;
35
- };
36
- }];
27
+ }, import("@formicoidea/labre-core/shared/services").ToolbarActionGenerator];
37
28
  readonly when: (ctx: ToolbarContext) => boolean;
38
29
  };
39
30
  export declare const bpmnPoolToolbarExtension: import("@formicoidea/labre-core/store/index.js").ExtensionType;
31
+ /**
32
+ * The pool's FLAG-GATED row: the legend and the validation dropdown, in one
33
+ * module because a flavour may carry exactly one (a second on the same key
34
+ * throws `DuplicateServiceDefinitionError` before the editor finishes setting
35
+ * up — the same merge C4's board makes for the same reason).
36
+ *
37
+ * `c.legend` and not `b.legend`: the toolbar sorts lexicographically by id
38
+ * after partitioning by placement, and `'b.add-lane' < 'b.legend' <
39
+ * 'b.remove-lane'` would wedge the button between the two lane gestures. `c.`
40
+ * puts it where it belongs — resize · add lane · remove lane · **Legend** ·
41
+ * Validation, with Export XML in the "⋮".
42
+ *
43
+ * Gated, because GENERATING a legend is tooling (ADR 0026, amending 0009). What
44
+ * the gesture already wrote is document content and keeps painting with the
45
+ * flag off: the box is plain shapes and text plus `bpmnNode`s the always-on
46
+ * renderer paints, and the pool keeps its resize handle and its lanes either
47
+ * way.
48
+ */
49
+ export declare const bpmnPoolToolingToolbarConfig: ToolbarModuleConfig;
50
+ export declare const bpmnPoolToolingToolbarExtension: import("@formicoidea/labre-core/store/index.js").ExtensionType;
@@ -1,6 +1,6 @@
1
- import { EdgelessCRUDIdentifier } from '@formicoidea/labre-core/blocks/surface';
1
+ import { EdgelessCRUDIdentifier, legendToolbarAction, validationToolbarConfig, } from '@formicoidea/labre-core/blocks/surface';
2
2
  import { BpmnPoolElementModel } from '@formicoidea/labre-core/model';
3
- import { ActionPlacement, BOARD_RESIZE_TOGGLE, ToolbarModuleExtension, translateKey, } from '@formicoidea/labre-core/shared/services';
3
+ import { BOARD_RESIZE_TOGGLE, commandMoreAction, ToolbarModuleExtension, translateKey, } from '@formicoidea/labre-core/shared/services';
4
4
  import { BlockFlavourIdentifier, getRegisteredCommands, runCommand, } from '@formicoidea/labre-core/std';
5
5
  import { html } from 'lit';
6
6
  import { bpmnExportXmlIcon, bpmnLaneAddIcon, bpmnLaneRemoveIcon, } from './icons.js';
@@ -84,43 +84,6 @@ function commandAction(id, commandId, labelKey, labelFallback, icon) {
84
84
  },
85
85
  };
86
86
  }
87
- /**
88
- * The same thing, for the "⋮" menu instead of the row.
89
- *
90
- * Two differences, and the widget imposes both: a menu line is drawn from
91
- * `label` (a tooltip on a line that is already words would be a second copy of
92
- * them), and `placement: ActionPlacement.More` is what partitions it out of the
93
- * row in the first place — `renderToolbar` splits on exactly that flag, and the
94
- * entries it collects come from every module contributing to the element,
95
- * so a framework can add to the "⋮" without owning it.
96
- */
97
- function commandMoreAction(id, commandId, labelKey, labelFallback, icon) {
98
- return {
99
- id,
100
- placement: ActionPlacement.More,
101
- when: (ctx) => {
102
- const command = findCommand(ctx, commandId);
103
- return command !== undefined && (command.when?.(ctx.std) ?? true);
104
- },
105
- generate: (ctx) => ({
106
- icon,
107
- label: translateKey(ctx.std, labelKey, labelFallback),
108
- run: (runCtx) => {
109
- const command = findCommand(runCtx, commandId);
110
- if (!command)
111
- return;
112
- // The same `source` the row's own entries report: the "⋮" is a
113
- // degradation of the row, not a surface of its own, and the
114
- // `ElementCreationSource` union deliberately names places rather than
115
- // widths (`docs/adr/0008`).
116
- runCommand(runCtx.std, command, {
117
- surface: 'contextual-toolbar',
118
- source: 'toolbar:general',
119
- });
120
- },
121
- }),
122
- };
123
- }
124
87
  const findCommand = (ctx, id) => getRegisteredCommands(ctx.std).find(candidate => candidate.id === id);
125
88
  export const bpmnPoolToolbarConfig = {
126
89
  actions: [
@@ -141,3 +104,40 @@ export const bpmnPoolToolbarExtension = ToolbarModuleExtension({
141
104
  id: BlockFlavourIdentifier('affine:surface:bpmnPool'),
142
105
  config: bpmnPoolToolbarConfig,
143
106
  });
107
+ /**
108
+ * The pool's FLAG-GATED row: the legend and the validation dropdown, in one
109
+ * module because a flavour may carry exactly one (a second on the same key
110
+ * throws `DuplicateServiceDefinitionError` before the editor finishes setting
111
+ * up — the same merge C4's board makes for the same reason).
112
+ *
113
+ * `c.legend` and not `b.legend`: the toolbar sorts lexicographically by id
114
+ * after partitioning by placement, and `'b.add-lane' < 'b.legend' <
115
+ * 'b.remove-lane'` would wedge the button between the two lane gestures. `c.`
116
+ * puts it where it belongs — resize · add lane · remove lane · **Legend** ·
117
+ * Validation, with Export XML in the "⋮".
118
+ *
119
+ * Gated, because GENERATING a legend is tooling (ADR 0026, amending 0009). What
120
+ * the gesture already wrote is document content and keeps painting with the
121
+ * flag off: the box is plain shapes and text plus `bpmnNode`s the always-on
122
+ * renderer paints, and the pool keeps its resize handle and its lanes either
123
+ * way.
124
+ */
125
+ export const bpmnPoolToolingToolbarConfig = {
126
+ actions: [
127
+ legendToolbarAction({
128
+ id: 'c.legend',
129
+ Model: BpmnPoolElementModel,
130
+ owner: 'bpmn',
131
+ framework: 'bpmn',
132
+ }),
133
+ // The generic dropdown, not a BPMN variant of it: the config names no
134
+ // framework — it reads the registered rules and profiles — so this is the
135
+ // very same object wardley, c4 and the context map register.
136
+ ...validationToolbarConfig.actions,
137
+ ],
138
+ when: bpmnPoolToolbarConfig.when,
139
+ };
140
+ export const bpmnPoolToolingToolbarExtension = ToolbarModuleExtension({
141
+ id: BlockFlavourIdentifier('custom:affine:surface:bpmnPool'),
142
+ config: bpmnPoolToolingToolbarConfig,
143
+ });
@@ -38,9 +38,16 @@ export declare const bpmnTaskIcon: import("lit-html").TemplateResult<2>;
38
38
  export declare const bpmnTaskUserIcon: import("lit-html").TemplateResult<2>;
39
39
  /** Service task — the rectangle with a gear in its top-left corner. */
40
40
  export declare const bpmnTaskServiceIcon: import("lit-html").TemplateResult<2>;
41
- /** Sub-process — the rectangle with the collapsed [+] marker on its bottom edge. */
41
+ /**
42
+ * Sub-process — the plain rectangle, like the task it is drawn as.
43
+ *
44
+ * The notation puts a collapsed [+] on its bottom edge and the canvas does not
45
+ * paint one (see `node/node-renderer.ts`): that box is a button everywhere else
46
+ * and opens nothing here. A palette glyph says what lands on the board, so it
47
+ * has none either, and the menu label is what tells this entry from the task.
48
+ */
42
49
  export declare const bpmnSubProcessIcon: import("lit-html").TemplateResult<2>;
43
- /** Call activity — the same [+] marker, on the notation's THICKEST border. */
50
+ /** Call activity — the same bare rectangle, on the notation's THICKEST border. */
44
51
  export declare const bpmnCallActivityIcon: import("lit-html").TemplateResult<2>;
45
52
  /** Exclusive gateway — diamond with an X. */
46
53
  export declare const bpmnGatewayIcon: import("lit-html").TemplateResult<2>;
@@ -87,17 +87,20 @@ export const bpmnTaskServiceIcon = svg `<svg width="24" height="24" viewBox="0 0
87
87
  <circle cx="7.6" cy="11" r="0.5" stroke="currentColor" stroke-width="0.8"/>
88
88
  <path d="M7.6 8.9 V9.3 M7.6 12.7 V13.1 M5.5 11 H5.9 M9.3 11 H9.7 M6.1 9.5 L6.4 9.8 M8.8 12.2 L9.1 12.5 M9.1 9.5 L8.8 9.8 M6.4 12.2 L6.1 12.5" stroke="currentColor" stroke-width="1" stroke-linecap="round"/>
89
89
  </svg>`;
90
- /** Sub-process — the rectangle with the collapsed [+] marker on its bottom edge. */
90
+ /**
91
+ * Sub-process — the plain rectangle, like the task it is drawn as.
92
+ *
93
+ * The notation puts a collapsed [+] on its bottom edge and the canvas does not
94
+ * paint one (see `node/node-renderer.ts`): that box is a button everywhere else
95
+ * and opens nothing here. A palette glyph says what lands on the board, so it
96
+ * has none either, and the menu label is what tells this entry from the task.
97
+ */
91
98
  export const bpmnSubProcessIcon = svg `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
92
99
  <rect x="3.5" y="6.5" width="17" height="11" rx="2.5" stroke="currentColor" stroke-width="1.6"/>
93
- <rect x="9.6" y="12.6" width="4.8" height="4.8" stroke="currentColor" stroke-width="1.1"/>
94
- <path d="M12 13.8 V16.2 M10.8 15 H13.2" stroke="currentColor" stroke-width="1.1" stroke-linecap="round"/>
95
100
  </svg>`;
96
- /** Call activity — the same [+] marker, on the notation's THICKEST border. */
101
+ /** Call activity — the same bare rectangle, on the notation's THICKEST border. */
97
102
  export const bpmnCallActivityIcon = svg `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
98
103
  <rect x="4" y="7" width="16" height="10" rx="2.5" stroke="currentColor" stroke-width="3"/>
99
- <rect x="9.6" y="12.2" width="4.8" height="4.8" stroke="currentColor" stroke-width="1.1"/>
100
- <path d="M12 13.4 V15.8 M10.8 14.6 H13.2" stroke="currentColor" stroke-width="1.1" stroke-linecap="round"/>
101
104
  </svg>`;
102
105
  /** Exclusive gateway — diamond with an X. */
103
106
  export const bpmnGatewayIcon = svg `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -0,0 +1,23 @@
1
+ import type { FrameworkPalette, NamedPalette } from '@formicoidea/labre-core/components/color-picker';
2
+ import type { ChromeWording } from '@formicoidea/labre-core/shared/services';
3
+ /**
4
+ * The two BPMN swatches' own names, resolved by `resolvePaletteLabel`
5
+ * (`@labre/affine-components/color-picker`) through each swatch's own
6
+ * {@link NamedPalette.labelWording} — the mechanism `WARDLEY_PALETTE_WORDINGS`
7
+ * established, reused here without either package naming the other.
8
+ *
9
+ * Exported so `../translations.ts` can list them under source `chrome`: a
10
+ * swatch's name is toolbar chrome, never seeded into a document.
11
+ */
12
+ export declare const BPMN_PALETTE_WORDING_START: ChromeWording;
13
+ export declare const BPMN_PALETTE_WORDING_END: ChromeWording;
14
+ export declare const BPMN_PALETTE_WORDINGS: readonly ChromeWording[];
15
+ /** The two notation hues, then the neutrals every drawing needs. */
16
+ export declare const BPMN_PALETTE_LIST: NamedPalette[];
17
+ /**
18
+ * BPMN's page of the colour pickers' carousel (`docs/adr/0027`). Registered
19
+ * from the FLAG-GATED view extension, because offering hues is TOOLING: a
20
+ * process drawn while the flag was on keeps every colour it was painted with
21
+ * when the flag goes off (`docs/adr/0009`).
22
+ */
23
+ export declare const BPMN_FRAMEWORK_PALETTE: FrameworkPalette;