@formicoidea/labre-framework-bpmn 0.42.0 → 0.45.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.
@@ -1,13 +1,39 @@
1
1
  import { mountShapeTextEditor } from '@formicoidea/labre-core/gfx/shape';
2
2
  import { ShapeElementModel, } from '@formicoidea/labre-core/model';
3
3
  import { GfxElementModelView } from '@formicoidea/labre-core/std/gfx';
4
+ import { bpmnLabelMode } from '../consts.js';
5
+ /**
6
+ * Whether a double-click on this node opens the shape's own inner-text editor.
7
+ *
8
+ * An INSCRIBED kind (activities, annotation, group) wears its name inside the
9
+ * shape, so the gesture lands there, exactly like a native shape.
10
+ *
11
+ * An EXTERNAL kind (events, gateways, data shapes) wears its name as a separate
12
+ * `bpmn:label` text grouped under it (R38), which is edited by double-clicking
13
+ * that text — `TextElementView`'s job, not this one's. The symbol itself has
14
+ * nothing to open, the same answer Wardley's node view gives its circles: an
15
+ * editor mounted here would write a second, invisible name into a field that
16
+ * must stay empty. No redirect to the label either: Wardley does not redirect,
17
+ * and the label is one click away inside the group.
18
+ *
19
+ * The one exception is a LEGACY node: an event drawn before R38 may carry inner
20
+ * text (nothing is migrated, `BPMN_EXTERNAL_LABEL_KINDS`). That text is still
21
+ * painted, so it must still be editable — hence "already has text" reopens
22
+ * the editor, and emptying it closes the door behind it.
23
+ */
24
+ export function bpmnNodeEditsInnerText(model) {
25
+ if (bpmnLabelMode(model.kind) === 'inscribed')
26
+ return true;
27
+ return (model.text?.length ?? 0) > 0;
28
+ }
4
29
  /**
5
30
  * View for a BPMN flow-object node. Registering it ensures `gfx.view.get(model)`
6
31
  * returns a view (required so move / select / connector interactions work).
7
32
  *
8
- * BPMN nodes are native shapes, so we reuse the shape inner-text editor: a
9
- * double-click mounts the editable text overlay (`mountShapeTextEditor`),
10
- * exactly like a native shape (used to label the task).
33
+ * BPMN nodes are native shapes, so an inscribed kind reuses the shape
34
+ * inner-text editor: a double-click mounts the editable text overlay
35
+ * (`mountShapeTextEditor`), exactly like a native shape. Which kinds do is
36
+ * {@link bpmnNodeEditsInnerText}'s call.
11
37
  *
12
38
  * Mirrors {@link EdgyNodeView}.
13
39
  */
@@ -19,7 +45,8 @@ export class BpmnNodeView extends GfxElementModelView {
19
45
  const edgeless = this.std.view.getBlock(this.std.store.root.id);
20
46
  if (edgeless &&
21
47
  !this.model.isLocked() &&
22
- this.model instanceof ShapeElementModel) {
48
+ this.model instanceof ShapeElementModel &&
49
+ bpmnNodeEditsInnerText(this.model)) {
23
50
  mountShapeTextEditor(this.model, edgeless);
24
51
  }
25
52
  });
package/dist/presets.d.ts CHANGED
@@ -67,7 +67,9 @@ export interface BpmnLabelFit {
67
67
  *
68
68
  * Every value the creation builder writes is calibrated against
69
69
  * {@link NODE_SIZE}: an 18-unit font and the shape's native 20-unit horizontal
70
- * inset are comfortable in a 120-unit task, which is what the palette draws.
70
+ * inset are comfortable in a 120-unit task — the box the palette drew when
71
+ * this was written, kept as the activities' fit reference
72
+ * (`ACTIVITY_FIT_REF`) now that it draws 180×108.
71
73
  * A file draws to its author's scale, and bpmn.io's normative sizes — the ones
72
74
  * nearly every `.bpmn` in the wild carries — are a 100×80 task and a 36-unit
73
75
  * event. In a 100-unit task the native inset leaves 60 units of line, which is
@@ -124,6 +126,35 @@ export declare function bpmnNodeProps(kind: BpmnNodeKind, box: {
124
126
  }): Record<string, unknown> & {
125
127
  type: string;
126
128
  };
129
+ /**
130
+ * Where an external label's box goes for a symbol centred on `(cx, cy)`:
131
+ * centred on the symbol's vertical axis, {@link BPMN_LABEL_GAP} under its
132
+ * bottom edge — the placement BPMN's own cartography uses for an event's, a
133
+ * gateway's or a data shape's name.
134
+ *
135
+ * Reads the kind's canonical size, so the creation gesture and a template scene
136
+ * agree; an importer that keeps a file's own box passes that box's bottom
137
+ * through {@link bpmnLabelBoxUnder} instead.
138
+ */
139
+ export declare function bpmnLabelBoxFor(kind: BpmnNodeKind, cx: number, cy: number): {
140
+ x: number;
141
+ y: number;
142
+ };
143
+ /** The label box for a symbol whose vertical axis is `cx` and bottom edge `bottom`. */
144
+ export declare function bpmnLabelBoxUnder(cx: number, bottom: number): {
145
+ x: number;
146
+ y: number;
147
+ };
148
+ /**
149
+ * A gravitating label, as props — a free text element in the pack's own type,
150
+ * centred, carrying the role that makes it a NAME rather than a note.
151
+ *
152
+ * Mirrors `wardleyLabelProps`; one builder so the palette, the scenes and the
153
+ * importer write the same element.
154
+ */
155
+ export declare function bpmnLabelProps(text: string, x: number, y: number): Record<string, unknown> & {
156
+ type: string;
157
+ };
127
158
  /**
128
159
  * One BPMN pool, as the props `surface.addElement` takes.
129
160
  *
package/dist/presets.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { FontFamily, SHAPE_TEXT_PADDING, SHAPE_TEXT_VERTICAL_PADDING, ShapeStyle, StrokeStyle, TextAlign, TextFitMode, TextVerticalAlign, } from '@formicoidea/labre-core/model';
2
2
  import { Bound } from '@formicoidea/labre-core/global/gfx';
3
- import { CALL_ACTIVITY_WIDTH, END_WIDTH, EVENT_END, EVENT_START, GROUP_RADIUS, GROUP_STROKE, INNER_FONT_SIZE, LABEL_INSET_RATIO, LABEL_MIN_FONT_SIZE, NEUTRAL_STROKE, NODE_FILL, NODE_SIZE, NODE_STROKE_WIDTH, START_WIDTH, TASK_RADIUS, } from './consts.js';
3
+ import { ACTIVITY_FIT_REF, BPMN_LABEL_GAP, BPMN_LABEL_H, BPMN_LABEL_W, CALL_ACTIVITY_WIDTH, END_WIDTH, EVENT_END, EVENT_START, GROUP_RADIUS, GROUP_STROKE, INNER_FONT_SIZE, isBpmnActivityKind, LABEL_FONT_SIZE, LABEL_INSET_RATIO, LABEL_MIN_FONT_SIZE, NEUTRAL_STROKE, NODE_FILL, NODE_SIZE, NODE_STROKE_WIDTH, START_WIDTH, TASK_RADIUS, } from './consts.js';
4
4
  import { BPMN_ROLE, BPMN_ROLE_OF_KIND } from './roles.js';
5
5
  /** Per-kind native shape + accent presets (style C). */
6
6
  export const NODE_PRESETS = {
@@ -141,7 +141,9 @@ const insetOf = (extent, native) => Number.isFinite(extent) && extent > 0
141
141
  *
142
142
  * Every value the creation builder writes is calibrated against
143
143
  * {@link NODE_SIZE}: an 18-unit font and the shape's native 20-unit horizontal
144
- * inset are comfortable in a 120-unit task, which is what the palette draws.
144
+ * inset are comfortable in a 120-unit task — the box the palette drew when
145
+ * this was written, kept as the activities' fit reference
146
+ * (`ACTIVITY_FIT_REF`) now that it draws 180×108.
145
147
  * A file draws to its author's scale, and bpmn.io's normative sizes — the ones
146
148
  * nearly every `.bpmn` in the wild carries — are a 100×80 task and a 36-unit
147
149
  * event. In a 100-unit task the native inset leaves 60 units of line, which is
@@ -171,7 +173,9 @@ const insetOf = (extent, native) => Number.isFinite(extent) && extent > 0
171
173
  * box was never going to hold.
172
174
  */
173
175
  export function bpmnLabelFit(kind, w, h) {
174
- const size = NODE_SIZE[kind];
176
+ // An activity is fitted against the box its type was calibrated in, not the
177
+ // box the palette now draws (see `ACTIVITY_FIT_REF`).
178
+ const size = isBpmnActivityKind(kind) ? ACTIVITY_FIT_REF : NODE_SIZE[kind];
175
179
  const scale = Math.min(1, ratioOf(w, size.w), ratioOf(h, size.h));
176
180
  return {
177
181
  fontSize: Math.max(LABEL_MIN_FONT_SIZE, Math.round(INNER_FONT_SIZE * scale)),
@@ -256,6 +260,43 @@ export function bpmnNodeProps(kind, box) {
256
260
  xywh: box.xywh,
257
261
  };
258
262
  }
263
+ /* ── The gravitating label (R38) ───────────────────────────────────────── */
264
+ /**
265
+ * Where an external label's box goes for a symbol centred on `(cx, cy)`:
266
+ * centred on the symbol's vertical axis, {@link BPMN_LABEL_GAP} under its
267
+ * bottom edge — the placement BPMN's own cartography uses for an event's, a
268
+ * gateway's or a data shape's name.
269
+ *
270
+ * Reads the kind's canonical size, so the creation gesture and a template scene
271
+ * agree; an importer that keeps a file's own box passes that box's bottom
272
+ * through {@link bpmnLabelBoxUnder} instead.
273
+ */
274
+ export function bpmnLabelBoxFor(kind, cx, cy) {
275
+ return bpmnLabelBoxUnder(cx, cy + NODE_SIZE[kind].h / 2);
276
+ }
277
+ /** The label box for a symbol whose vertical axis is `cx` and bottom edge `bottom`. */
278
+ export function bpmnLabelBoxUnder(cx, bottom) {
279
+ return { x: cx - BPMN_LABEL_W / 2, y: bottom + BPMN_LABEL_GAP };
280
+ }
281
+ /**
282
+ * A gravitating label, as props — a free text element in the pack's own type,
283
+ * centred, carrying the role that makes it a NAME rather than a note.
284
+ *
285
+ * Mirrors `wardleyLabelProps`; one builder so the palette, the scenes and the
286
+ * importer write the same element.
287
+ */
288
+ export function bpmnLabelProps(text, x, y) {
289
+ return {
290
+ type: 'text',
291
+ text,
292
+ role: BPMN_ROLE.label,
293
+ fontFamily: FontFamily.Inter,
294
+ fontSize: LABEL_FONT_SIZE,
295
+ color: NEUTRAL_STROKE,
296
+ textAlign: TextAlign.Center,
297
+ xywh: new Bound(x, y, BPMN_LABEL_W, BPMN_LABEL_H).serialize(),
298
+ };
299
+ }
259
300
  /**
260
301
  * One BPMN pool, as the props `surface.addElement` takes.
261
302
  *
package/dist/reading.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { BPMN_POOL_BACKGROUND } from './background.js';
1
2
  import { BPMN_ROLE, BPMN_ROLES } from './roles.js';
2
3
  /**
3
4
  * What BPMN lets the tool read of an artefact (MF3, the reversed reading).
@@ -35,13 +36,44 @@ import { BPMN_ROLE, BPMN_ROLES } from './roles.js';
35
36
  * under the sequence flow's two words would put a sentence in the panel that
36
37
  * the notation never said.
37
38
  *
38
- * ## No nature, no phase
39
+ * ## A name inscribed, or a name beside
40
+ *
41
+ * Since R38 (`docs/adr/0029`) an event, a gateway and a data shape carry their
42
+ * name on a free `bpmn:label` text grouped with them, not as inner text. The
43
+ * two profiles that read them therefore declare `labelRole`, which the engine
44
+ * falls back to when the subject's own text is empty — Wardley's reading does
45
+ * the same. An activity's inscribed name still wins, because own text is read
46
+ * first; the annotation and the group ARE their text and need no fallback.
47
+ *
48
+ * ## No nature; a lane, not a phase
39
49
  *
40
50
  * BPMN ships no type-3 tag pack: its kinds ARE the roles, and the panel says
41
51
  * them on the type line, chain included ("Message start event › Start event ›
42
- * Event › Flow object"). And a pool's lanes are participants, not zones on a
43
- * plotted axis, so there is nothing a position could be read against yet.
52
+ * Event › Flow object").
53
+ *
54
+ * A pool's lanes are not an ordered axis, but they ARE where a flow object or a
55
+ * data shape is drawn: the lane its centre falls in, read in two dimensions (no
56
+ * `axis`) off the pool's own `lanes` and named by the name the user gave it —
57
+ * the same lane `bpmnLaneOf` and the audit report. The annotation and the group
58
+ * are left without a frame: commentary is about the picture, not in a lane, and
59
+ * §10.4 lets a group cross every lane there is.
60
+ */
61
+ /**
62
+ * The pool, read for its lanes. Shared by the two profiles that sit IN a lane,
63
+ * so a task and the data object beside it are placed by one declaration.
44
64
  */
65
+ const BPMN_LANE_FRAME = {
66
+ backgroundRole: BPMN_ROLE.pool,
67
+ background: BPMN_POOL_BACKGROUND,
68
+ label: {
69
+ labelKey: 'com.labre.bpmn.reading.field.lane',
70
+ labelFallback: 'Lane',
71
+ },
72
+ none: {
73
+ labelKey: 'com.labre.bpmn.reading.lane.none',
74
+ labelFallback: 'Not in a lane of a pool.',
75
+ },
76
+ };
45
77
  /**
46
78
  * The two words a sequence flow is read with, from the subject's end.
47
79
  *
@@ -65,7 +97,10 @@ export const BPMN_READING = {
65
97
  framework: 'bpmn',
66
98
  roles: BPMN_ROLES,
67
99
  appliesTo: BPMN_ROLE.flowObject,
100
+ // An event's or a gateway's name gravitates (R38): see the module comment.
101
+ labelRole: BPMN_ROLE.label,
68
102
  relation: { edgeRole: BPMN_ROLE.sequenceFlow, sides: SEQUENCE_SIDES },
103
+ frame: BPMN_LANE_FRAME,
69
104
  };
70
105
  /**
71
106
  * The paperwork: a data object and a data store.
@@ -78,6 +113,9 @@ export const BPMN_DATA_READING = {
78
113
  framework: 'bpmn',
79
114
  roles: BPMN_ROLES,
80
115
  appliesTo: BPMN_ROLE.data,
116
+ // Both data shapes are named beside the symbol (R38).
117
+ labelRole: BPMN_ROLE.label,
118
+ frame: BPMN_LANE_FRAME,
81
119
  };
82
120
  /** A note on the picture. Readable, so a reader can link it to a record. */
83
121
  export const BPMN_ANNOTATION_READING = {
package/dist/roles.d.ts CHANGED
@@ -51,7 +51,7 @@ import type { RoleDefs, RoleId } from '@formicoidea/labre-core/std/gfx';
51
51
  * same promise every role in this library has made (PRD principle 8).
52
52
  */
53
53
  /** Every role this framework declares. */
54
- export type BpmnRole = 'flow-object' | 'event' | 'start-event' | 'message-start-event' | 'timer-start-event' | 'end-event' | 'message-end-event' | 'terminate-end-event' | 'activity' | 'task' | 'user-task' | 'service-task' | 'sub-process' | 'call-activity' | 'gateway' | 'gateway-exclusive' | 'parallel-gateway' | 'data' | 'data-object' | 'data-store' | 'text-annotation' | 'group' | 'pool' | 'sequence-flow' | 'message-flow' | 'association';
54
+ export type BpmnRole = 'flow-object' | 'event' | 'start-event' | 'message-start-event' | 'timer-start-event' | 'end-event' | 'message-end-event' | 'terminate-end-event' | 'activity' | 'task' | 'user-task' | 'service-task' | 'sub-process' | 'call-activity' | 'gateway' | 'gateway-exclusive' | 'parallel-gateway' | 'data' | 'data-object' | 'data-store' | 'text-annotation' | 'group' | 'pool' | 'sequence-flow' | 'message-flow' | 'association' | 'label';
55
55
  export type BpmnRoleId = `bpmn:${BpmnRole}`;
56
56
  /** Role ids, keyed by the `kind` used at the creation sites. */
57
57
  export declare const BPMN_ROLE: {
@@ -81,6 +81,7 @@ export declare const BPMN_ROLE: {
81
81
  readonly sequenceFlow: "bpmn:sequence-flow";
82
82
  readonly messageFlow: "bpmn:message-flow";
83
83
  readonly association: "bpmn:association";
84
+ readonly label: "bpmn:label";
84
85
  };
85
86
  export declare const BPMN_ROLES: RoleDefs;
86
87
  /**
package/dist/roles.js CHANGED
@@ -40,6 +40,10 @@ export const BPMN_ROLE = {
40
40
  sequenceFlow: 'bpmn:sequence-flow',
41
41
  messageFlow: 'bpmn:message-flow',
42
42
  association: 'bpmn:association',
43
+ // The gravitating name: a free text grouped under an event, a gateway or a
44
+ // data shape. Same call as `wardley:label` — the role is the only thing that
45
+ // tells a name from a free text the author typed beside the process.
46
+ label: 'bpmn:label',
43
47
  };
44
48
  const _everyRoleIsMapped = true;
45
49
  void _everyRoleIsMapped;
@@ -369,12 +373,31 @@ const FLOW_DEFS = [
369
373
  labelFallback: 'Association',
370
374
  },
371
375
  ];
376
+ /**
377
+ * The gravitating label (R38): the text element grouped under an event, a
378
+ * gateway or a data shape, whose words are that artefact's NAME.
379
+ *
380
+ * Kind `text`, parent-less: it is not a flow object, not data and not an
381
+ * artifact, and no rule about the process may fall on the words that name a
382
+ * step. It exists so the export, the reading panel and the board picker can
383
+ * tell a name from a free text the author typed beside the process — exactly
384
+ * what `wardley:label` does for a component.
385
+ */
386
+ const LABEL_DEFS = [
387
+ {
388
+ id: BPMN_ROLE.label,
389
+ kind: 'text',
390
+ labelKey: roleKey(BPMN_ROLE.label),
391
+ labelFallback: 'Label',
392
+ },
393
+ ];
372
394
  const DEFS = [
373
395
  ...FLOW_OBJECT_DEFS,
374
396
  ...DATA_DEFS,
375
397
  ...ARTIFACT_DEFS,
376
398
  ...POOL_DEFS,
377
399
  ...FLOW_DEFS,
400
+ ...LABEL_DEFS,
378
401
  ];
379
402
  // Null prototype: this is a lookup table keyed by ids that may one day come
380
403
  // from host-supplied packs, so `defs['toString']` must not resolve.
package/dist/rules.d.ts CHANGED
@@ -147,7 +147,10 @@ import type { EndpointTriplet, ValidationRule } from '@formicoidea/labre-core/bl
147
147
  * not a bound that failed and its sentence never reads like one;
148
148
  * - `label-presence` reads the subject's OWN `text`. A framework whose artefacts
149
149
  * are named by a separate element beside them is asking a different question,
150
- * and this family cannot answer it. BPMN names its steps in place, so it can.
150
+ * and this family cannot answer it. BPMN names its ACTIVITIES in place, so it
151
+ * can ask it of them; since R38 its events, gateways and data shapes are
152
+ * named by a grouped `bpmn:label` instead, and are not asked (see
153
+ * {@link unlabeledStep}).
151
154
  */
152
155
  /**
153
156
  * `eitherMin` was also shipped, and this pack deliberately does not use it.
package/dist/rules.js CHANGED
@@ -148,7 +148,10 @@ import { BPMN_ROLE, BPMN_ROLES } from './roles.js';
148
148
  * not a bound that failed and its sentence never reads like one;
149
149
  * - `label-presence` reads the subject's OWN `text`. A framework whose artefacts
150
150
  * are named by a separate element beside them is asking a different question,
151
- * and this family cannot answer it. BPMN names its steps in place, so it can.
151
+ * and this family cannot answer it. BPMN names its ACTIVITIES in place, so it
152
+ * can ask it of them; since R38 its events, gateways and data shapes are
153
+ * named by a grouped `bpmn:label` instead, and are not asked (see
154
+ * {@link unlabeledStep}).
152
155
  */
153
156
  /**
154
157
  * `eitherMin` was also shipped, and this pack deliberately does not use it.
@@ -1452,23 +1455,36 @@ const singleBlankStart = {
1452
1455
  * **`audit`**, because an unnamed step is a diagram that is not done rather than
1453
1456
  * a diagram that is wrong, and the panel is where "not done" belongs.
1454
1457
  *
1455
- * Written on `bpmn:flow-object`: an unnamed gateway is worse than an unnamed
1456
- * task, since the whole content of a decision is the question it asks.
1457
- *
1458
- * The `label-presence` family reads the subject's OWN `text`, which is where
1459
- * BPMN puts the name of a step — so this rule can be asked at all.
1458
+ * Written on `bpmn:activity`, and only there. The `label-presence` family
1459
+ * reads the subject's OWN `text`, and since R38 (`docs/adr/0029`) only an
1460
+ * activity keeps its name there: an event, a gateway or a data shape is named
1461
+ * by a free `bpmn:label` text grouped with it, which the family cannot see
1462
+ * (`packages/affine/blocks/surface/src/extensions/validation.ts`, the
1463
+ * label-presence evaluator). Left on `bpmn:flow-object`, the rule would report
1464
+ * every NAMED event and gateway as unnamed.
1465
+ *
1466
+ * Losing them costs little: BPMN practice commonly leaves an event or a
1467
+ * converging gateway unnamed, while an activity is a box whose whole content is
1468
+ * its name — an empty one says nothing at all. The rule id is kept (ids are
1469
+ * forever); it was shipped as "step", and an activity is the step par
1470
+ * excellence.
1471
+ *
1472
+ * ponytail: the ceiling is "a gateway that asks no question is not reported".
1473
+ * The upgrade path is a label-presence mode that also reads the `labelRole`
1474
+ * member of the subject's group, the way `readName` does for the reading
1475
+ * panel — an engine change, not a pack one.
1460
1476
  */
1461
1477
  const unlabeledStep = {
1462
1478
  id: 'bpmn.unlabeled-step',
1463
1479
  framework: 'bpmn',
1464
1480
  family: 'label-presence',
1465
1481
  severity: 'audit',
1466
- appliesTo: BPMN_ROLE.flowObject,
1482
+ appliesTo: BPMN_ROLE.activity,
1467
1483
  roles: BPMN_ROLES,
1468
1484
  messageKey: 'com.labre.bpmn.validation.unlabeled-step',
1469
1485
  messageFallback: 'This step has no name.',
1470
1486
  suggestionKey: 'com.labre.bpmn.validation.unlabeled-step.suggestion',
1471
- suggestionFallback: 'Name it in a verb phrase a reader outside the room would understand — "Check the credit limit" rather than "Step 3". An unnamed gateway is worse still: the whole content of a decision is the question it asks.',
1487
+ suggestionFallback: 'Name it in a verb phrase a reader outside the room would understand — "Check the credit limit" rather than "Step 3".',
1472
1488
  version: 1,
1473
1489
  // A name is nowhere required by the specification: an unnamed step is a
1474
1490
  // diagram that is not done, never one that is wrong.
@@ -23,6 +23,18 @@ export declare const SIMPLE_PROCESS_SEED: {
23
23
  readonly key: "com.labre.bpmn.example.simple-process.reject";
24
24
  readonly fallback: "Reject";
25
25
  };
26
+ readonly requestReceived: {
27
+ readonly key: "com.labre.bpmn.example.simple-process.request-received";
28
+ readonly fallback: "Request received";
29
+ };
30
+ readonly approved: {
31
+ readonly key: "com.labre.bpmn.example.simple-process.approved";
32
+ readonly fallback: "Approved?";
33
+ };
34
+ readonly requestHandled: {
35
+ readonly key: "com.labre.bpmn.example.simple-process.request-handled";
36
+ readonly fallback: "Request handled";
37
+ };
26
38
  };
27
39
  /**
28
40
  * The two worked scenes' and the free-standing "Sequence flow" swatch's own
@@ -53,5 +65,13 @@ export declare const MESSAGE_EXCHANGE_SEED: {
53
65
  readonly key: "com.labre.bpmn.example.message-exchange.confirm-order";
54
66
  readonly fallback: "Confirm order";
55
67
  };
68
+ readonly orderNeeded: {
69
+ readonly key: "com.labre.bpmn.example.message-exchange.order-needed";
70
+ readonly fallback: "Order needed";
71
+ };
72
+ readonly orderPlaced: {
73
+ readonly key: "com.labre.bpmn.example.message-exchange.order-placed";
74
+ readonly fallback: "Order placed";
75
+ };
56
76
  };
57
77
  export declare const bpmnTemplateCategory: TemplateCategory;
@@ -1,9 +1,9 @@
1
- import { makeTemplateSnapshot, surfaceText, templateFromCommand, } from '@formicoidea/labre-core/gfx/template';
1
+ import { makeTemplateSnapshot, surfaceText, surfaceYMap, templateFromCommand, } from '@formicoidea/labre-core/gfx/template';
2
2
  import { ConnectorMode, PointStyle, StrokeStyle, } from '@formicoidea/labre-core/model';
3
3
  import { translateKey, } from '@formicoidea/labre-core/shared/services';
4
4
  import { bpmnCommands } from '../commands.js';
5
5
  import { EVENT_END, EVENT_START, GROUP_STROKE, MESSAGE_STROKE, MESSAGE_WIDTH, NEUTRAL_STROKE, NODE_SIZE, POOL_BAND_FILL, POOL_FRAME_COLOR, SEQUENCE_STROKE, SEQUENCE_WIDTH, } from '../consts.js';
6
- import { bpmnNodeProps, bpmnPoolProps } from '../presets.js';
6
+ import { bpmnLabelBoxFor, bpmnLabelProps, bpmnNodeProps, bpmnPoolProps, } from '../presets.js';
7
7
  import { BPMN_ROLE } from '../roles.js';
8
8
  /**
9
9
  * The seeds the two worked scenes write — declared once so the scene builder
@@ -28,6 +28,22 @@ export const SIMPLE_PROCESS_SEED = {
28
28
  key: 'com.labre.bpmn.example.simple-process.reject',
29
29
  fallback: 'Reject',
30
30
  },
31
+ // The three names the scene's events and gateway carry since they gravitate
32
+ // (R38): a worked example names its punctual symbols the way BPMN practice
33
+ // does — a state for an event, a question for a gateway — rather than
34
+ // repeating the artefact's kind under it.
35
+ requestReceived: {
36
+ key: 'com.labre.bpmn.example.simple-process.request-received',
37
+ fallback: 'Request received',
38
+ },
39
+ approved: {
40
+ key: 'com.labre.bpmn.example.simple-process.approved',
41
+ fallback: 'Approved?',
42
+ },
43
+ requestHandled: {
44
+ key: 'com.labre.bpmn.example.simple-process.request-handled',
45
+ fallback: 'Request handled',
46
+ },
31
47
  };
32
48
  /**
33
49
  * The two worked scenes' and the free-standing "Sequence flow" swatch's own
@@ -67,6 +83,15 @@ export const MESSAGE_EXCHANGE_SEED = {
67
83
  key: 'com.labre.bpmn.example.message-exchange.confirm-order',
68
84
  fallback: 'Confirm order',
69
85
  },
86
+ // The customer's two events, named since they gravitate (R38).
87
+ orderNeeded: {
88
+ key: 'com.labre.bpmn.example.message-exchange.order-needed',
89
+ fallback: 'Order needed',
90
+ },
91
+ orderPlaced: {
92
+ key: 'com.labre.bpmn.example.message-exchange.order-placed',
93
+ fallback: 'Order placed',
94
+ },
70
95
  };
71
96
  /**
72
97
  * One seed's resolved text — the host's catalogue when `std` is a real
@@ -121,6 +146,30 @@ function node(kind, x, y, text) {
121
146
  ...(text === undefined ? {} : { text: surfaceText(text) }),
122
147
  });
123
148
  }
149
+ /**
150
+ * A scene node whose name GRAVITATES (R38, `bpmnLabelMode`): the symbol with no
151
+ * inner text, its label centred under it, and the native group that binds the
152
+ * two — the three elements `createBpmnNode` draws for the same kind, and the
153
+ * pattern Wardley's shipped maps use (`pair()` in `gfx/wardley`).
154
+ *
155
+ * Spread into a scene: `<id>` is the symbol, so connectors keep binding to it,
156
+ * `<id>Label` its name and `<id>Group` the pair.
157
+ */
158
+ function named(id, kind, x, y, text) {
159
+ const { w, h } = NODE_SIZE[kind];
160
+ const at = bpmnLabelBoxFor(kind, x + w / 2, y + h / 2);
161
+ const label = `${id}Label`;
162
+ return {
163
+ [id]: node(kind, x, y),
164
+ // A snapshot carries a serialized `Y.Text`, where `addElement` takes a
165
+ // plain string.
166
+ [label]: { ...bpmnLabelProps(text, at.x, at.y), text: surfaceText(text) },
167
+ [`${id}Group`]: {
168
+ type: 'group',
169
+ children: surfaceYMap({ [id]: true, [label]: true }),
170
+ },
171
+ };
172
+ }
124
173
  /** A scene participant, from the same description the toolbox draws from. */
125
174
  const pool = (x, y, w, h, name) => defined(bpmnPoolProps({ xywh: `[${x},${y},${w},${h}]`, name }));
126
175
  /** Drop the keys a preset left undefined — what `addElement` would store. */
@@ -261,15 +310,24 @@ const scene = (name, preview, build, nameKey) => ({
261
310
  content: makeTemplateSnapshot(build(), name),
262
311
  localize: std => makeTemplateSnapshot(build(std), name),
263
312
  });
313
+ /**
314
+ * Start, a task, an exclusive split into two tasks, and the end they both reach.
315
+ *
316
+ * Laid out for the 180×108 activity (R38) on one horizontal axis at y = 160:
317
+ * the two branches sit 40 units apart with 32 units of margin to the pool's
318
+ * edges, and every symbol's gravitating label lands inside the pool as well —
319
+ * `bpmnPoolOf` judges whole containment, and a name hanging out of its
320
+ * participant would read as belonging to no one.
321
+ */
264
322
  function process(std) {
265
323
  return {
266
- pool: pool(0, 0, 640, 200, seedText(std, SIMPLE_PROCESS_SEED.poolName)),
267
- start: node('startEvent', 40, 72),
268
- task1: node('task', 116, 64, seedText(std, SIMPLE_PROCESS_SEED.submitRequest)),
269
- gw: node('gatewayExclusive', 272, 64),
270
- task2: node('task', 376, 20, seedText(std, SIMPLE_PROCESS_SEED.fulfil)),
271
- task3: node('task', 376, 124, seedText(std, SIMPLE_PROCESS_SEED.reject)),
272
- end: node('endEvent', 556, 72),
324
+ pool: pool(0, 0, 840, 320, seedText(std, SIMPLE_PROCESS_SEED.poolName)),
325
+ ...named('start', 'startEvent', 72, 132, seedText(std, SIMPLE_PROCESS_SEED.requestReceived)),
326
+ task1: node('task', 168, 106, seedText(std, SIMPLE_PROCESS_SEED.submitRequest)),
327
+ ...named('gw', 'gatewayExclusive', 388, 124, seedText(std, SIMPLE_PROCESS_SEED.approved)),
328
+ task2: node('task', 500, 32, seedText(std, SIMPLE_PROCESS_SEED.fulfil)),
329
+ task3: node('task', 500, 180, seedText(std, SIMPLE_PROCESS_SEED.reject)),
330
+ ...named('end', 'endEvent', 720, 132, seedText(std, SIMPLE_PROCESS_SEED.requestHandled)),
273
331
  c1: seq('start', 'task1'),
274
332
  c2: seq('task1', 'gw'),
275
333
  c3: seq('gw', 'task2'),
@@ -288,7 +346,9 @@ function process(std) {
288
346
  * way to show that — the solid arrow stays home, the dashed one crosses.
289
347
  *
290
348
  * Stacked vertically with a 40-unit gutter, so the message flow is a straight
291
- * orthogonal drop between two tasks that already line up.
349
+ * orthogonal drop between two tasks that already line up. Each 200-high pool
350
+ * centres its 108-high activity (R38), and the customer's two events keep
351
+ * their gravitating names inside the pool.
292
352
  *
293
353
  * ## The end event, which is not decoration
294
354
  *
@@ -304,10 +364,10 @@ function messageExchange(std) {
304
364
  return {
305
365
  customer: pool(0, 0, 640, 200, seedText(std, MESSAGE_EXCHANGE_SEED.customer)),
306
366
  supplier: pool(0, 240, 640, 200, seedText(std, MESSAGE_EXCHANGE_SEED.supplier)),
307
- start: node('startEvent', 40, 72),
308
- ask: node('taskUser', 140, 64, seedText(std, MESSAGE_EXCHANGE_SEED.placeOrder)),
309
- done: node('endEvent', 320, 72),
310
- answer: node('taskService', 140, 304, seedText(std, MESSAGE_EXCHANGE_SEED.confirmOrder)),
367
+ ...named('start', 'startEvent', 72, 72, seedText(std, MESSAGE_EXCHANGE_SEED.orderNeeded)),
368
+ ask: node('taskUser', 168, 46, seedText(std, MESSAGE_EXCHANGE_SEED.placeOrder)),
369
+ ...named('done', 'endEvent', 388, 72, seedText(std, MESSAGE_EXCHANGE_SEED.orderPlaced)),
370
+ answer: node('taskService', 168, 286, seedText(std, MESSAGE_EXCHANGE_SEED.confirmOrder)),
311
371
  // Inside the first participant: what happens, and in what order.
312
372
  inside: seq('start', 'ask'),
313
373
  closes: seq('ask', 'done'),
@@ -27,7 +27,7 @@ export declare const bpmnPoolToolbarConfig: {
27
27
  }, import("@formicoidea/labre-core/shared/services").ToolbarActionGenerator];
28
28
  readonly when: (ctx: ToolbarContext) => boolean;
29
29
  };
30
- export declare const bpmnPoolToolbarExtension: import("@formicoidea/labre-core/store/index.js").ExtensionType;
30
+ export declare const bpmnPoolToolbarExtension: import("@formicoidea/labre-core/_pkgs/store/index.js").ExtensionType;
31
31
  /**
32
32
  * The pool's FLAG-GATED row: the legend and the validation dropdown, in one
33
33
  * module because a flavour may carry exactly one (a second on the same key
@@ -47,4 +47,4 @@ export declare const bpmnPoolToolbarExtension: import("@formicoidea/labre-core/s
47
47
  * way.
48
48
  */
49
49
  export declare const bpmnPoolToolingToolbarConfig: ToolbarModuleConfig;
50
- export declare const bpmnPoolToolingToolbarExtension: import("@formicoidea/labre-core/store/index.js").ExtensionType;
50
+ export declare const bpmnPoolToolingToolbarExtension: import("@formicoidea/labre-core/_pkgs/store/index.js").ExtensionType;
@@ -0,0 +1,21 @@
1
+ import { type ToolbarContext, type ToolbarModuleConfig } from '@formicoidea/labre-core/shared/services';
2
+ /**
3
+ * Every selected BPMN node wears its name INSIDE the shape.
4
+ *
5
+ * An event, a gateway or a data shape names itself with a grouped `bpmn:label`
6
+ * text under the symbol (R38), so "Add text" on one would write a second,
7
+ * inner name the notation never draws there. The gesture exists for the
8
+ * inscribed kinds only — the same gate `bpmnNodeEditsInnerText` puts on the
9
+ * double-click, minus the legacy exception: a legacy event's inner text is
10
+ * already there, and "Add text" only ever shows on a node with none.
11
+ */
12
+ export declare function bpmnSelectionIsInscribed(ctx: ToolbarContext): boolean;
13
+ export declare const bpmnNodeToolbarConfig: ToolbarModuleConfig;
14
+ /**
15
+ * The base shape features on a selected BPMN node, on the BARE flavour key.
16
+ * Registered from the ALWAYS-ON view extension: recolouring or re-typesetting
17
+ * a stored element is editing content, not tooling (`docs/adr/0009`). The
18
+ * "Change type" morph, which IS tooling, takes an owner-suffixed key beside
19
+ * it (`view.ts`).
20
+ */
21
+ export declare const bpmnNodeToolbarExtension: import("@formicoidea/labre-core/_pkgs/store/index.js").ExtensionType;